aboutsummaryrefslogtreecommitdiff
path: root/vnext
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2023-05-31 06:10:51 +0300
committerGravatar Vitaly Takmazov2023-05-31 06:11:05 +0300
commitf2a7ea3af919548d41383734e8a3667086a44bcc (patch)
treee0d9db07956f10ccc4e27fc9e0d45d88950dbfbb /vnext
parent395185d093b2d6bb46d02830088e00a3a2ca20c1 (diff)
eslint: enforce semicolons only before statement continuation chars
Diffstat (limited to 'vnext')
-rw-r--r--vnext/server/common/MessageUtils.js28
-rw-r--r--vnext/server/common/MessageUtils.spec.js32
-rw-r--r--vnext/server/durov.js54
-rw-r--r--vnext/server/hms.js44
-rw-r--r--vnext/server/http.js34
-rw-r--r--vnext/server/index.js64
-rw-r--r--vnext/server/middleware/event.js74
-rw-r--r--vnext/server/middleware/oembed.js20
-rw-r--r--vnext/server/middleware/renderer.js84
-rw-r--r--vnext/server/middleware/urlexpand.js8
-rw-r--r--vnext/server/sape.js34
-rw-r--r--vnext/server/sender.js138
-rw-r--r--vnext/server/webpack.config.js20
-rw-r--r--vnext/src/App.js124
-rw-r--r--vnext/src/api/index.js144
-rw-r--r--vnext/src/index.js50
-rw-r--r--vnext/src/ui/Avatar.js10
-rw-r--r--vnext/src/ui/Button.js6
-rw-r--r--vnext/src/ui/Chat.js70
-rw-r--r--vnext/src/ui/Comment.js34
-rw-r--r--vnext/src/ui/Contact.js8
-rw-r--r--vnext/src/ui/Contacts.js24
-rw-r--r--vnext/src/ui/Feeds.js118
-rw-r--r--vnext/src/ui/Header.js22
-rw-r--r--vnext/src/ui/Icon.js28
-rw-r--r--vnext/src/ui/Login.js48
-rw-r--r--vnext/src/ui/Message.js66
-rw-r--r--vnext/src/ui/MessageInput.js90
-rw-r--r--vnext/src/ui/PM.js18
-rw-r--r--vnext/src/ui/Post.js52
-rw-r--r--vnext/src/ui/SearchBox.js12
-rw-r--r--vnext/src/ui/Settings.js100
-rw-r--r--vnext/src/ui/Spinner.js10
-rw-r--r--vnext/src/ui/Thread.js130
-rw-r--r--vnext/src/ui/UploadButton.js20
-rw-r--r--vnext/src/ui/UserInfo.js70
-rw-r--r--vnext/src/ui/Users.js22
-rw-r--r--vnext/src/ui/VisitorContext.js16
-rw-r--r--vnext/src/ui/__tests__/Avatar.test.js14
-rw-r--r--vnext/src/ui/__tests__/MessageInput.test.js48
-rw-r--r--vnext/src/ui/__tests__/UserLink.test.js18
-rw-r--r--vnext/src/ui/helpers/BubbleStyle.js8
-rw-r--r--vnext/src/utils/embed.js260
-rw-r--r--vnext/webpack.config.js24
44 files changed, 1147 insertions, 1151 deletions
diff --git a/vnext/server/common/MessageUtils.js b/vnext/server/common/MessageUtils.js
index b7ed8ec0..bb3d791f 100644
--- a/vnext/server/common/MessageUtils.js
+++ b/vnext/server/common/MessageUtils.js
@@ -3,7 +3,7 @@
* @param {import('../../src/api').Message} msg message
*/
export function isPM(msg) {
- return !msg.mid;
+ return !msg.mid
}
/**
@@ -11,7 +11,7 @@
* @param {import('../../src/api').Message} msg message
*/
export function isReply(msg) {
- return msg.rid && msg.rid > 0;
+ return msg.rid && msg.rid > 0
}
/**
@@ -19,7 +19,7 @@ export function isReply(msg) {
* @param {import('../../src/api').Message} msg message
*/
export function isService(msg) {
- return msg.service && msg.service;
+ return msg.service && msg.service
}
/**
@@ -29,11 +29,11 @@ export function isService(msg) {
*/
export function formatTitle(msg) {
if (isReply(msg)) {
- return `Reply by ${msg.user.uname}:`;
+ return `Reply by ${msg.user.uname}:`
} else if (isPM(msg)) {
- return `Private message from ${msg.user.uname}:`;
+ return `Private message from ${msg.user.uname}:`
}
- return `${msg.user.uname}`;
+ return `${msg.user.uname}`
}
/**
@@ -43,11 +43,11 @@ export function formatTitle(msg) {
*/
export function formatQuote(msg) {
if (isReply(msg)) {
- return msg.replyQuote || '';
+ return msg.replyQuote || ''
} else if (isPM(msg)) {
- return '';
+ return ''
}
- return (msg.tags || []).map(t => `*${t}`).join(', ');
+ return (msg.tags || []).map(t => `*${t}`).join(', ')
}
/**
@@ -56,19 +56,19 @@ export function formatQuote(msg) {
* @returns {string} formatted body
*/
export function formatMessage(msg) {
- return msg.body || 'Sent an image';
+ return msg.body || 'Sent an image'
}
-const baseURL = 'https://juick.com';
+const baseURL = 'https://juick.com'
/**
* @param {import('../api').Message} msg message
*/
export function formatUrl(msg) {
if (isReply(msg)) {
- return `${baseURL}/m/${msg.mid}#${msg.rid}`;
+ return `${baseURL}/m/${msg.mid}#${msg.rid}`
} else if (isPM(msg)) {
- return `${baseURL}/pm/inbox`;
+ return `${baseURL}/pm/inbox`
}
- return `${baseURL}/m/${msg.mid}`;
+ return `${baseURL}/m/${msg.mid}`
}
diff --git a/vnext/server/common/MessageUtils.spec.js b/vnext/server/common/MessageUtils.spec.js
index 5e6f64c2..33336795 100644
--- a/vnext/server/common/MessageUtils.spec.js
+++ b/vnext/server/common/MessageUtils.spec.js
@@ -1,4 +1,4 @@
-import { formatTitle, formatMessage, formatQuote } from './MessageUtils';
+import { formatTitle, formatMessage, formatQuote } from './MessageUtils'
describe('Message formatting', () => {
it('Blog message', () => {
@@ -13,11 +13,11 @@ describe('Message formatting', () => {
'people'
],
'body': 'The message'
- };
- expect(formatTitle(msg)).toMatchSnapshot();
- expect(formatQuote(msg)).toMatchSnapshot();
- expect(formatMessage(msg)).toMatchSnapshot();
- });
+ }
+ expect(formatTitle(msg)).toMatchSnapshot()
+ expect(formatQuote(msg)).toMatchSnapshot()
+ expect(formatMessage(msg)).toMatchSnapshot()
+ })
it('Reply message', () => {
let msg = {
'mid': 1,
@@ -28,11 +28,11 @@ describe('Message formatting', () => {
},
'replyQuote': '> The message',
'body': 'The #reply #bla'
- };
- expect(formatTitle(msg)).toMatchSnapshot();
- expect(formatQuote(msg)).toMatchSnapshot();
- expect(formatMessage(msg)).toMatchSnapshot();
- });
+ }
+ expect(formatTitle(msg)).toMatchSnapshot()
+ expect(formatQuote(msg)).toMatchSnapshot()
+ expect(formatMessage(msg)).toMatchSnapshot()
+ })
it('PM', () => {
let msg = {
'user': {
@@ -40,8 +40,8 @@ describe('Message formatting', () => {
'uname': 'ugnich'
},
'body': 'The PM'
- };
- expect(formatTitle(msg)).toMatchSnapshot();
- expect(formatMessage(msg)).toMatchSnapshot();
- });
-});
+ }
+ expect(formatTitle(msg)).toMatchSnapshot()
+ expect(formatMessage(msg)).toMatchSnapshot()
+ })
+})
diff --git a/vnext/server/durov.js b/vnext/server/durov.js
index e9bbdb9a..6ab1831f 100644
--- a/vnext/server/durov.js
+++ b/vnext/server/durov.js
@@ -1,52 +1,52 @@
-import TelegramBot from 'node-telegram-bot-api';
-import debug from 'debug';
-var log = debug('durov');
-import config from 'config';
+import TelegramBot from 'node-telegram-bot-api'
+import debug from 'debug'
+var log = debug('durov')
+import config from 'config'
-import { formatQuote, formatTitle } from './common/MessageUtils';
-import { format } from '../src/utils/embed';
+import { formatQuote, formatTitle } from './common/MessageUtils'
+import { format } from '../src/utils/embed'
-let sender;
-let demouser;
-let bot_token;
-const durov_token_key = 'service.durov.token';
+let sender
+let demouser
+let bot_token
+const durov_token_key = 'service.durov.token'
if (config.has(durov_token_key)) {
- bot_token = config.get(durov_token_key);
- sender = new TelegramBot(bot_token);
- demouser = config.get('service.durov.demouser');
+ bot_token = config.get(durov_token_key)
+ sender = new TelegramBot(bot_token)
+ demouser = config.get('service.durov.demouser')
sender.setWebHook(`${config.get('service.baseURL')}/api/${bot_token}`).then(() => {
- log('Webhook is set');
- }).catch(console.log);
+ log('Webhook is set')
+ }).catch(console.log)
sender.on('message', msg => {
- log(`MESSAGE: ${JSON.stringify(msg)}`);
- });
+ log(`MESSAGE: ${JSON.stringify(msg)}`)
+ })
}
export const webhookPath = () => {
- return bot_token;
-};
+ return bot_token
+}
export const webhook = (req, res) => {
- sender.processUpdate(JSON.parse(req.body));
- res.sendStatus(200);
-};
+ sender.processUpdate(JSON.parse(req.body))
+ res.sendStatus(200)
+}
/**
* @param {import('../src/api').Message} msg
* @param {string[]} subscribers
*/
export const sendTelegramNotification = (msg, subscribers) => {
- log(`Telegram event: ${JSON.stringify(msg)}, ${subscribers} ${subscribers.length}`);
+ log(`Telegram event: ${JSON.stringify(msg)}, ${subscribers} ${subscribers.length}`)
if (!msg.service) {
if (subscribers && subscribers.includes(demouser)) {
- const message = `${formatTitle(msg)}\n${formatQuote(msg)}\n${format(msg.body, msg.uid, false)}`;
- log(message);
+ const message = `${formatTitle(msg)}\n${formatQuote(msg)}\n${format(msg.body, msg.uid, false)}`
+ log(message)
sender.sendMessage(demouser, message, {
parse_mode: 'HTML',
disable_web_page_preview: true
- }).then(log).catch(log);
+ }).then(log).catch(log)
}
}
-};
+}
diff --git a/vnext/server/hms.js b/vnext/server/hms.js
index 1f897cef..66bbad82 100644
--- a/vnext/server/hms.js
+++ b/vnext/server/hms.js
@@ -1,32 +1,32 @@
-import axios from 'axios';
-import config from 'config';
-import debug from 'debug';
-var log = debug('hms');
+import axios from 'axios'
+import config from 'config'
+import debug from 'debug'
+var log = debug('hms')
-const { client_id, client_secret } = config.has('service.hms') ? config.get('service.hms') : {};
+const { client_id, client_secret } = config.has('service.hms') ? config.get('service.hms') : {}
const refreshToken = async () => {
if (client_id) {
- const params = new URLSearchParams();
- params.append('grant_type', 'client_credentials');
- params.append('client_id', client_id);
- params.append('client_secret', client_secret);
- const res = await axios.post('https://oauth-login.cloud.huawei.com/oauth2/v3/token', params).catch(console.log);
+ const params = new URLSearchParams()
+ params.append('grant_type', 'client_credentials')
+ params.append('client_id', client_id)
+ params.append('client_secret', client_secret)
+ const res = await axios.post('https://oauth-login.cloud.huawei.com/oauth2/v3/token', params).catch(console.log)
try {
- log(`HMS response: ${JSON.stringify(res.data)}`);
- const access = res.data;
- log(`HMS access token: ${access.access_token}`);
- return access.access_token;
+ log(`HMS response: ${JSON.stringify(res.data)}`)
+ const access = res.data
+ log(`HMS access token: ${access.access_token}`)
+ return access.access_token
} catch (error) {
- log(error);
- return '';
+ log(error)
+ return ''
}
}
- return '';
-};
+ return ''
+}
export const send = async (msg, tokenList = []) => {
- const adminToken = await refreshToken();
+ const adminToken = await refreshToken()
if (adminToken) {
const response = await axios.post(`https://push-api.cloud.huawei.com/v1/${client_id}/messages:send`, {
headers: {
@@ -52,7 +52,7 @@ export const send = async (msg, tokenList = []) => {
'token': tokenList
}
}
- }).catch(log);
- log(`hcm: ${response.status}`);
+ }).catch(log)
+ log(`hcm: ${response.status}`)
}
-};
+}
diff --git a/vnext/server/http.js b/vnext/server/http.js
index 0ffa8bfe..a90afef3 100644
--- a/vnext/server/http.js
+++ b/vnext/server/http.js
@@ -1,7 +1,7 @@
-import axios from 'axios';
-import config from 'config';
-import debug from 'debug';
-var log = debug('http');
+import axios from 'axios'
+import config from 'config'
+import debug from 'debug'
+var log = debug('http')
/**
@@ -10,7 +10,6 @@ var log = debug('http');
*/
/**
* fetch message subscribers
- *
* @param {URLSearchParams} params - request params
* @returns {Promise<import('../client').SecureUser[]>} subscribers list
*/
@@ -18,26 +17,25 @@ export function subscribers(params) {
return new Promise((resolve, reject) => {
client.get(`/notifications?${params.toString()}`)
.then(response => {
- log(`CODE: ${response.status}`);
- resolve(response.data);
+ log(`CODE: ${response.status}`)
+ resolve(response.data)
})
- .catch(reason => { reject(reason); });
- });
+ .catch(reason => { reject(reason) })
+ })
}
/**
* delete invalid tokens
- *
* @param {import('../client').Token[]} tokens tokens
*/
export const deleteSubscribers = async (tokens) => {
- const response = await client.post('/notifications/delete', tokens);
- return response.data;
-};
+ const response = await client.post('/notifications/delete', tokens)
+ return response.data
+}
-const baseURL = config.get('service.baseURL') + '/api';
-const user = config.get('service.user') || process.env.JUICK_SERVICE_USER;
-const password = config.get('service.password') || process.env.JUICK_SERVICE_PASSWORD;
+const baseURL = config.get('service.baseURL') + '/api'
+const user = config.get('service.user') || process.env.JUICK_SERVICE_USER
+const password = config.get('service.password') || process.env.JUICK_SERVICE_PASSWORD
/** @type { import('axios').AxiosInstance } */
let client = axios.create({
@@ -45,5 +43,5 @@ let client = axios.create({
headers: user ? {
'Authorization': 'Basic ' + Buffer.from(user + ':' + password).toString('base64')
} : {}
-});
-log(`HTTP client initialized with base URL ${baseURL} ${ user? `and ${user} user` : ''}`);
+})
+log(`HTTP client initialized with base URL ${baseURL} ${ user? `and ${user} user` : ''}`)
diff --git a/vnext/server/index.js b/vnext/server/index.js
index 5bfa9070..8eb86981 100644
--- a/vnext/server/index.js
+++ b/vnext/server/index.js
@@ -1,53 +1,53 @@
-import express from 'express';
-import { raw } from 'body-parser';
-import cors from 'cors';
-import config from 'config';
-import debug from 'debug';
-const log = debug('http');
-
-import serverRenderer from './middleware/renderer';
-import event from './middleware/event';
-import oembed from './middleware/oembed';
-import urlExpand from './middleware/urlexpand';
-
-const PORT = 8081;
-import path from 'path';
-import { webhook, webhookPath } from './durov';
+import express from 'express'
+import { raw } from 'body-parser'
+import cors from 'cors'
+import config from 'config'
+import debug from 'debug'
+const log = debug('http')
+
+import serverRenderer from './middleware/renderer'
+import event from './middleware/event'
+import oembed from './middleware/oembed'
+import urlExpand from './middleware/urlexpand'
+
+const PORT = 8081
+import path from 'path'
+import { webhook, webhookPath } from './durov'
// initialize the application and create the routes
-const app = express();
-app.use(raw());
-app.use(cors());
-const router = express.Router();
+const app = express()
+app.use(raw())
+app.use(cors())
+const router = express.Router()
-router.post('/api/sender', event);
-router.get('/api/oembed', oembed);
-router.get('/api/urlexpand', urlExpand);
+router.post('/api/sender', event)
+router.get('/api/oembed', oembed)
+router.get('/api/urlexpand', urlExpand)
-const durov_webhook = webhookPath();
+const durov_webhook = webhookPath()
if (durov_webhook) {
- router.post(`/api/${durov_webhook}`, webhook);
+ router.post(`/api/${durov_webhook}`, webhook)
}
-router.use('^/$', serverRenderer);
+router.use('^/$', serverRenderer)
-const STATIC_ROOT = config.get('service.static_root') || path.resolve(__dirname, 'public');
+const STATIC_ROOT = config.get('service.static_root') || path.resolve(__dirname, 'public')
// other static resources should just be served as they are
router.use(express.static(
STATIC_ROOT,
{ maxAge: '30d' },
-));
+))
-router.use('*', serverRenderer);
+router.use('*', serverRenderer)
-app.use(router);
+app.use(router)
// start the app
app.listen(PORT, (error) => {
if (error) {
- return console.log('something bad happened', error);
+ return console.log('something bad happened', error)
}
- log('listening on ' + PORT + '...');
-});
+ log('listening on ' + PORT + '...')
+})
diff --git a/vnext/server/middleware/event.js b/vnext/server/middleware/event.js
index 5681eb59..59f36950 100644
--- a/vnext/server/middleware/event.js
+++ b/vnext/server/middleware/event.js
@@ -1,54 +1,54 @@
-import { simpleParser } from 'mailparser';
-import { isPM, isReply, isService } from '../common/MessageUtils';
-import { sendTelegramNotification } from '../durov';
-import { subscribers } from '../http';
-import { sendNotification, buildNotification } from '../sender';
-import debug from 'debug';
-import { send } from '../hms';
-var log = debug('event');
+import { simpleParser } from 'mailparser'
+import { isPM, isReply, isService } from '../common/MessageUtils'
+import { sendTelegramNotification } from '../durov'
+import { subscribers } from '../http'
+import { sendNotification, buildNotification } from '../sender'
+import debug from 'debug'
+import { send } from '../hms'
+var log = debug('event')
/** @type {number[]} */
-const allSandboxIds = [];
+const allSandboxIds = []
/**
* handle message event
* @param {import('../../client').Message} msg message
*/
function processMessageEvent(msg) {
- let params = {};
- params.uid = isPM(msg) ? msg.to.uid : msg.user.uid;
+ let params = {}
+ params.uid = isPM(msg) ? msg.to.uid : msg.user.uid
if (isReply(msg)) {
- params.mid = msg.mid;
- params.rid = msg.rid;
+ params.mid = msg.mid
+ params.rid = msg.rid
} else if (!isPM(msg) && !isService(msg)) {
- params.mid = msg.mid;
+ params.mid = msg.mid
}
subscribers(new URLSearchParams(JSON.parse(JSON.stringify(params)))).then(users => {
users.forEach(user => {
- log(`${user.uname}: ${user.unreadCount}`);
+ log(`${user.uname}: ${user.unreadCount}`)
let [sandboxTokens, productionTokens] = (user.tokens || [])
.filter(t => ['mpns', 'apns', 'fcm', 'web'].includes(t.type))
.map(t => t.type === 'web' ? JSON.parse(t.token) : t.token)
.reduce((result, element, i) => {
allSandboxIds.includes(user.uid)
? result[0].push(element)
- : result[1].push(element);
- return result;
- }, [[], []]);
- sendNotification(productionTokens, sandboxTokens, buildNotification(user, msg));
+ : result[1].push(element)
+ return result
+ }, [[], []])
+ sendNotification(productionTokens, sandboxTokens, buildNotification(user, msg))
let durovIds = (user.tokens || [])
.filter(t => ['durov'].includes(t.type))
- .map(t => t.token);
- sendTelegramNotification(msg, durovIds);
+ .map(t => t.token)
+ sendTelegramNotification(msg, durovIds)
let hmsIds = (user.tokens || [])
.filter(t => t.type === 'hcm')
- .map(t => t.token);
- log(`${user.uname}: ${hmsIds}`);
+ .map(t => t.token)
+ log(`${user.uname}: ${hmsIds}`)
send(msg, hmsIds).then(() => {
// TODO: handle results
- }).catch(log);
- });
- }).catch(log);
+ }).catch(log)
+ })
+ }).catch(log)
}
/**
@@ -58,30 +58,30 @@ function processMessageEvent(msg) {
export default function handleMessage(req, res) {
return simpleParser(req.body, {})
.then(parsed => {
- const new_version = parsed.headers.get('x-event-version') == '1.0';
- log(`New event: ${parsed.text}, new version: ${new_version}`);
+ const new_version = parsed.headers.get('x-event-version') == '1.0'
+ log(`New event: ${parsed.text}, new version: ${new_version}`)
if (new_version) {
/** @type {import('../../client').SystemEvent} */
- const event = JSON.parse(parsed.text || '');
+ const event = JSON.parse(parsed.text || '')
if (event.type === 'message' && event.message) {
if (event.message.service) {
// TODO: remove
- let msg = { ...event.message };
+ let msg = { ...event.message }
if (event.from) {
- msg.user = event.from;
+ msg.user = event.from
}
- processMessageEvent(msg);
+ processMessageEvent(msg)
} else {
- processMessageEvent(event.message);
+ processMessageEvent(event.message)
}
}
} else {
/** @type {import('../../client').Message} */
- const msg = JSON.parse(parsed.text || '');
- processMessageEvent(msg);
+ const msg = JSON.parse(parsed.text || '')
+ processMessageEvent(msg)
}
- res.end();
+ res.end()
})
- .catch(err => { log(err); res.status(400).send('Invalid request'); });
+ .catch(err => { log(err); res.status(400).send('Invalid request') })
}
diff --git a/vnext/server/middleware/oembed.js b/vnext/server/middleware/oembed.js
index 8b1ed61d..5d062228 100644
--- a/vnext/server/middleware/oembed.js
+++ b/vnext/server/middleware/oembed.js
@@ -1,19 +1,19 @@
-import { embeddedTweet } from '../../src/api';
+import { embeddedTweet } from '../../src/api'
/**
* Return content for embedding
* @type {import('express').RequestParamHandler}
*/
const oembed = async (req, res) => {
- let url = (req.query.url || '').toString();
+ let url = (req.query.url || '').toString()
return embeddedTweet(url).then(result => {
- res.send(result);
- res.end();
+ res.send(result)
+ res.end()
}).catch(err => {
- console.log(`HTTP ${err.response ? err.response.status : err.code}: ${url}`);
- res.sendStatus(err.response ? err.response.status : 500);
- res.end();
- });
-};
+ console.log(`HTTP ${err.response ? err.response.status : err.code}: ${url}`)
+ res.sendStatus(err.response ? err.response.status : 500)
+ res.end()
+ })
+}
-export default oembed;
+export default oembed
diff --git a/vnext/server/middleware/renderer.js b/vnext/server/middleware/renderer.js
index debba758..e7e0324e 100644
--- a/vnext/server/middleware/renderer.js
+++ b/vnext/server/middleware/renderer.js
@@ -1,54 +1,54 @@
-import * as ReactDOMServer from 'react-dom/server';
-import cookie from 'cookie';
-import config from 'config';
+import * as ReactDOMServer from 'react-dom/server'
+import cookie from 'cookie'
+import config from 'config'
// import our main App component
-import App from '../../src/App';
+import App from '../../src/App'
-import { getLinks } from '../sape';
-import { StaticRouter } from 'react-router-dom/server';
-import { VisitorProvider } from '../../src/ui/VisitorContext';
+import { getLinks } from '../sape'
+import { StaticRouter } from 'react-router-dom/server'
+import { VisitorProvider } from '../../src/ui/VisitorContext'
-import path from 'path';
-import fs from 'fs';
+import path from 'path'
+import fs from 'fs'
// convert a Unicode string to a string in which
// each 16-bit unit occupies only one byte
function toBinary(string) {
- const codeUnits = new Uint16Array(string.length);
+ const codeUnits = new Uint16Array(string.length)
for (let i = 0; i < codeUnits.length; i++) {
- codeUnits[i] = string.charCodeAt(i);
+ codeUnits[i] = string.charCodeAt(i)
}
- return Buffer.from(String.fromCharCode(...new Uint8Array(codeUnits.buffer))).toString('base64');
+ return Buffer.from(String.fromCharCode(...new Uint8Array(codeUnits.buffer))).toString('base64')
}
-const STATIC_ROOT = config.get('service.static_root') || path.resolve(__dirname, 'public');
+const STATIC_ROOT = config.get('service.static_root') || path.resolve(__dirname, 'public')
const serverRenderer = async (req, res) => {
// point to the html file created by CRA's build tool
- const filePath = path.resolve(STATIC_ROOT, 'index.html');
+ const filePath = path.resolve(STATIC_ROOT, 'index.html')
// links
- const cookies = cookie.parse(req.headers.cookie || '');
+ const cookies = cookie.parse(req.headers.cookie || '')
- const links = await getLinks(req.originalUrl, cookies['sape_cookie']);
+ const links = await getLinks(req.originalUrl, cookies['sape_cookie'])
fs.readFile(filePath, 'utf8', (err, htmlData) => {
if (err) {
- console.error('err', err);
- return res.status(404).end();
+ console.error('err', err)
+ return res.status(404).end()
}
- const routerContext = {};
+ const routerContext = {}
const props = {
footer: links.join(' ')
- };
+ }
- const marker = '<div id="app">';
- const data = htmlData.split(marker);
- const propsData = `<script>window.__PROPS__="${toBinary(JSON.stringify(props))}";</script>${marker}`;
- let didError = false;
+ const marker = '<div id="app">'
+ const data = htmlData.split(marker)
+ const propsData = `<script>window.__PROPS__="${toBinary(JSON.stringify(props))}";</script>${marker}`
+ let didError = false
const { pipe } = ReactDOMServer.renderToPipeableStream(
<VisitorProvider>
<StaticRouter location={req.baseUrl} context={routerContext}>
@@ -57,33 +57,33 @@ const serverRenderer = async (req, res) => {
</VisitorProvider>
, {
onShellReady() {
- res.statusCode = didError ? 500 : 200;
- res.setHeader('Content-type', 'text/html');
- res.write(data[0]);
- res.write(propsData);
- pipe(res, { end: false });
+ res.statusCode = didError ? 500 : 200
+ res.setHeader('Content-type', 'text/html')
+ res.write(data[0])
+ res.write(propsData)
+ pipe(res, { end: false })
},
onShellError() {
- didError = true;
- res.statusCode = 500;
- res.setHeader('Content-type', 'text/html');
+ didError = true
+ res.statusCode = 500
+ res.setHeader('Content-type', 'text/html')
res.send(
'<h1>Something went wrong :(</h1>'
- );
- res.end();
+ )
+ res.end()
},
onAllReady() {
if (!didError) {
- res.write(data[1]);
+ res.write(data[1])
}
- res.end();
+ res.end()
},
onError(err) {
- didError = true;
- console.log(err);
+ didError = true
+ console.log(err)
}
- });
- });
-};
+ })
+ })
+}
-export default serverRenderer;
+export default serverRenderer
diff --git a/vnext/server/middleware/urlexpand.js b/vnext/server/middleware/urlexpand.js
index f78d1cc0..a99f80a7 100644
--- a/vnext/server/middleware/urlexpand.js
+++ b/vnext/server/middleware/urlexpand.js
@@ -1,4 +1,4 @@
-import { expandShortenedLink } from '../../src/api';
+import { expandShortenedLink } from '../../src/api'
/**
* Expand URLs
@@ -6,8 +6,8 @@ import { expandShortenedLink } from '../../src/api';
* @param {import("next").NextApiResponse} res
*/
export default function urlExpand(req, res) {
- let url = (req.query.url || '').toString();
+ let url = (req.query.url || '').toString()
return expandShortenedLink(url).then(result => {
- res.json(result);
- });
+ res.json(result)
+ })
}
diff --git a/vnext/server/sape.js b/vnext/server/sape.js
index ad374f2b..cff9b48a 100644
--- a/vnext/server/sape.js
+++ b/vnext/server/sape.js
@@ -1,9 +1,9 @@
-import { parseStringPromise } from 'xml2js';
-import axios from 'axios';
-import { setupCache } from 'axios-cache-interceptor';
-import config from 'config';
+import { parseStringPromise } from 'xml2js'
+import axios from 'axios'
+import { setupCache } from 'axios-cache-interceptor'
+import config from 'config'
-const token = config.get('service.sape.token') || process.env.SAPE_TOKEN;
+const token = config.get('service.sape.token') || process.env.SAPE_TOKEN
/** @external Promise */
@@ -14,19 +14,19 @@ const token = config.get('service.sape.token') || process.env.SAPE_TOKEN;
*/
export const getLinks = async (uri, sapeCookie) => {
if (!token) {
- console.warn('Sape is not configured');
- return [];
+ console.warn('Sape is not configured')
+ return []
}
- const response = await sape.get(`http://dispencer-01.sape.ru/code.php?user=${token}&host=juick.com&charset=UTF-8&as_xml=true`);
- const data = await parseStringPromise(response.data);
- const showCode = token === sapeCookie;
- const requestURI = showCode ? '*' : uri;
+ const response = await sape.get(`http://dispencer-01.sape.ru/code.php?user=${token}&host=juick.com&charset=UTF-8&as_xml=true`)
+ const data = await parseStringPromise(response.data)
+ const showCode = token === sapeCookie
+ const requestURI = showCode ? '*' : uri
const page = data.sape.page.filter(page => {
- const uri = page['$']['uri'];
- return uri === requestURI;
- });
- return page.length > 0 ? showCode ? [page[0]._] : page[0].link : [];
-};
+ const uri = page['$']['uri']
+ return uri === requestURI
+ })
+ return page.length > 0 ? showCode ? [page[0]._] : page[0].link : []
+}
/** @type { import('axios-cache-interceptor').AxiosCacheInstance } */
let sape = setupCache(
@@ -36,4 +36,4 @@ let sape = setupCache(
}
}),
{ ttl: 3600 * 1000 }
-);
+)
diff --git a/vnext/server/sender.js b/vnext/server/sender.js
index 7c72cbf3..48b5fb78 100644
--- a/vnext/server/sender.js
+++ b/vnext/server/sender.js
@@ -1,11 +1,11 @@
-import PushNotifications from 'node-pushnotifications';
-import debug from 'debug';
-const log = debug('sender');
-import { deleteSubscribers } from './http';
-import { formatMessage, formatTitle, formatQuote } from './common/MessageUtils';
-import config from 'config';
+import PushNotifications from 'node-pushnotifications'
+import debug from 'debug'
+const log = debug('sender')
+import { deleteSubscribers } from './http'
+import { formatMessage, formatTitle, formatQuote } from './common/MessageUtils'
+import config from 'config'
-let cfg = /** @type { import('node-pushnotifications').Settings } */ (config);
+let cfg = /** @type { import('node-pushnotifications').Settings } */ (config)
const apnConfig = (production = true) => {
const apn = {
@@ -16,98 +16,96 @@ const apnConfig = (production = true) => {
teamId: cfg.apn?.token?.teamId || process.env.JUICK_APN_TEAM_ID
},
production: production
- };
- return apn;
-};
+ }
+ return apn
+}
const gcmConfig = {
...cfg.gcm,
id: cfg.gcm?.id || process.env.JUICK_GCM_ID
-};
+}
const push = new PushNotifications({
...config,
apn: apnConfig(true),
gcm: gcmConfig,
-});
+})
const sandbox = new PushNotifications({
...config,
apn: apnConfig(false),
gcm: gcmConfig
-});
+})
/** @type {string} */
-const application = config.get('service.application') || process.env.JUICK_APN_APPLICATION || '';
+const application = config.get('service.application') || process.env.JUICK_APN_APPLICATION || ''
/**
* send notification
- *
* @param {PushNotifications.RegistrationId[]} productionIds
* @param {PushNotifications.RegistrationId[]} sandboxIds
* @param {PushNotifications.Data} data
*/
export function sendNotification(productionIds, sandboxIds, data) {
[productionIds, sandboxIds].map((registrationIds, index) => {
- let sender = index == 0 ? push : sandbox;
+ let sender = index == 0 ? push : sandbox
if (registrationIds && registrationIds.length) {
sender.send(registrationIds, data)
.then((results) => {
results.forEach(result => {
- log(`${result.method}: ${result.success} success, ${result.failure} failure`);
+ log(`${result.method}: ${result.success} success, ${result.failure} failure`)
if (result.failure) {
- console.error(`${result.method} failure: ${JSON.stringify(result)}`);
- console.error(`Failed data: ${JSON.stringify(data)}`);
+ console.error(`${result.method} failure: ${JSON.stringify(result)}`)
+ console.error(`Failed data: ${JSON.stringify(data)}`)
}
- });
+ })
results.filter(r => r.method === 'apn')
.forEach(async r => {
- log(`Response message: ${JSON.stringify(r.message)}`);
+ log(`Response message: ${JSON.stringify(r.message)}`)
let badTokens = r.message.filter(m => m.errorMsg === 'BadDeviceToken').map(m => {
- return { 'type': 'apns', 'token': m.regId };
- });
+ return { 'type': 'apns', 'token': m.regId }
+ })
if (badTokens.length > 0) {
- await deleteSubscribers(badTokens);
- log(`${badTokens.length} APNS tokens deleted`);
+ await deleteSubscribers(badTokens)
+ log(`${badTokens.length} APNS tokens deleted`)
}
- });
+ })
results.filter(r => r.method === 'gcm')
.forEach(async r => {
let badTokens = r.message.filter(m => m.errorMsg === 'NotRegistered' || m.errorMsg === 'MismatchSenderId' || m.errorMsg === 'InvalidRegistration').map(m => {
- return { 'type': 'fcm', 'token': m.regId };
- });
+ return { 'type': 'fcm', 'token': m.regId }
+ })
if (badTokens.length > 0) {
- await deleteSubscribers(badTokens);
- log(`${badTokens.length} GCM tokens deleted`);
+ await deleteSubscribers(badTokens)
+ log(`${badTokens.length} GCM tokens deleted`)
}
- });
+ })
results.filter(r => r.method === 'mpns')
.forEach(async r => {
let badTokens = r.message.filter(m => m.errorMsg === 'The channel expired.').map(m => {
- return { 'type': 'mpns', 'token': m.regId };
- });
+ return { 'type': 'mpns', 'token': m.regId }
+ })
if (badTokens.length > 0) {
- await deleteSubscribers(badTokens);
- log(`${badTokens.length} MPNS tokens deleted`);
+ await deleteSubscribers(badTokens)
+ log(`${badTokens.length} MPNS tokens deleted`)
}
- });
+ })
results.filter(r => r.method === 'webPush')
.forEach(async r => {
let badTokens = r.message.filter(m => m.error && m.error['statusCode'] === 410).map(m => {
- return { 'type': 'web', 'token': JSON.stringify(m.regId) };
- });
+ return { 'type': 'web', 'token': JSON.stringify(m.regId) }
+ })
if (badTokens.length > 0) {
- await deleteSubscribers(badTokens);
- log(`${badTokens.length} WebPush tokens deleted`);
+ await deleteSubscribers(badTokens)
+ log(`${badTokens.length} WebPush tokens deleted`)
}
- });
+ })
})
- .catch((err) => { console.error(JSON.stringify(err)); });
+ .catch((err) => { console.error(JSON.stringify(err)) })
}
- });
+ })
}
/**
* builds notification object
- *
* @param {import('../client').SecureUser} user user
* @param {import('../client').Message} msg message
* @returns {PushNotifications.Data} notification template
@@ -119,33 +117,33 @@ export function buildNotification(user, msg) {
message: msg
},
timeToLive: 0
- };
- let { tokens, ...subscriber } = user;
+ }
+ let { tokens, ...subscriber } = user
if (msg.service) {
- template.contentAvailable = true;
- template.custom.service = true;
- template.custom.user = subscriber;
+ template.contentAvailable = true
+ template.custom.service = true
+ template.custom.user = subscriber
} else {
- const avatar = `https://i.juick.com/a/${msg.user.uid}.png`;
- const title = formatTitle(msg);
- const body = `${formatQuote(msg)}\n${formatMessage(msg)}`;
- template.custom.mid = msg.mid;
- template.custom.rid = msg.rid;
- template.custom.uname = msg.user.uname;
- template.custom.avatarUrl = avatar;
- template.image1src = avatar;
- template.text1 = title;
- template.text2 = body;
- template.title = title;
- template.body = body;
- template.badge = user.unreadCount || 0;
- template.mutableContent = 1;
- template.color = '#3c77aa';
- template.icon = 'ic_notification';
- template.clickAction = 'com.juick.NEW_EVENT_ACTION';
- const tag = msg.mid == 0 ? msg.user.uname : msg.mid;
- template.tag = `${tag}`;
- template.android_channel_id = 'default';
+ const avatar = `https://i.juick.com/a/${msg.user.uid}.png`
+ const title = formatTitle(msg)
+ const body = `${formatQuote(msg)}\n${formatMessage(msg)}`
+ template.custom.mid = msg.mid
+ template.custom.rid = msg.rid
+ template.custom.uname = msg.user.uname
+ template.custom.avatarUrl = avatar
+ template.image1src = avatar
+ template.text1 = title
+ template.text2 = body
+ template.title = title
+ template.body = body
+ template.badge = user.unreadCount || 0
+ template.mutableContent = 1
+ template.color = '#3c77aa'
+ template.icon = 'ic_notification'
+ template.clickAction = 'com.juick.NEW_EVENT_ACTION'
+ const tag = msg.mid == 0 ? msg.user.uname : msg.mid
+ template.tag = `${tag}`
+ template.android_channel_id = 'default'
}
- return template;
+ return template
}
diff --git a/vnext/server/webpack.config.js b/vnext/server/webpack.config.js
index 61f00d4d..77a86abd 100644
--- a/vnext/server/webpack.config.js
+++ b/vnext/server/webpack.config.js
@@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-var-requires */
-const ESLintPlugin = require('eslint-webpack-plugin');
-const TerserPlugin = require('terser-webpack-plugin');
+const ESLintPlugin = require('eslint-webpack-plugin')
+const TerserPlugin = require('terser-webpack-plugin')
module.exports = () => {
- const node_env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development';
- const dev = node_env !== 'production';
+ const node_env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development'
+ const dev = node_env !== 'production'
const config = {
mode: node_env,
devtool: dev ? 'cheap-module-source-map' : false,
@@ -37,7 +37,7 @@ module.exports = () => {
symlinks: false,
extensions: ['.js']
}
- };
+ }
if (dev) {
config.plugins.push(
new ESLintPlugin({
@@ -46,7 +46,7 @@ module.exports = () => {
failOnWarning: false,
failOnError: true,
fix: false
- }));
+ }))
config.devServer = {
hot: true,
historyApiFallback: true,
@@ -55,7 +55,7 @@ module.exports = () => {
runtimeErrors: true
}
}
- };
+ }
}
config.optimization = {
minimize: !dev,
@@ -67,6 +67,6 @@ module.exports = () => {
terserOptions: {},
}),
]
- };
- return config;
-};
+ }
+ return config
+}
diff --git a/vnext/src/App.js b/vnext/src/App.js
index 125b6fda..e723fe9c 100644
--- a/vnext/src/App.js
+++ b/vnext/src/App.js
@@ -1,25 +1,25 @@
-import { useState, useEffect, useRef, Fragment, useCallback } from 'react';
-import { Route, Link, Routes, useSearchParams } from 'react-router-dom';
+import { useState, useEffect, useRef, Fragment, useCallback } from 'react'
+import { Route, Link, Routes, useSearchParams } from 'react-router-dom'
-import svg4everybody from 'svg4everybody';
+import svg4everybody from 'svg4everybody'
-import Icon from './ui/Icon';
-import { Discover, Discussions, Blog, Tag, Home } from './ui/Feeds';
-import { Friends, Readers } from './ui/Users';
-import Settings from './ui/Settings';
-import Contacts from './ui/Contacts';
-import Chat from './ui/Chat';
-import Header from './ui/Header';
-import Post from './ui/Post';
-import Thread from './ui/Thread';
-import Login from './ui/Login';
+import Icon from './ui/Icon'
+import { Discover, Discussions, Blog, Tag, Home } from './ui/Feeds'
+import { Friends, Readers } from './ui/Users'
+import Settings from './ui/Settings'
+import Contacts from './ui/Contacts'
+import Chat from './ui/Chat'
+import Header from './ui/Header'
+import Post from './ui/Post'
+import Thread from './ui/Thread'
+import Login from './ui/Login'
-import { useCookies } from 'react-cookie';
+import { useCookies } from 'react-cookie'
-import { me, trends } from './api';
-import { useVisitor } from './ui/VisitorContext';
-import Avatar from './ui/Avatar';
-import { Toaster } from 'react-hot-toast';
+import { me, trends } from './api'
+import { useVisitor } from './ui/VisitorContext'
+import Avatar from './ui/Avatar'
+import { Toaster } from 'react-hot-toast'
/**
*
@@ -29,34 +29,34 @@ import { Toaster } from 'react-hot-toast';
*/
export default function App({ footer }) {
- let contentRef = useRef(null);
- const [cookie, setCookie] = useCookies(['hash']);
+ let contentRef = useRef(null)
+ const [cookie, setCookie] = useCookies(['hash'])
- const [allTrends, setAllTrends] = useState([]);
+ const [allTrends, setAllTrends] = useState([])
- const [visitor, setVisitor] = useVisitor();
+ const [visitor, setVisitor] = useVisitor()
- const params = useSearchParams();
+ const params = useSearchParams()
useEffect(() => {
- svg4everybody();
+ svg4everybody()
if (params['hash']) {
- setCookie('hash', params['hash'], { path: '/' });
- let retpath = params['retpath'] || `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
- window.history.replaceState({}, document.title, retpath);
+ setCookie('hash', params['hash'], { path: '/' })
+ let retpath = params['retpath'] || `${window.location.protocol}//${window.location.host}${window.location.pathname}`
+ window.history.replaceState({}, document.title, retpath)
}
- }, [setCookie, footer, params]);
+ }, [setCookie, footer, params])
let updateStatus = useCallback(() => {
// refresh server visitor state (unread counters)
me().then(visitor => {
- setVisitor(visitor);
- }).catch(console.error);
- }, [setVisitor]);
+ setVisitor(visitor)
+ }).catch(console.error)
+ }, [setVisitor])
- const [hash, setHash] = useState(cookie.hash);
+ const [hash, setHash] = useState(cookie.hash)
- const [eventSource, setEventSource] = /** @param EventSource? */ useState({});
+ const [eventSource, setEventSource] = /** @param EventSource? */ useState({})
/**
* @param {import("./api").SecureUser} visitor
@@ -64,53 +64,53 @@ export default function App({ footer }) {
let auth = useCallback((visitor) => {
setVisitor(prevState => {
if (visitor.hash != prevState.hash) {
- setHash(visitor.hash);
+ setHash(visitor.hash)
}
- return visitor;
- });
- }, [setVisitor]);
+ return visitor
+ })
+ }, [setVisitor])
useEffect(() => {
- let es;
+ let es
const anonymousUser = {
uid: 0
- };
+ }
if (hash) {
me().then(visitor => auth(visitor))
- .catch(() => setVisitor(anonymousUser));
+ .catch(() => setVisitor(anonymousUser))
if ('EventSource' in window) {
- const eventParams = new URLSearchParams({ hash: hash });
- let url = new URL(`https://juick.com/api/events?${eventParams.toString()}`);
- console.log(url.toString());
- es = new EventSource(url.toString());
+ const eventParams = new URLSearchParams({ hash: hash })
+ let url = new URL(`https://juick.com/api/events?${eventParams.toString()}`)
+ console.log(url.toString())
+ es = new EventSource(url.toString())
es.onopen = () => {
- console.log('online');
- };
+ console.log('online')
+ }
es.onerror = () => {
- es.removeEventListener('read', updateStatus);
- es.removeEventListener('msg', updateStatus);
- };
- es.addEventListener('read', updateStatus);
- es.addEventListener('msg', updateStatus);
- setEventSource(es);
+ es.removeEventListener('read', updateStatus)
+ es.removeEventListener('msg', updateStatus)
+ }
+ es.addEventListener('read', updateStatus)
+ es.addEventListener('msg', updateStatus)
+ setEventSource(es)
}
} else {
- setVisitor(anonymousUser);
+ setVisitor(anonymousUser)
}
return (() => {
if (es && es.removeEventListener) {
- es.removeEventListener('read', updateStatus);
- es.removeEventListener('msg', updateStatus);
+ es.removeEventListener('read', updateStatus)
+ es.removeEventListener('msg', updateStatus)
}
- });
- }, [auth, hash, setVisitor, updateStatus]);
+ })
+ }, [auth, hash, setVisitor, updateStatus])
useEffect(() => {
const getTrends = async () => {
- setAllTrends(await trends());
- };
- getTrends().catch(console.error);
- }, []);
+ setAllTrends(await trends())
+ }
+ getTrends().catch(console.error)
+ }, [])
return (
<>
@@ -200,5 +200,5 @@ export default function App({ footer }) {
</Routes>
</section>
</>
- );
+ )
}
diff --git a/vnext/src/api/index.js b/vnext/src/api/index.js
index ef753eaf..566478a9 100644
--- a/vnext/src/api/index.js
+++ b/vnext/src/api/index.js
@@ -1,7 +1,7 @@
-import axios from 'axios';
-import Cookies from 'universal-cookie';
+import axios from 'axios'
+import Cookies from 'universal-cookie'
-const apiBaseUrl = 'https://juick.com';
+const apiBaseUrl = 'https://juick.com'
/**
* @typedef {object} Token
@@ -73,17 +73,17 @@ const apiBaseUrl = 'https://juick.com';
const client = axios.create({
baseURL: apiBaseUrl
-});
+})
client.interceptors.request.use(config => {
if (config.url.startsWith('/')) {
// only local URLs
- let cookies = new Cookies();
+ let cookies = new Cookies()
config.params = Object.assign(config.params || {}, {
hash: cookies.get('hash')
- });
+ })
}
- return config;
-});
+ return config
+})
/**
* fetch my info
@@ -92,28 +92,28 @@ client.interceptors.request.use(config => {
* @returns {Promise<SecureUser, Error>} me object
*/
export function me(username = '', password = '') {
- let cookies = new Cookies();
+ let cookies = new Cookies()
return new Promise((resolve, reject) => {
client.get('/api/me', {
headers: username ? {
'Authorization': 'Basic ' + window.btoa(unescape(encodeURIComponent(username + ':' + password)))
} : {}
}).then(response => {
- let visitor = response.data;
- cookies.set('hash', visitor.hash, { path: '/' });
- resolve(visitor);
+ let visitor = response.data
+ cookies.set('hash', visitor.hash, { path: '/' })
+ resolve(visitor)
}).catch(reason => {
- cookies.remove('hash', { path: '/' });
- reject(reason);
- });
- });
+ cookies.remove('hash', { path: '/' })
+ reject(reason)
+ })
+ })
}
/**
* @param {string} username
*/
export function info(username) {
- return client.get(`/api/info/${username}`);
+ return client.get(`/api/info/${username}`)
}
@@ -121,7 +121,7 @@ export function info(username) {
*
*/
export function getChats() {
- return client.get('/api/groups_pms');
+ return client.get('/api/groups_pms')
}
/**
@@ -132,7 +132,7 @@ export function getChat(userName) {
params: {
'uname': userName
}
- });
+ })
}
/**
@@ -140,10 +140,10 @@ export function getChat(userName) {
* @param {string} body
*/
export function pm(userName, body) {
- let form = new FormData();
- form.set('uname', userName);
- form.set('body', body);
- return client.post('/api/pm', form);
+ let form = new FormData()
+ form.set('uname', userName)
+ form.set('body', body)
+ return client.post('/api/pm', form)
}
/**
@@ -153,7 +153,7 @@ export function pm(userName, body) {
export function getMessages(path, params) {
return client.get(path, {
params: params
- });
+ })
}
/**
@@ -161,10 +161,10 @@ export function getMessages(path, params) {
* @param {string} attach
*/
export function post(body, attach) {
- let form = new FormData();
- form.append('attach', attach);
- form.append('body', body);
- return client.post('/api/post', form);
+ let form = new FormData()
+ form.append('attach', attach)
+ form.append('body', body)
+ return client.post('/api/post', form)
}
/**
@@ -174,12 +174,12 @@ export function post(body, attach) {
* @param {string} attach
*/
export function comment(mid, rid, body, attach) {
- let form = new FormData();
- form.append('mid', mid.toString());
- form.append('rid', rid.toString());
- form.append('body', body);
- form.append('attach', attach);
- return client.post('/api/comment', form);
+ let form = new FormData()
+ form.append('mid', mid.toString())
+ form.append('rid', rid.toString())
+ form.append('body', body)
+ form.append('attach', attach)
+ return client.post('/api/comment', form)
}
/**
* Edit message
@@ -188,48 +188,48 @@ export function comment(mid, rid, body, attach) {
* @param {string?} body
*/
export function update(mid, rid, body) {
- let form = new FormData();
- form.append('mid', mid);
- form.append('rid', rid);
- form.append('body', body);
- return client.post('/api/update', form);
+ let form = new FormData()
+ form.append('mid', mid)
+ form.append('rid', rid)
+ 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);
- return client.post('/api/me/upload', form);
+ let form = new FormData()
+ form.append('avatar', newAvatar)
+ return client.post('/api/me/upload', form)
}
/**
* @param {string} network
*/
function socialLink(network) {
- return `${apiBaseUrl}/api/_${network}login?state=${window.location.protocol}//${window.location.host}${window.location.pathname}`;
+ return `${apiBaseUrl}/api/_${network}login?state=${window.location.protocol}//${window.location.host}${window.location.pathname}`
}
/**
*
*/
export function facebookLink() {
- return socialLink('fb');
+ return socialLink('fb')
}
/**
*
*/
export function vkLink() {
- return socialLink('vk');
+ return socialLink('vk')
}
/**
*
*/
export function appleLink() {
- return socialLink('apple');
+ return socialLink('apple')
}
/**
@@ -237,10 +237,10 @@ export function appleLink() {
* @param {SecureUser} visitor
*/
export function markReadTracker(msg, visitor) {
- return `${apiBaseUrl}/api/thread/mark_read/${msg.mid}-${msg.rid || 0}.gif?hash=${visitor.hash}`;
+ return `${apiBaseUrl}/api/thread/mark_read/${msg.mid}-${msg.rid || 0}.gif?hash=${visitor.hash}`
}
-let profileCache = {};
+let profileCache = {}
/**
* Fetch user profile
@@ -249,18 +249,18 @@ let profileCache = {};
export function fetchUserUri(profileUrl) {
return new Promise((resolve, reject) => {
if (profileCache[profileUrl]) {
- resolve(profileCache[profileUrl]);
+ resolve(profileCache[profileUrl])
} else {
client.get(profileUrl, {
headers: {
'Accept': 'application/ld+json'
}
}).then(response => {
- profileCache[profileUrl] = response.data;
- resolve(response.data);
- }).catch(reject);
+ profileCache[profileUrl] = response.data
+ resolve(response.data)
+ }).catch(reject)
}
- });
+ })
}
/**
@@ -269,13 +269,13 @@ export function fetchUserUri(profileUrl) {
*/
export const trends = async () => {
try {
- const response = await client.get('/api/tags');
- return response.data;
+ const response = await client.get('/api/tags')
+ return response.data
} catch (e) {
- console.error(e);
- return [];
+ console.error(e)
+ return []
}
-};
+}
/**
* Fetch Tweet content
@@ -289,9 +289,9 @@ export function fetchUserUri(profileUrl) {
'omit_script': true,
'url': url
}
- });
- return response.data;
-};
+ })
+ return response.data
+}
/**
* Checks if HTTP error code is redirection code
@@ -299,7 +299,7 @@ export function fetchUserUri(profileUrl) {
* @returns {boolean} is HTTP request redirected or not
*/
function isHttpRedirected(code = 200) {
- return [301, 302].includes(code);
+ return [301, 302].includes(code)
}
/**
@@ -308,7 +308,7 @@ function isHttpRedirected(code = 200) {
* @returns {boolean} is HTTP request successful or not
*/
function isHttpSuccessful(code = 200) {
- return code >= 200 && code < 300;
+ return code >= 200 && code < 300
}
/**
@@ -323,25 +323,25 @@ function expandShortenedLink(url = '') {
}).then(response => {
if (isHttpSuccessful(response.status)) {
// URL is not redirected
- resolve(url);
- return;
+ resolve(url)
+ return
}
if (isHttpRedirected(response.status)) {
- resolve(/** @type { string } */ (response.headers['Location']));
- return;
+ resolve(/** @type { string } */ (response.headers['Location']))
+ return
}
// Error case
- reject('Invalid response');
+ reject('Invalid response')
}).catch(error => {
- reject(error);
- });
- });
+ reject(error)
+ })
+ })
}
export {
embeddedTweet,
expandShortenedLink
-};
+}
diff --git a/vnext/src/index.js b/vnext/src/index.js
index 11a78a48..5ab543c4 100644
--- a/vnext/src/index.js
+++ b/vnext/src/index.js
@@ -1,32 +1,32 @@
-import 'core-js/modules/es.array.map';
-import 'core-js/modules/es.map';
-import 'core-js/modules/es.object.create';
-import 'core-js/modules/es.object.define-property';
-import 'core-js/modules/es.object.set-prototype-of';
-import 'core-js/modules/es.promise';
-import 'core-js/modules/es.set';
-import 'core-js/modules/es.symbol';
-import 'core-js/modules/web.dom-collections.iterator';
-import 'url-polyfill';
-import { StrictMode, lazy } from 'react';
-import { createRoot, hydrateRoot } from 'react-dom/client';
-import { BrowserRouter } from 'react-router-dom';
-import { CookiesProvider } from 'react-cookie';
+import 'core-js/modules/es.array.map'
+import 'core-js/modules/es.map'
+import 'core-js/modules/es.object.create'
+import 'core-js/modules/es.object.define-property'
+import 'core-js/modules/es.object.set-prototype-of'
+import 'core-js/modules/es.promise'
+import 'core-js/modules/es.set'
+import 'core-js/modules/es.symbol'
+import 'core-js/modules/web.dom-collections.iterator'
+import 'url-polyfill'
+import { StrictMode, lazy } from 'react'
+import { createRoot, hydrateRoot } from 'react-dom/client'
+import { BrowserRouter } from 'react-router-dom'
+import { CookiesProvider } from 'react-cookie'
-import { VisitorProvider } from './ui/VisitorContext';
+import { VisitorProvider } from './ui/VisitorContext'
-const Juick = lazy(() => import('./App'));
+const Juick = lazy(() => import('./App'))
function fromBinary(encoded) {
- const binary = window.atob(encoded);
- const bytes = new Uint8Array(binary.length);
+ const binary = window.atob(encoded)
+ const bytes = new Uint8Array(binary.length)
for (let i = 0; i < bytes.length; i++) {
- bytes[i] = binary.charCodeAt(i);
+ bytes[i] = binary.charCodeAt(i)
}
- return String.fromCharCode(...new Uint16Array(bytes.buffer));
+ return String.fromCharCode(...new Uint16Array(bytes.buffer))
}
-const props = window.__PROPS__ ? JSON.parse(fromBinary(window.__PROPS__)) : {};
+const props = window.__PROPS__ ? JSON.parse(fromBinary(window.__PROPS__)) : {}
const JuickApp = () => (
<StrictMode>
@@ -38,11 +38,11 @@ const JuickApp = () => (
</CookiesProvider>
</VisitorProvider>
</StrictMode>
-);
+)
-let root = document.getElementById('app');
+let root = document.getElementById('app')
if (window.__PROPS__) {
- hydrateRoot(root, <JuickApp />);
+ hydrateRoot(root, <JuickApp />)
} else {
- createRoot(root).render(<JuickApp />);
+ createRoot(root).render(<JuickApp />)
}
diff --git a/vnext/src/ui/Avatar.js b/vnext/src/ui/Avatar.js
index 9d93521f..1a8db0c3 100644
--- a/vnext/src/ui/Avatar.js
+++ b/vnext/src/ui/Avatar.js
@@ -1,7 +1,7 @@
-import { memo } from 'react';
-import { Link } from 'react-router-dom';
+import { memo } from 'react'
+import { Link } from 'react-router-dom'
-import Icon from './Icon';
+import Icon from './Icon'
/**
* @typedef {object} AvatarProps
@@ -38,7 +38,7 @@ function Avatar({ user, style, link, children }) {
{children}
</div>
</div>
- );
+ )
}
-export default memo(Avatar);
+export default memo(Avatar)
diff --git a/vnext/src/ui/Button.js b/vnext/src/ui/Button.js
index 2c315e46..dd425021 100644
--- a/vnext/src/ui/Button.js
+++ b/vnext/src/ui/Button.js
@@ -1,4 +1,4 @@
-import { memo } from 'react';
+import { memo } from 'react'
/**
* @param {import('react').ClassAttributes<HTMLButtonElement> & import('react').ButtonHTMLAttributes<HTMLButtonElement>} props
@@ -6,7 +6,7 @@ import { memo } from 'react';
function Button(props) {
return (
<button className="Button" {...props} />
- );
+ )
}
-export default memo(Button);
+export default memo(Button)
diff --git a/vnext/src/ui/Chat.js b/vnext/src/ui/Chat.js
index 4fdeaac7..55b89d09 100644
--- a/vnext/src/ui/Chat.js
+++ b/vnext/src/ui/Chat.js
@@ -1,17 +1,17 @@
-import { useEffect, useState, useCallback } from 'react';
-import { useParams } from 'react-router-dom';
-import dayjs from 'dayjs';
-import utc from 'dayjs/plugin/utc';
-dayjs.extend(utc);
+import { useEffect, useState, useCallback } from 'react'
+import { useParams } from 'react-router-dom'
+import dayjs from 'dayjs'
+import utc from 'dayjs/plugin/utc'
+dayjs.extend(utc)
-import PM from './PM';
-import MessageInput from './MessageInput';
-import UserInfo from './UserInfo';
+import PM from './PM'
+import MessageInput from './MessageInput'
+import UserInfo from './UserInfo'
-import { getChat, pm } from '../api';
+import { getChat, pm } from '../api'
-import { useVisitor } from './VisitorContext';
-import { Helmet } from 'react-helmet';
+import { useVisitor } from './VisitorContext'
+import { Helmet } from 'react-helmet'
/**
*
@@ -24,48 +24,48 @@ import { Helmet } from 'react-helmet';
* @param {ChatProps} props
*/
export default function Chat(props) {
- const [visitor] = useVisitor();
- const [messages, setMessages] = useState([]);
- const params = useParams();
+ const [visitor] = useVisitor()
+ const [messages, setMessages] = useState([])
+ const params = useParams()
let loadChat = useCallback((uname) => {
- const { hash } = visitor;
+ const { hash } = visitor
if (hash && uname) {
getChat(uname)
.then(response => {
- setMessages(response.data);
- }).catch(console.log);
+ setMessages(response.data)
+ }).catch(console.log)
}
- }, [visitor]);
+ }, [visitor])
let onMessage = useCallback((json) => {
- const msg = JSON.parse(json.data);
+ const msg = JSON.parse(json.data)
if (msg.user.uname === params.user) {
setMessages((oldChat) => {
- return [msg, ...oldChat];
- });
+ return [msg, ...oldChat]
+ })
}
- }, [params.user]);
+ }, [params.user])
let onSend = async ({ body }) => {
- let result = false;
- let res = await pm(params.user, body).catch(console.error);
- result = res.status == 200;
- return result;
- };
+ let result = false
+ let res = await pm(params.user, body).catch(console.error)
+ result = res.status == 200
+ return result
+ }
useEffect(() => {
if (props.connection.addEventListener) {
- props.connection.addEventListener('msg', onMessage);
+ props.connection.addEventListener('msg', onMessage)
}
- loadChat(params.user);
- console.log(props.connection);
+ loadChat(params.user)
+ console.log(props.connection)
return () => {
if (props.connection.removeEventListener) {
- props.connection.removeEventListener('msg', onMessage);
+ props.connection.removeEventListener('msg', onMessage)
}
- };
- }, [props.connection, onMessage, loadChat, params.user]);
- const uname = params.user;
+ }
+ }, [props.connection, onMessage, loadChat, params.user])
+ const uname = params.user
return (
<div className="msg-cont">
<Helmet>
@@ -90,5 +90,5 @@ export default function Chat(props) {
)
}
</div>
- );
+ )
}
diff --git a/vnext/src/ui/Comment.js b/vnext/src/ui/Comment.js
index e8fb2afb..6ba34ff0 100644
--- a/vnext/src/ui/Comment.js
+++ b/vnext/src/ui/Comment.js
@@ -1,14 +1,14 @@
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react'
-import MessageInput from './MessageInput';
-import Avatar from './Avatar';
-import { UserLink } from './UserInfo';
-import Button from './Button';
+import MessageInput from './MessageInput'
+import Avatar from './Avatar'
+import { UserLink } from './UserInfo'
+import Button from './Button'
-import { format, embedUrls } from '../utils/embed';
+import { format, embedUrls } from '../utils/embed'
-import { chatItemStyle } from './helpers/BubbleStyle';
-import { useVisitor } from './VisitorContext';
+import { chatItemStyle } from './helpers/BubbleStyle'
+import { useVisitor } from './VisitorContext'
/**
* @param {{
@@ -23,20 +23,20 @@ import { useVisitor } from './VisitorContext';
* @returns import('react').ReactElement
*/
export default function Comment({ msg, draft, active, setActive, onStartEditing, postComment }) {
- const [visitor] = useVisitor();
+ const [visitor] = useVisitor()
/** @type {import('react').MutableRefObject<HTMLDivElement?>} */
- const embedRef = useRef(null);
+ const embedRef = useRef(null)
/** @type {import('react').MutableRefObject<HTMLDivElement?>} */
- const msgRef = useRef(null);
- const [author] = useState(msg.user);
+ const msgRef = useRef(null)
+ const [author] = useState(msg.user)
useEffect(() => {
if (msgRef.current && embedRef.current) {
- embedUrls(msgRef.current.querySelectorAll('a'), embedRef.current);
+ embedUrls(msgRef.current.querySelectorAll('a'), embedRef.current)
if (!embedRef.current.hasChildNodes()) {
- embedRef.current.style.display = 'none';
+ embedRef.current.style.display = 'none'
}
}
- }, []);
+ }, [])
return (
<div style={chatItemStyle(visitor, msg)}>
<div className="msg-header">
@@ -94,11 +94,11 @@ export default function Comment({ msg, draft, active, setActive, onStartEditing,
</div>
}
</div>
- );
+ )
}
/**
* @type {import('react').CSSProperties}
*/
const linkStyle = {
cursor: 'pointer'
-};
+}
diff --git a/vnext/src/ui/Contact.js b/vnext/src/ui/Contact.js
index 75c80332..d7bb12c8 100644
--- a/vnext/src/ui/Contact.js
+++ b/vnext/src/ui/Contact.js
@@ -1,6 +1,6 @@
-import { memo } from 'react';
+import { memo } from 'react'
-import Avatar from './Avatar';
+import Avatar from './Avatar'
/**
* @typedef {object} ContactProps
@@ -18,7 +18,7 @@ function Contact({ user, style }) {
{user.unreadCount && <span className="badge">{user.unreadCount}</span>}
<div className="msg-ts">{user.lastMessageText}</div>
</Avatar>
- );
+ )
}
-export default memo(Contact);
+export default memo(Contact)
diff --git a/vnext/src/ui/Contacts.js b/vnext/src/ui/Contacts.js
index b1f87723..1c23f042 100644
--- a/vnext/src/ui/Contacts.js
+++ b/vnext/src/ui/Contacts.js
@@ -1,23 +1,23 @@
-import { useEffect, useState } from 'react';
-import { Helmet } from 'react-helmet';
+import { useEffect, useState } from 'react'
+import { Helmet } from 'react-helmet'
-import { getChats } from '../api';
+import { getChats } from '../api'
-import Contact from './Contact.js';
-import { ChatSpinner } from './Spinner';
+import Contact from './Contact.js'
+import { ChatSpinner } from './Spinner'
/**
*
*/
export default function Contacts() {
- const [pms, setPms] = useState([]);
+ const [pms, setPms] = useState([])
useEffect(() => {
getChats()
.then(response => {
- setPms(response.data.pms);
- }).catch(console.log);
- }, []);
+ setPms(response.data.pms)
+ }).catch(console.log)
+ }, [])
return (
<div className="msg-cont">
<Helmet>
@@ -31,7 +31,7 @@ export default function Contacts() {
}
</div>
</div>
- );
+ )
}
const chatListStyle = {
@@ -39,7 +39,7 @@ const chatListStyle = {
flexDirection: 'column',
width: '100%',
padding: '12px'
-};
+}
const chatTitleStyle = {
width: '100%',
@@ -48,4 +48,4 @@ const chatTitleStyle = {
background: 'var(--main-background-color)',
color: 'var(--text-color)',
borderBottom: '1px solid var(--border-color)'
-};
+}
diff --git a/vnext/src/ui/Feeds.js b/vnext/src/ui/Feeds.js
index 48da52e3..086a910e 100644
--- a/vnext/src/ui/Feeds.js
+++ b/vnext/src/ui/Feeds.js
@@ -1,18 +1,18 @@
-import { useState, useEffect } from 'react';
-import { Link, useLocation, useParams, Navigate, useSearchParams } from 'react-router-dom';
+import { useState, useEffect } from 'react'
+import { Link, useLocation, useParams, Navigate, useSearchParams } from 'react-router-dom'
-import dayjs from 'dayjs';
-import utc from 'dayjs/plugin/utc';
-dayjs.extend(utc);
+import dayjs from 'dayjs'
+import utc from 'dayjs/plugin/utc'
+dayjs.extend(utc)
-import Message from './Message';
-import Spinner from './Spinner';
+import Message from './Message'
+import Spinner from './Spinner'
-import UserInfo from './UserInfo';
+import UserInfo from './UserInfo'
-import { getMessages } from '../api';
-import { useVisitor } from './VisitorContext';
-import { Helmet } from 'react-helmet';
+import { getMessages } from '../api'
+import { useVisitor } from './VisitorContext'
+import { Helmet } from 'react-helmet'
/**
* @typedef {object} Query
@@ -28,29 +28,29 @@ import { Helmet } from 'react-helmet';
*/
function RequireAuth({ children }) {
- let location = useLocation();
- let [visitor] = useVisitor();
+ let location = useLocation()
+ let [visitor] = useVisitor()
if (!visitor.hash) {
// Redirect them to the /login page, but save the current location they were
// trying to go to when they were redirected. This allows us to send them
// along to that page after they login, which is a nicer user experience
// than dropping them off on the home page.
- return <Navigate to="/login" state={{ from: location }} />;
+ return <Navigate to="/login" state={{ from: location }} />
}
- return children;
+ return children
}
/**
*
*/
export function Discover() {
- const [search] = useSearchParams();
+ const [search] = useSearchParams()
const query = {
baseUrl: '/api/messages',
search: search,
pageParam: search.search ? 'page' : 'before_mid'
- };
+ }
return (
<>
<Helmet>
@@ -58,7 +58,7 @@ export function Discover() {
</Helmet>
<Feed query={query} />
</>
- );
+ )
}
/**
@@ -68,7 +68,7 @@ export function Discussions() {
const query = {
baseUrl: '/api/messages/discussions',
pageParam: 'to'
- };
+ }
return (
<>
<Helmet>
@@ -76,26 +76,26 @@ export function Discussions() {
</Helmet>
<Feed query={query} />
</>
- );
+ )
}
/**
*
*/
export function Blog() {
- const { user } = useParams();
- const [params] = useSearchParams();
+ const { user } = useParams()
+ const [params] = useSearchParams()
const search = {
...params,
uname: user
- };
+ }
const query = {
baseUrl: '/api/messages',
search: search,
pageParam: search.search ? 'page' : 'before_mid'
- };
- const blogTitle = `${user} blog`;
- const pageTitle = search.tag ? `${blogTitle}: #${search.tag}` : blogTitle;
+ }
+ const blogTitle = `${user} blog`
+ const pageTitle = search.tag ? `${blogTitle}: #${search.tag}` : blogTitle
return (
<>
<Helmet>
@@ -106,22 +106,22 @@ export function Blog() {
</div>
<Feed query={query} />
</>
- );
+ )
}
/**
*
*/
export function Tag() {
- const params = useParams();
- const { tag } = params;
+ const params = useParams()
+ const { tag } = params
const query = {
baseUrl: '/api/messages',
search: {
tag: tag
},
pageParam: 'before_mid'
- };
+ }
return (
<>
<Helmet>
@@ -129,7 +129,7 @@ export function Tag() {
</Helmet>
<Feed query={query} />
</>
- );
+ )
}
/**
@@ -139,12 +139,12 @@ export function Home() {
const query = {
baseUrl: '/api/home',
pageParam: 'before_mid'
- };
+ }
return (
<RequireAuth>
<Feed query={query} />
</RequireAuth>
- );
+ )
}
/**
@@ -157,52 +157,52 @@ export function Home() {
* @param {FeedState} props
*/
function Feed({ query }) {
- const location = useLocation();
- const [visitor] = useVisitor();
+ const location = useLocation()
+ const [visitor] = useVisitor()
const [state, setState] = useState({
hash: visitor.hash,
msgs: [],
nextpage: null,
error: false,
tag: ''
- });
- const [loading, setLoading] = useState(true);
- const [filter] = useSearchParams();
+ })
+ const [loading, setLoading] = useState(true)
+ const [filter] = useSearchParams()
useEffect(() => {
- setLoading(true);
+ setLoading(true)
let getPageParam = (pageParam, lastMessage, /** @type { URLSearchParams } */ filterParams) => {
- const pageValue = pageParam === 'before_mid' ? lastMessage.mid : pageParam === 'page' ? (Number(filterParams.page) || 0) + 1 : dayjs.utc(lastMessage.updated).valueOf();
- filterParams.append(pageParam, pageValue);
- return `?${filterParams.toString()}`;
- };
- let params = { ...Object.fromEntries(filter), ...query.search };
- let url = query.baseUrl;
+ const pageValue = pageParam === 'before_mid' ? lastMessage.mid : pageParam === 'page' ? (Number(filterParams.page) || 0) + 1 : dayjs.utc(lastMessage.updated).valueOf()
+ filterParams.append(pageParam, pageValue)
+ return `?${filterParams.toString()}`
+ }
+ let params = { ...Object.fromEntries(filter), ...query.search }
+ let url = query.baseUrl
getMessages(url, params)
.then(response => {
- const { data } = response;
- const { pageParam } = query;
- const lastMessage = data.slice(-1)[0] || {};
- const nextpage = getPageParam(pageParam, lastMessage, new URLSearchParams(params));
- document.body.scrollTop = 0;
- document.documentElement.scrollTop = 0;
+ const { data } = response
+ const { pageParam } = query
+ const lastMessage = data.slice(-1)[0] || {}
+ const nextpage = getPageParam(pageParam, lastMessage, new URLSearchParams(params))
+ document.body.scrollTop = 0
+ document.documentElement.scrollTop = 0
setState((prevState) => {
return {
...prevState,
msgs: data,
nextpage: nextpage,
tag: filter['tag'] || ''
- };
- });
- setLoading(false);
+ }
+ })
+ setLoading(false)
}).catch(() => {
setState((prevState) => {
return {
...prevState,
error: true
- };
- });
- });
- }, [query, filter]);
+ }
+ })
+ })
+ }, [query, filter])
return (state.msgs.length > 0 ? (
<div className="msgs">
{
@@ -227,5 +227,5 @@ function Feed({ query }) {
}
</div>
) : state.error ? <div>error</div> : loading ? <div className="msgs"><Spinner /><Spinner /><Spinner /><Spinner /></div> : <div>No more messages</div>
- );
+ )
}
diff --git a/vnext/src/ui/Header.js b/vnext/src/ui/Header.js
index 6cf07844..cee24c67 100644
--- a/vnext/src/ui/Header.js
+++ b/vnext/src/ui/Header.js
@@ -1,19 +1,19 @@
-import { memo, useCallback } from 'react';
-import { Link, useNavigate } from 'react-router-dom';
+import { memo, useCallback } from 'react'
+import { Link, useNavigate } from 'react-router-dom'
-import SearchBox from './SearchBox';
+import SearchBox from './SearchBox'
function Header() {
- const navigate = useNavigate();
+ const navigate = useNavigate()
/**
* @param {string} searchString
*/
let searchAll = useCallback((searchString) => {
- let location = {};
- location.pathname = '/discover';
- location.search = `?search=${searchString}`;
- navigate(location);
- }, [navigate]);
+ let location = {}
+ location.pathname = '/discover'
+ location.search = `?search=${searchString}`
+ navigate(location)
+ }, [navigate])
return (
<div id="header">
<div id="header_wrapper">
@@ -25,7 +25,7 @@ function Header() {
</div>
</div>
</div>
- );
+ )
}
-export default memo(Header);
+export default memo(Header)
diff --git a/vnext/src/ui/Icon.js b/vnext/src/ui/Icon.js
index 6d10df16..bc5ce08a 100644
--- a/vnext/src/ui/Icon.js
+++ b/vnext/src/ui/Icon.js
@@ -1,7 +1,7 @@
-import { createElement, memo } from 'react';
-import PropTypes from 'prop-types';
+import { createElement, memo } from 'react'
+import PropTypes from 'prop-types'
-import evilIcons from 'evil-icons/assets/sprite.svg';
+import evilIcons from 'evil-icons/assets/sprite.svg'
/**
* @typedef {object} IconProps
@@ -16,18 +16,18 @@ import evilIcons from 'evil-icons/assets/sprite.svg';
* @param {IconProps} props - icon props
*/
function IconElement(props) {
- var size = props.size ? ' icon--' + props.size : '';
- var className = props.className ? ' ' + props.className : '';
- var klass = 'icon' + (!props.noFill ? ' icon--' + props.name : '') + size + className;
+ var size = props.size ? ' icon--' + props.size : ''
+ var className = props.className ? ' ' + props.className : ''
+ var klass = 'icon' + (!props.noFill ? ' icon--' + props.name : '') + size + className
- var name = '#' + props.name + '-icon';
- var useTag = `<use xlink:href='${evilIcons}${name}' />`;
- var Icon = createElement('svg', { className: 'icon__cnt', dangerouslySetInnerHTML: { __html: useTag } });
+ var name = '#' + props.name + '-icon'
+ var useTag = `<use xlink:href='${evilIcons}${name}' />`
+ var Icon = createElement('svg', { className: 'icon__cnt', dangerouslySetInnerHTML: { __html: useTag } })
return createElement(
'div',
{ className: klass },
wrapSpinner(Icon, klass)
- );
+ )
}
/**
@@ -40,17 +40,17 @@ function wrapSpinner(Html, klass) {
'div',
{ className: 'icon__spinner' },
Html
- );
+ )
} else {
- return Html;
+ return Html
}
}
-export default memo(IconElement);
+export default memo(IconElement)
IconElement.propTypes = {
size: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
className: PropTypes.string,
noFill: PropTypes.bool
-};
+}
diff --git a/vnext/src/ui/Login.js b/vnext/src/ui/Login.js
index 73da49c8..5d9908cb 100644
--- a/vnext/src/ui/Login.js
+++ b/vnext/src/ui/Login.js
@@ -1,13 +1,13 @@
-import { useEffect } from 'react';
-import { useLocation, useNavigate } from 'react-router-dom';
+import { useEffect } from 'react'
+import { useLocation, useNavigate } from 'react-router-dom'
-import Icon from './Icon';
-import Button from './Button';
-import { useForm } from 'react-hook-form';
+import Icon from './Icon'
+import Button from './Button'
+import { useForm } from 'react-hook-form'
-import { me, facebookLink, vkLink, appleLink } from '../api';
+import { me, facebookLink, vkLink, appleLink } from '../api'
-import { useVisitor } from './VisitorContext';
+import { useVisitor } from './VisitorContext'
/**
* @typedef {object} LoginProps
@@ -19,29 +19,29 @@ import { useVisitor } from './VisitorContext';
* @param {LoginProps} props
*/
function Login({ onAuth }) {
- const location = useLocation();
- const navigate = useNavigate();
- const [visitor] = useVisitor();
+ const location = useLocation()
+ const navigate = useNavigate()
+ const [visitor] = useVisitor()
useEffect(() => {
if (visitor.hash) {
- const {retpath } = location.state || '/';
- console.log(retpath);
- navigate(retpath);
+ const {retpath } = location.state || '/'
+ console.log(retpath)
+ navigate(retpath)
}
- }, [navigate, location.state, visitor]);
+ }, [navigate, location.state, visitor])
- const { register, handleSubmit } = useForm();
+ const { register, handleSubmit } = useForm()
/** @type { import('react-hook-form').SubmitHandler<import('react-hook-form').FieldValues> } */
let onSubmit = (values) => {
me(values.username, values.password)
.then(response => {
- onAuth(response);
+ onAuth(response)
}
).catch(ex => {
- console.log(ex);
- });
- };
+ console.log(ex)
+ })
+ }
return (
<div className="msg-cont">
<div className="dialoglogin">
@@ -64,25 +64,25 @@ function Login({ onAuth }) {
</form>
</div>
</div>
- );
+ )
}
-export default Login;
+export default Login
const socialButtonsStyle = {
display: 'flex',
justifyContent: 'space-evenly',
padding: '4px'
-};
+}
const facebookButtonStyle = {
color: '#fff',
padding: '2px 14px',
background: '#3b5998'
-};
+}
const vkButtonStyle = {
color: '#fff',
padding: '2px 14px',
background: '#4c75a3'
-};
+}
diff --git a/vnext/src/ui/Message.js b/vnext/src/ui/Message.js
index c5ad175d..e4135700 100644
--- a/vnext/src/ui/Message.js
+++ b/vnext/src/ui/Message.js
@@ -1,18 +1,18 @@
-import React, { Fragment, memo, useEffect, useRef } from 'react';
+import React, { Fragment, memo, useEffect, useRef } from 'react'
-import dayjs from 'dayjs';
-import utc from 'dayjs/plugin/utc';
-dayjs.extend(utc);
-import relativeTime from 'dayjs/plugin/relativeTime';
-dayjs.extend(relativeTime);
+import dayjs from 'dayjs'
+import utc from 'dayjs/plugin/utc'
+dayjs.extend(utc)
+import relativeTime from 'dayjs/plugin/relativeTime'
+dayjs.extend(relativeTime)
-import { Link } from 'react-router-dom';
-import Icon from './Icon';
-import Avatar from './Avatar';
-import { UserLink } from './UserInfo';
+import { Link } from 'react-router-dom'
+import Icon from './Icon'
+import Avatar from './Avatar'
+import { UserLink } from './UserInfo'
-import { format, embedUrls } from '../utils/embed';
-import { useVisitor } from './VisitorContext';
+import { format, embedUrls } from '../utils/embed'
+import { useVisitor } from './VisitorContext'
/**
* @callback ToggleSubscriptionCallback
@@ -31,30 +31,30 @@ import { useVisitor } from './VisitorContext';
* @param {React.PropsWithChildren<{}> & MessageProps} props props
*/
export default function Message({ data, isThread, onToggleSubscription, children }) {
- const [visitor] = useVisitor();
- const isCode = (data.tags || []).indexOf('code') >= 0;
- const likesSummary = data.likes ? `${data.likes}` : 'Recommend';
- const commentsSummary = data.replies ? `${data.replies}` : 'Comment';
+ const [visitor] = useVisitor()
+ const isCode = (data.tags || []).indexOf('code') >= 0
+ const likesSummary = data.likes ? `${data.likes}` : 'Recommend'
+ const commentsSummary = data.replies ? `${data.replies}` : 'Comment'
/**
* @type {React.MutableRefObject<HTMLDivElement?>}
*/
- const embedRef = useRef(null);
+ const embedRef = useRef(null)
/**
* @type {React.MutableRefObject<HTMLDivElement?>}
*/
- const msgRef = useRef(null);
+ const msgRef = useRef(null)
useEffect(() => {
- const msg = msgRef.current;
- const embed = embedRef.current;
+ const msg = msgRef.current
+ const embed = embedRef.current
if (msg && embed) {
- embedUrls(msg.querySelectorAll('a'), embed);
+ embedUrls(msg.querySelectorAll('a'), embed)
if (!embed.hasChildNodes()) {
- embed.style.display = 'none';
+ embed.style.display = 'none'
}
}
- }, []);
+ }, [])
const canComment = data.user && visitor.uid === data.user.uid || !data.ReadOnly && visitor.uid > 0
- || !data.ReadOnly && !isThread;
+ || !data.ReadOnly && !isThread
return (
<div className="msg-cont">
<Recommendations forMessage={data} />
@@ -120,7 +120,7 @@ export default function Message({ data, isThread, onToggleSubscription, children
{
data.user && canComment && ((
isThread ? (
- <a className="msg-button" onClick={() => { onToggleSubscription(data); }}>
+ <a className="msg-button" onClick={() => { onToggleSubscription(data) }}>
{
data.subscribed ? (<>
<Icon name="ei-check" size="s" />
@@ -143,14 +143,14 @@ export default function Message({ data, isThread, onToggleSubscription, children
}
{children}
</div >
- );
+ )
}
/**
* @param {{isCode: boolean, data: {__html: string}}} props props
*/
function MessageContainer({ isCode, data }) {
- return isCode ? (<pre dangerouslySetInnerHTML={data} />) : (<span dangerouslySetInnerHTML={data} />);
+ return isCode ? (<pre dangerouslySetInnerHTML={data} />) : (<span dangerouslySetInnerHTML={data} />)
}
/**
@@ -171,18 +171,18 @@ function Tags({ data, user }) {
))
}
</span>
- ) : null;
+ ) : null
}
-const TagsList = memo(Tags);
+const TagsList = memo(Tags)
/**
*
* @param {{forMessage: import('../client').Message}} props props
*/
function Recommends({ forMessage }) {
- const { recommendations } = forMessage;
- const likes = forMessage.likes || 0;
+ const { recommendations } = forMessage
+ const likes = forMessage.likes || 0
return recommendations && recommendations.length > 0 && (
<div className="msg-recomms">{'♡ by '}
{
@@ -197,7 +197,7 @@ function Recommends({ forMessage }) {
likes > recommendations.length && (<span>&nbsp;and {likes - recommendations.length} others</span>)
}
</div>
- ) || null;
+ ) || null
}
-const Recommendations = memo(Recommends);
+const Recommendations = memo(Recommends)
diff --git a/vnext/src/ui/MessageInput.js b/vnext/src/ui/MessageInput.js
index 3d24e728..6ebf4361 100644
--- a/vnext/src/ui/MessageInput.js
+++ b/vnext/src/ui/MessageInput.js
@@ -1,10 +1,10 @@
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useState, useEffect, useRef } from 'react'
-import Icon from './Icon';
-import Button from './Button';
+import Icon from './Icon'
+import Button from './Button'
-import UploadButton from './UploadButton';
-import toast from 'react-hot-toast';
+import UploadButton from './UploadButton'
+import toast from 'react-hot-toast'
/**
@@ -13,13 +13,13 @@ import toast from 'react-hot-toast';
*/
function moveCaretToEnd(el) {
if (typeof el.selectionStart == 'number') {
- el.selectionStart = el.selectionEnd = el.value.length;
+ el.selectionStart = el.selectionEnd = el.value.length
} else if (typeof el.createTextRange != 'undefined') {
// Internet Explorer
- el.focus();
- var range = el.createTextRange();
- range.collapse(false);
- range.select();
+ el.focus()
+ var range = el.createTextRange()
+ range.collapse(false)
+ range.select()
}
}
@@ -42,74 +42,74 @@ export default function MessageInput({ text, rows, placeholder, onSend }) {
/**
* @type {React.MutableRefObject<HTMLTextAreaElement?>}
*/
- let textareaRef = useRef(null);
+ let textareaRef = useRef(null)
/**
* @type {React.MutableRefObject<HTMLInputElement?>}
*/
- let fileinput = useRef(null);
+ let fileinput = useRef(null)
let updateFocus = () => {
- const isDesktop = window.matchMedia('(min-width: 62.5rem)');
+ const isDesktop = window.matchMedia('(min-width: 62.5rem)')
if (isDesktop.matches) {
- const textarea = textareaRef.current;
+ const textarea = textareaRef.current
if (textarea) {
- textarea.focus();
- moveCaretToEnd(textarea);
+ textarea.focus()
+ moveCaretToEnd(textarea)
}
}
- };
+ }
useEffect(() => {
textChanged({
target: {
value: text
}
- });
- updateFocus();
- }, [text]);
+ })
+ updateFocus()
+ }, [text])
- let [body, setBody] = useState(text);
+ let [body, setBody] = useState(text)
let handleCtrlEnter = (event) => {
if (event.ctrlKey && (event.charCode == 10 || event.charCode == 13)) {
- onSubmit({});
+ onSubmit({})
}
- };
+ }
const textChanged = (event) => {
- setBody(event.target.value);
- const el = textareaRef.current;
+ setBody(event.target.value)
+ const el = textareaRef.current
if (el) {
- const offset = el.offsetHeight - el.clientHeight;
- const height = el.scrollHeight + offset;
- el.style.height = `${height + offset}px`;
+ const offset = el.offsetHeight - el.clientHeight
+ const height = el.scrollHeight + offset
+ el.style.height = `${height + offset}px`
}
- };
- const [attach, setAttach] = useState('');
+ }
+ const [attach, setAttach] = useState('')
let uploadValueChanged = (attach) => {
- setAttach(attach);
- };
+ setAttach(attach)
+ }
let onSubmit = (event) => {
if (event.preventDefault) {
- event.preventDefault();
+ event.preventDefault()
}
- const input = fileinput.current;
+ const input = fileinput.current
if (input && input.files) {
onSend({
body: body,
attach: attach ? input.files[0] : ''
}).then((success) => {
if (success) {
- setAttach('');
- setBody('');
+ setAttach('')
+ setBody('')
if (textareaRef.current) {
- textareaRef.current.style.height = '';
+ textareaRef.current.style.height = ''
}
- updateFocus();
+ updateFocus()
} else {
- toast('Can not update this message');
+ toast('Can not update this message')
}
- }).catch(console.log);
+ }).catch(console.log)
}
- };
+ }
return (
<form className="msg-comment-target" style={{ padding: '12px', width: '100%' }} onSubmit={onSubmit}>
<div style={commentStyle}>
@@ -122,7 +122,7 @@ export default function MessageInput({ text, rows, placeholder, onSend }) {
</div>
</div>
</form>
- );
+ )
}
/**
@@ -133,7 +133,7 @@ const commentStyle = {
flexDirection: 'column',
width: '100%',
marginTop: '10px'
-};
+}
/**
* @type {React.CSSProperties}
@@ -143,7 +143,7 @@ const inputBarStyle = {
alignItems: 'center',
justifyContent: 'space-between',
padding: '3px'
-};
+}
/**
* @type {React.CSSProperties}
@@ -156,4 +156,4 @@ const textInputStyle = {
border: 0,
outline: 'none',
padding: '4px'
-};
+}
diff --git a/vnext/src/ui/PM.js b/vnext/src/ui/PM.js
index d5a7eff1..3aa877b1 100644
--- a/vnext/src/ui/PM.js
+++ b/vnext/src/ui/PM.js
@@ -1,13 +1,13 @@
-import { memo } from 'react';
+import { memo } from 'react'
-import Avatar from './Avatar';
-import { format } from '../utils/embed';
-import { chatItemStyle } from './helpers/BubbleStyle';
-import { useVisitor } from './VisitorContext';
+import Avatar from './Avatar'
+import { format } from '../utils/embed'
+import { chatItemStyle } from './helpers/BubbleStyle'
+import { useVisitor } from './VisitorContext'
function PM(props) {
- const { chat } = props;
- const [visitor] = useVisitor();
+ const { chat } = props
+ const [visitor] = useVisitor()
return (
<li>
<div style={chatItemStyle(visitor, chat)}>
@@ -17,10 +17,10 @@ function PM(props) {
</div>
</div>
</li>
- );
+ )
}
-export default memo(PM);
+export default memo(PM)
/*
PM.propTypes = {
chat: MessageType.isRequired
diff --git a/vnext/src/ui/Post.js b/vnext/src/ui/Post.js
index 45eceb35..6f7d1e93 100644
--- a/vnext/src/ui/Post.js
+++ b/vnext/src/ui/Post.js
@@ -1,42 +1,42 @@
-import { useState } from 'react';
-import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
+import { useState } from 'react'
+import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
-import Button from './Button';
-import MessageInput from './MessageInput';
+import Button from './Button'
+import MessageInput from './MessageInput'
-import { post, update } from '../api';
-import { useVisitor } from './VisitorContext';
-import { Helmet } from 'react-helmet';
+import { post, update } from '../api'
+import { useVisitor } from './VisitorContext'
+import { Helmet } from 'react-helmet'
/**
*
*/
export default function Post() {
- const location = useLocation();
- const navigate = useNavigate();
- const [visitor] = useVisitor();
- let draftMessage = (location.state || {}).data || {};
- let [draft, setDraft] = useState(draftMessage.body);
- let [params] = useSearchParams();
+ const location = useLocation()
+ const navigate = useNavigate()
+ const [visitor] = useVisitor()
+ let draftMessage = (location.state || {}).data || {}
+ let [draft, setDraft] = useState(draftMessage.body)
+ let [params] = useSearchParams()
let postMessage = async ({ attach, body }) => {
try {
- const res = draftMessage.mid ? await update(draftMessage.mid, 0, body) : await post(body, attach);
- let result = res.status == 200;
+ const res = draftMessage.mid ? await update(draftMessage.mid, 0, body) : await post(body, attach)
+ let result = res.status == 200
if (result) {
- const msg = res.data.newMessage;
- navigate(`/${visitor.uname}/${msg.mid}`);
+ const msg = res.data.newMessage
+ navigate(`/${visitor.uname}/${msg.mid}`)
}
- return result;
+ return result
} catch (e) {
- console.log(e);
+ console.log(e)
}
- return false;
- };
+ return false
+ }
let appendTag = (tag) => {
setDraft(prevDraft => {
- return `${prevDraft || ''} *${tag} `;
- });
- };
+ return `${prevDraft || ''} *${tag} `
+ })
+ }
return (
<div className="msg-cont">
<Helmet>
@@ -51,12 +51,12 @@ export default function Post() {
<p>Tags:</p>
{
visitor.tagStats.map(t => {
- return (<Button key={t.tag} onClick={() => { appendTag(t.tag); }}>{t.tag}</Button>);
+ return (<Button key={t.tag} onClick={() => { appendTag(t.tag) }}>{t.tag}</Button>)
})
}
</div>
}
</div>
- );
+ )
}
diff --git a/vnext/src/ui/SearchBox.js b/vnext/src/ui/SearchBox.js
index 636967b1..e63a19ee 100644
--- a/vnext/src/ui/SearchBox.js
+++ b/vnext/src/ui/SearchBox.js
@@ -1,4 +1,4 @@
-import { useForm } from 'react-hook-form';
+import { useForm } from 'react-hook-form'
/**
* @typedef {object} SearchBoxPropsFields
@@ -13,18 +13,18 @@ import { useForm } from 'react-hook-form';
* @param {SearchBoxProps} props
*/
function SearchBox({ onSearch }) {
- const { register, handleSubmit } = useForm();
+ const { register, handleSubmit } = useForm()
/** @type { import('react-hook-form').SubmitHandler<import('react-hook-form').FieldValues> } */
let onSubmit = ( values ) => {
- onSearch(values.search);
- };
+ onSearch(values.search)
+ }
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input className="text" type="text"
placeholder="Search..." {...register('search')} />
<input data-testid="submit" type="submit" hidden />
</form>
- );
+ )
}
-export default SearchBox;
+export default SearchBox
diff --git a/vnext/src/ui/Settings.js b/vnext/src/ui/Settings.js
index 380d8ff6..1205cd49 100644
--- a/vnext/src/ui/Settings.js
+++ b/vnext/src/ui/Settings.js
@@ -1,46 +1,46 @@
-import { Fragment, useState, useRef } from 'react';
+import { Fragment, useState, useRef } from 'react'
-import { me, updateAvatar } from '../api';
+import { me, updateAvatar } from '../api'
-import Button from './Button';
-import Icon from './Icon';
-import UploadButton from './UploadButton';
-import Avatar from './Avatar';
-import { useVisitor } from './VisitorContext';
-import { Helmet } from 'react-helmet';
+import Button from './Button'
+import Icon from './Icon'
+import UploadButton from './UploadButton'
+import Avatar from './Avatar'
+import { useVisitor } from './VisitorContext'
+import { Helmet } from 'react-helmet'
/**
* @param {{ onChange: Function }} props
*/
function ChangeAvatarForm({ onChange }) {
- const [visitor] = useVisitor();
- const [avatar, setAvatar] = useState('');
- const [preview, setPreview] = useState();
- const avatarInput = useRef();
+ const [visitor] = useVisitor()
+ const [avatar, setAvatar] = useState('')
+ const [preview, setPreview] = useState()
+ const avatarInput = useRef()
let avatarChanged = (newAvatar) => {
- setAvatar(newAvatar);
- setPreview('');
+ setAvatar(newAvatar)
+ setPreview('')
if (newAvatar) {
- let reader = new FileReader();
+ let reader = new FileReader()
reader.onloadend = (preview) => {
- setPreview(preview.target.result);
- };
- reader.readAsDataURL(avatarInput.current.files[0]);
+ setPreview(preview.target.result)
+ }
+ reader.readAsDataURL(avatarInput.current.files[0])
}
- };
- let previewUser = { ...visitor, uname: '<preview>' };
+ }
+ let previewUser = { ...visitor, uname: '<preview>' }
if (preview) {
- previewUser = { ...visitor, avatar: preview, uname: '<preview>' };
+ previewUser = { ...visitor, avatar: preview, uname: '<preview>' }
}
let onSubmitAvatar = async (event) => {
if (event.preventDefault) {
- event.preventDefault();
+ event.preventDefault()
}
- await updateAvatar(avatarInput.current.files[0]);
- avatarChanged('');
- let visitor = await me();
- onChange(visitor);
- };
+ await updateAvatar(avatarInput.current.files[0])
+ avatarChanged('')
+ let visitor = await me()
+ onChange(visitor)
+ }
return (
<form>
<small>Recommendations: PNG, 96x96, &lt;50Kb. Also, JPG and GIF supported.</small>
@@ -50,7 +50,7 @@ function ChangeAvatarForm({ onChange }) {
<Avatar user={previewUser} />
<Button onClick={onSubmitAvatar}>Update</Button>
</form>
- );
+ )
}
/**
@@ -58,47 +58,47 @@ function ChangeAvatarForm({ onChange }) {
*/
export default function Settings({ onChange }) {
- const [visitor] = useVisitor();
+ const [visitor] = useVisitor()
let passwordChanged = () => {
- console.log('password changed');
- };
+ console.log('password changed')
+ }
let onSubmitPassword = (event) => {
if (event.preventDefault) {
- event.preventDefault();
+ event.preventDefault()
}
- console.log('password update');
- };
+ console.log('password update')
+ }
let emailChanged = () => {
- console.log('email update');
- };
+ console.log('email update')
+ }
let disableTelegram = () => {
- console.log('telegram disable');
- };
+ console.log('telegram disable')
+ }
let disableFacebook = (event) => {
if (event.preventDefault) {
- event.preventDefault();
+ event.preventDefault()
}
- console.log('facebook disable');
- };
+ console.log('facebook disable')
+ }
let enableFacebook = (event) => {
if (event.preventDefault) {
- event.preventDefault();
+ event.preventDefault()
}
- console.log('facebook enable');
- };
+ console.log('facebook enable')
+ }
let disableTwitter = () => {
- console.log('twitter disable');
- };
+ console.log('twitter disable')
+ }
let deleteJid = () => {
// TODO
- };
+ }
let addEmail = () => {
// TODO
- };
+ }
let deleteEmail = () => {
// TODO
- };
+ }
return (
<div className="msg-cont">
<Helmet>
@@ -249,7 +249,7 @@ export default function Settings({ onChange }) {
</fieldset>
</div>
- );
+ )
}
diff --git a/vnext/src/ui/Spinner.js b/vnext/src/ui/Spinner.js
index 3e38571e..b611abb8 100644
--- a/vnext/src/ui/Spinner.js
+++ b/vnext/src/ui/Spinner.js
@@ -1,5 +1,5 @@
-import { memo } from 'react';
-import ContentLoader from 'react-content-loader';
+import { memo } from 'react'
+import ContentLoader from 'react-content-loader'
function Spinner(props) {
return (
@@ -20,10 +20,10 @@ function Spinner(props) {
</ContentLoader>
</div>
</div>
- );
+ )
}
-export default memo(Spinner);
+export default memo(Spinner)
/**
*
@@ -42,5 +42,5 @@ export function ChatSpinner(props) {
<rect x="56" y="20" rx="0" ry="0" width="85" height="6.4" />
<rect x="0" y="0" rx="0" ry="0" width="48" height="48" />
</ContentLoader>
- );
+ )
}
diff --git a/vnext/src/ui/Thread.js b/vnext/src/ui/Thread.js
index a3136c35..b727b73d 100644
--- a/vnext/src/ui/Thread.js
+++ b/vnext/src/ui/Thread.js
@@ -1,128 +1,128 @@
-import { useEffect, useState, useCallback } from 'react';
-import { useLocation, useParams } from 'react-router-dom';
+import { useEffect, useState, useCallback } from 'react'
+import { useLocation, useParams } from 'react-router-dom'
-import Comment from './Comment';
-import Message from './Message';
-import MessageInput from './MessageInput';
-import Spinner from './Spinner';
+import Comment from './Comment'
+import Message from './Message'
+import MessageInput from './MessageInput'
+import Spinner from './Spinner'
-import { getMessages, comment, update, post } from '../api';
-import { useVisitor } from './VisitorContext';
-import { Helmet } from 'react-helmet';
+import { getMessages, comment, update, post } from '../api'
+import { useVisitor } from './VisitorContext'
+import { Helmet } from 'react-helmet'
/**
* @type { import('../api').Message }
*/
-const emptyMessage = {};
+const emptyMessage = {}
/**
* Thread component
* @param {import('react').PropsWithChildren<{}>} props
*/
export default function Thread(props) {
- const location = useLocation();
- const params = useParams();
- const [message, setMessage] = useState((location.state || {}).data || {});
- const [replies, setReplies] = useState([]);
- const [loading, setLoading] = useState(false);
- const [active, setActive] = useState(0);
+ const location = useLocation()
+ const params = useParams()
+ const [message, setMessage] = useState((location.state || {}).data || {})
+ const [replies, setReplies] = useState([])
+ const [loading, setLoading] = useState(false)
+ const [active, setActive] = useState(0)
- const [editing, setEditing] = useState(emptyMessage);
- const [visitor] = useVisitor();
- const [hash] = useState(visitor.hash);
- const { mid } = params;
+ const [editing, setEditing] = useState(emptyMessage)
+ const [visitor] = useVisitor()
+ const [hash] = useState(visitor.hash)
+ const { mid } = params
let loadReplies = useCallback(() => {
- document.body.scrollTop = 0;
- document.documentElement.scrollTop = 0;
- setReplies([]);
- setLoading(true);
+ document.body.scrollTop = 0
+ document.documentElement.scrollTop = 0
+ setReplies([])
+ setLoading(true)
let params = {
mid: mid
- };
- params.hash = hash;
+ }
+ params.hash = hash
getMessages('/api/thread', params)
.then(response => {
- let updatedMessage = response.data.shift();
+ let updatedMessage = response.data.shift()
if (!message.mid) {
- setMessage(updatedMessage);
+ setMessage(updatedMessage)
}
- setReplies(response.data);
- setLoading(false);
- setActive(0);
+ setReplies(response.data)
+ setLoading(false)
+ setActive(0)
}
).catch(ex => {
- console.log(ex);
- });
- }, [hash, message.mid, mid]);
+ console.log(ex)
+ })
+ }, [hash, message.mid, mid])
let postComment = async ({ body, attach }) => {
try {
let res = editing.rid ? await update(mid, editing.rid, body)
- : await comment(mid, active, body, attach);
- let result = res.status == 200;
+ : await comment(mid, active, body, attach)
+ let result = res.status == 200
if (result) {
- setEditing(emptyMessage);
+ setEditing(emptyMessage)
}
- return result;
+ return result
} catch (e) {
- console.error(e);
+ console.error(e)
}
- return false;
- };
+ return false
+ }
let startEditing = (reply) => {
- setActive(reply.to.rid || 0);
- setEditing(reply);
- };
+ setActive(reply.to.rid || 0)
+ setEditing(reply)
+ }
useEffect(() => {
- setActive(0);
- loadReplies();
- }, [loadReplies]);
+ setActive(0)
+ loadReplies()
+ }, [loadReplies])
useEffect(() => {
let onReply = (json) => {
- const msg = JSON.parse(json.data);
+ const msg = JSON.parse(json.data)
if (msg.mid == message.mid) {
setReplies(oldReplies => {
- return [...oldReplies, msg];
- });
+ return [...oldReplies, msg]
+ })
setActive(prev => {
- return prev + 1;
- });
+ return prev + 1
+ })
}
- };
+ }
if (props.connection.addEventListener && message.mid) {
- props.connection.addEventListener('msg', onReply);
+ props.connection.addEventListener('msg', onReply)
}
return () => {
if (props.connection.removeEventListener && message.mid) {
- props.connection.removeEventListener('msg', onReply);
+ props.connection.removeEventListener('msg', onReply)
}
- };
- }, [props.connection, message.mid]);
+ }
+ }, [props.connection, message.mid])
- const loaders = Math.min(message.replies || 0, 10);
- const pageTitle = `${params.user} ${message && message.tags || 'thread'}`;
+ const loaders = Math.min(message.replies || 0, 10)
+ const pageTitle = `${params.user} ${message && message.tags || 'thread'}`
/** @type { import('./Message').ToggleSubscriptionCallback } */
const handleSubsciptionToggle = (message) => {
if (message.subscribed) {
if (confirm('Unsubscribe?')) {
post(`U #${message.mid}`).then((response) => {
if (response.status === 200) {
- setMessage({...message, subscribed: false});
+ setMessage({...message, subscribed: false})
}
- }).catch(console.error);
+ }).catch(console.error)
}
} else {
if (confirm('Subscribe?')) {
post(`S #${message.mid}`).then((response) => {
if (response.status === 200) {
- setMessage({...message, subscribed: true});
+ setMessage({...message, subscribed: true})
}
- }).catch(console.error);
+ }).catch(console.error)
}
}
- };
+ }
return (
<>
<Helmet>
@@ -155,5 +155,5 @@ export default function Thread(props) {
</ul>
}
</>
- );
+ )
}
diff --git a/vnext/src/ui/UploadButton.js b/vnext/src/ui/UploadButton.js
index b652e522..5ef2fd94 100644
--- a/vnext/src/ui/UploadButton.js
+++ b/vnext/src/ui/UploadButton.js
@@ -1,4 +1,4 @@
-import Icon from './Icon';
+import Icon from './Icon'
/**
* @typedef {object} UploadButtonProps
@@ -13,20 +13,20 @@ import Icon from './Icon';
*/
export default function UploadButton(props) {
let openfile = () => {
- const input = props.inputRef.current;
+ const input = props.inputRef.current
if (props.value) {
- props.onChange('');
+ props.onChange('')
} else {
- input.click();
+ input.click()
}
- };
+ }
/**
* @param {import('react').ChangeEvent<HTMLInputElement>} event
*/
let attachmentChanged = (event) => {
- props.onChange(event.target.value);
- };
+ props.onChange(event.target.value)
+ }
return (
<div style={props.value ? activeStyle : inactiveStyle}
onClick={openfile}>
@@ -35,14 +35,14 @@ export default function UploadButton(props) {
style={{ display: 'none' }} ref={props.inputRef} value={props.value}
onChange={attachmentChanged} />
</div>
- );
+ )
}
const inactiveStyle = {
cursor: 'pointer',
color: '#888'
-};
+}
const activeStyle = {
cursor: 'pointer',
color: 'green'
-};
+}
diff --git a/vnext/src/ui/UserInfo.js b/vnext/src/ui/UserInfo.js
index 2ca8c431..f71dfcdc 100644
--- a/vnext/src/ui/UserInfo.js
+++ b/vnext/src/ui/UserInfo.js
@@ -1,13 +1,13 @@
-import { memo, useState, useEffect, useRef } from 'react';
-import { Link } from 'react-router-dom';
+import { memo, useState, useEffect, useRef } from 'react'
+import { Link } from 'react-router-dom'
-import { info, fetchUserUri } from '../api';
+import { info, fetchUserUri } from '../api'
-import Avatar from './Avatar';
-import Icon from './Icon';
-import defaultAvatar from '../assets/av-96.png';
+import Avatar from './Avatar'
+import Icon from './Icon'
+import defaultAvatar from '../assets/av-96.png'
-let isMounted;
+let isMounted
/**
* User info component
@@ -17,19 +17,19 @@ export default function UserInfo({ uname, onUpdate, children }) {
const [user, setUser] = useState({
uname: uname,
uid: 0
- });
+ })
useEffect(() => {
- isMounted = true;
+ isMounted = true
info(uname).then(response => {
if (isMounted) {
- onUpdate && onUpdate(response.data);
- setUser(response.data);
+ onUpdate && onUpdate(response.data)
+ setUser(response.data)
}
- }).catch(console.log);
+ }).catch(console.log)
return () => {
- isMounted = false;
- };
- }, [onUpdate, uname]);
+ isMounted = false
+ }
+ }, [onUpdate, uname])
return (
<>
<div className="userinfo">
@@ -59,7 +59,7 @@ export default function UserInfo({ uname, onUpdate, children }) {
</div>
{children}
</>
- );
+ )
}
/**
@@ -67,22 +67,22 @@ export default function UserInfo({ uname, onUpdate, children }) {
* @param {{user: import('../api').User}} props
*/
function Summary({ user }) {
- const readUrl = `/${user.uname}/friends`;
- const readersUrl = `/${user.uname}/readers`;
- const blUrl = `/${user.uname}/bl`;
- let read = user.read && <Link key={readUrl} to={readUrl}>I read: {user.read.length}</Link>;
- let readers = user.readers && <Link key={readersUrl} to={readersUrl}>My readers: {user.readers.length}</Link>;
- let mybl = user.statsMyBL && <Link key={blUrl} to={blUrl}>My blacklist: {user.statsMyBL}</Link>;
- let presentItems = [read, readers, mybl].filter(Boolean);
+ const readUrl = `/${user.uname}/friends`
+ const readersUrl = `/${user.uname}/readers`
+ const blUrl = `/${user.uname}/bl`
+ let read = user.read && <Link key={readUrl} to={readUrl}>I read: {user.read.length}</Link>
+ let readers = user.readers && <Link key={readersUrl} to={readersUrl}>My readers: {user.readers.length}</Link>
+ let mybl = user.statsMyBL && <Link key={blUrl} to={blUrl}>My blacklist: {user.statsMyBL}</Link>
+ let presentItems = [read, readers, mybl].filter(Boolean)
return (
<div className="msg-summary">
{presentItems.length > 0 && presentItems.reduce((prev, curr) =>
[prev, ' ', curr])}
</div>
- );
+ )
}
-const UserSummary = memo(Summary);
+const UserSummary = memo(Summary)
/**
@@ -90,10 +90,10 @@ const UserSummary = memo(Summary);
* @param {{ user: import('../api').User}} props
*/
export function UserLink(props) {
- const [user, setUser] = useState(props.user);
- const userRef = useRef(user);
+ const [user, setUser] = useState(props.user)
+ const userRef = useRef(user)
useEffect(() => {
- isMounted = true;
+ isMounted = true
if (userRef.current.uri) {
fetchUserUri(userRef.current.uri).then(remote_user => {
if (isMounted) {
@@ -102,7 +102,7 @@ export function UserLink(props) {
uname: remote_user.preferredUsername,
avatar: remote_user.icon && remote_user.icon.url,
uri: userRef.current.uri
- });
+ })
}
}).catch(() => {
setUser({
@@ -110,13 +110,13 @@ export function UserLink(props) {
uname: userRef.current.uri,
uri: userRef.current.uri,
avatar: defaultAvatar
- });
- });
+ })
+ })
}
return () => {
- isMounted = false;
- };
- }, [props.user]);
+ isMounted = false
+ }
+ }, [props.user])
return (
user.uid ?
<Link key={user.uid} to={`/${user.uname}/`} className="info-avatar">
@@ -125,5 +125,5 @@ export function UserLink(props) {
: <a href={user.uri} className="info-avatar">
<img src={user.avatar || defaultAvatar} />{user.uname}
</a>
- );
+ )
}
diff --git a/vnext/src/ui/Users.js b/vnext/src/ui/Users.js
index 32ff2d03..e4fcba1f 100644
--- a/vnext/src/ui/Users.js
+++ b/vnext/src/ui/Users.js
@@ -1,15 +1,15 @@
-import { useState } from 'react';
-import { useParams } from 'react-router-dom';
+import { useState } from 'react'
+import { useParams } from 'react-router-dom'
-import UserInfo from './UserInfo';
-import Avatar from './Avatar';
-import { Helmet } from 'react-helmet';
+import UserInfo from './UserInfo'
+import Avatar from './Avatar'
+import { Helmet } from 'react-helmet'
/**
* Friends feed
*/
export function Friends() {
- const params = useParams();
+ const params = useParams()
return (
<>
<Helmet>
@@ -17,14 +17,14 @@ export function Friends() {
</Helmet>
<Users uname={params.user} prop='read' />
</>
- );
+ )
}
/**
* Readers feed
*/
export function Readers() {
- const params = useParams();
+ const params = useParams()
return (
<>
<Helmet>
@@ -32,7 +32,7 @@ export function Readers() {
</Helmet>
<Users uname={params.user} prop='readers' />
</>
- );
+ )
}
/**
@@ -40,7 +40,7 @@ export function Readers() {
* @param {{uname: string, prop: string}} props
*/
function Users({ uname, prop }) {
- const [user, setUser] = useState({ uid: 0, uname: uname });
+ const [user, setUser] = useState({ uid: 0, uname: uname })
return (
<UserInfo uname={uname} onUpdate={setUser}>
<div style={{ display: 'flex', flexWrap: 'wrap', flexDirection: 'row' }}>
@@ -52,5 +52,5 @@ function Users({ uname, prop }) {
}
</div>
</UserInfo>
- );
+ )
}
diff --git a/vnext/src/ui/VisitorContext.js b/vnext/src/ui/VisitorContext.js
index 240b709b..9740f9ca 100644
--- a/vnext/src/ui/VisitorContext.js
+++ b/vnext/src/ui/VisitorContext.js
@@ -1,18 +1,18 @@
-import { createContext, useContext, useState } from 'react';
+import { createContext, useContext, useState } from 'react'
-const Visitor = createContext();
+const Visitor = createContext()
/** @type {import('../api').SecureUser} */
const unknownUser = {
uid: -1
-};
+}
/**
* @param { import('react').PropsWithChildren<{}> } props
*/
export function VisitorProvider({ children }) {
- const state = useState(unknownUser);
- return <Visitor.Provider value={state}>{children}</Visitor.Provider>;
+ const state = useState(unknownUser)
+ return <Visitor.Provider value={state}>{children}</Visitor.Provider>
}
/**
@@ -23,9 +23,9 @@ export function VisitorProvider({ children }) {
* ]} visitor hook
*/
export function useVisitor() {
- const visitor = useContext(Visitor);
+ const visitor = useContext(Visitor)
if (visitor === undefined) {
- throw new Error('useVisitor must be used within a VisitorProvider');
+ throw new Error('useVisitor must be used within a VisitorProvider')
}
- return visitor;
+ return visitor
} \ No newline at end of file
diff --git a/vnext/src/ui/__tests__/Avatar.test.js b/vnext/src/ui/__tests__/Avatar.test.js
index f454f6c7..7aea804c 100644
--- a/vnext/src/ui/__tests__/Avatar.test.js
+++ b/vnext/src/ui/__tests__/Avatar.test.js
@@ -1,14 +1,14 @@
-import { MemoryRouter } from 'react-router-dom';
+import { MemoryRouter } from 'react-router-dom'
-import Avatar from '../Avatar';
-import renderer from 'react-test-renderer';
+import Avatar from '../Avatar'
+import renderer from 'react-test-renderer'
test('Avatar renders correctly', () => {
const component = renderer.create(
<MemoryRouter>
<Avatar user={{ uid: 1, uname: 'ugnich', avatar: 'https://juick.com/i/a/1-deadbeef.png' }} />
</MemoryRouter>
- );
- let tree = component.toJSON();
- expect(tree).toMatchSnapshot();
-});
+ )
+ let tree = component.toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/vnext/src/ui/__tests__/MessageInput.test.js b/vnext/src/ui/__tests__/MessageInput.test.js
index 0bfe2569..4af36b71 100644
--- a/vnext/src/ui/__tests__/MessageInput.test.js
+++ b/vnext/src/ui/__tests__/MessageInput.test.js
@@ -1,46 +1,46 @@
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
-import MessageInput from '../MessageInput';
+import MessageInput from '../MessageInput'
test('Gives immediate focus on to textarea on load', async () => {
- let draft = 'draft';
- render(<MessageInput text={draft} onSend={async () => { return true; }} />);
- expect(screen.getByText(draft)).toHaveFocus();
-});
+ let draft = 'draft'
+ render(<MessageInput text={draft} onSend={async () => { return true }} />)
+ expect(screen.getByText(draft)).toHaveFocus()
+})
test('Submits on ctrl-enter and pass validation', async () => {
- let result = false;
- const onSend = jest.fn(async ({ body }) => { result = body === 'YO'; return result; });
- let draft = 'draft';
- render(<MessageInput onSend={onSend} text={draft} />);
- let textarea = screen.getByText(draft);
- fireEvent.change(textarea, { target: { value: 'HI' } });
+ let result = false
+ const onSend = jest.fn(async ({ body }) => { result = body === 'YO'; return result })
+ let draft = 'draft'
+ render(<MessageInput onSend={onSend} text={draft} />)
+ let textarea = screen.getByText(draft)
+ fireEvent.change(textarea, { target: { value: 'HI' } })
// this event should not submit
fireEvent.keyPress(textarea, {
charCode: 13,
which: 13,
keyCode: 13
- });
+ })
// this event should submit
fireEvent.keyPress(textarea, {
charCode: 13,
which: 13,
keyCode: 13,
ctrlKey: true
- });
- expect(onSend).toHaveBeenCalledTimes(1);
- expect(result).toBe(false);
- fireEvent.change(textarea, { target: { value: 'YO' } });
+ })
+ expect(onSend).toHaveBeenCalledTimes(1)
+ expect(result).toBe(false)
+ fireEvent.change(textarea, { target: { value: 'YO' } })
fireEvent.keyPress(textarea, {
charCode: 13,
which: 13,
keyCode: 13,
ctrlKey: true
- });
- expect(onSend).toHaveBeenCalledTimes(2);
- expect(result).toBe(true);
- await waitFor(() => expect(textarea).toHaveTextContent(''));
- textarea.focus();
- expect(textarea).toHaveFocus();
-});
+ })
+ expect(onSend).toHaveBeenCalledTimes(2)
+ expect(result).toBe(true)
+ await waitFor(() => expect(textarea).toHaveTextContent(''))
+ textarea.focus()
+ expect(textarea).toHaveFocus()
+})
diff --git a/vnext/src/ui/__tests__/UserLink.test.js b/vnext/src/ui/__tests__/UserLink.test.js
index 6bb4da29..99ca42ce 100644
--- a/vnext/src/ui/__tests__/UserLink.test.js
+++ b/vnext/src/ui/__tests__/UserLink.test.js
@@ -1,10 +1,10 @@
-import { MemoryRouter } from 'react-router-dom';
+import { MemoryRouter } from 'react-router-dom'
-import { UserLink } from '../UserInfo';
-import renderer, { act } from 'react-test-renderer';
+import { UserLink } from '../UserInfo'
+import renderer, { act } from 'react-test-renderer'
test('UserLink renders correctly', async () => {
- let component = null;
+ let component = null
act(() => {
component = renderer.create(
<MemoryRouter>
@@ -14,8 +14,8 @@ test('UserLink renders correctly', async () => {
<UserLink user={{ uid: 0, uname: '', uri: 'https://example.com/u/test' }} />
</>
</MemoryRouter>
- );
- });
- let tree = component.toJSON();
- expect(tree).toMatchSnapshot();
-});
+ )
+ })
+ let tree = component.toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/vnext/src/ui/helpers/BubbleStyle.js b/vnext/src/ui/helpers/BubbleStyle.js
index d2886e1e..def60b62 100644
--- a/vnext/src/ui/helpers/BubbleStyle.js
+++ b/vnext/src/ui/helpers/BubbleStyle.js
@@ -4,9 +4,9 @@
* @returns { import('react').CSSProperties} CSS properties
*/
export function chatItemStyle(me, msg) {
- const user = msg.user;
- const isMe = me.uid === user.uid;
- const alignment = isMe ? 'flex-end' : 'flex-start';
+ const user = msg.user
+ const isMe = me.uid === user.uid
+ const alignment = isMe ? 'flex-end' : 'flex-start'
return {
padding: '3px 6px',
listStyle: 'none',
@@ -14,5 +14,5 @@ export function chatItemStyle(me, msg) {
display: 'flex',
flexDirection: 'column',
alignItems: alignment
- };
+ }
}
diff --git a/vnext/src/utils/embed.js b/vnext/src/utils/embed.js
index f16342cf..afec71b4 100644
--- a/vnext/src/utils/embed.js
+++ b/vnext/src/utils/embed.js
@@ -2,21 +2,21 @@ function htmlEscape(html) {
return html.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
- .replace(/>/g, '&gt;');
+ .replace(/>/g, '&gt;')
}
function insertAfter(newNode, referenceNode) {
- referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
+ referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling)
}
function setContent(containerNode, ...newNodes) {
- removeAllFrom(containerNode);
- newNodes.forEach(n => containerNode.appendChild(n));
- return containerNode;
+ removeAllFrom(containerNode)
+ newNodes.forEach(n => containerNode.appendChild(n))
+ return containerNode
}
function removeAllFrom(fromNode) {
- fromNode.innerHTML = '';
+ fromNode.innerHTML = ''
}
// rules :: [{pr: number, re: RegExp, with: string}]
@@ -24,96 +24,96 @@ function removeAllFrom(fromNode) {
// rules :: [{pr: number, re: RegExp, brackets: true, with: [string, string]}]
// rules :: [{pr: number, re: RegExp, brackets: true, with: [string, string, Function]}]
function formatText(txt, rules) {
- let idCounter = 0;
- function nextId() { return idCounter++; }
+ let idCounter = 0
+ function nextId() { return idCounter++ }
function ft(txt, rules) {
- let matches = rules.map(r => { r.re.lastIndex = 0; return [r, r.re.exec(txt)]; })
+ let matches = rules.map(r => { r.re.lastIndex = 0; return [r, r.re.exec(txt)] })
.filter(([, m]) => m !== null)
- .sort(([r1, m1], [r2, m2]) => (r1.pr - r2.pr) || (m1.index - m2.index));
+ .sort(([r1, m1], [r2, m2]) => (r1.pr - r2.pr) || (m1.index - m2.index))
if (matches && matches.length > 0) {
- let [rule, match] = matches[0];
- let subsequentRules = rules.filter(r => r.pr >= rule.pr);
- let idStr = `<>(${nextId()})<>`;
- let outerStr = txt.substring(0, match.index) + idStr + txt.substring(rule.re.lastIndex);
+ let [rule, match] = matches[0]
+ let subsequentRules = rules.filter(r => r.pr >= rule.pr)
+ let idStr = `<>(${nextId()})<>`
+ let outerStr = txt.substring(0, match.index) + idStr + txt.substring(rule.re.lastIndex)
let innerStr = (rule.brackets)
- ? (() => { let [l, r, f] = rule.with; return l + ft((f ? f(match[1]) : match[1]), subsequentRules) + r; })()
- : match[0].replace(rule.re, rule.with);
- return ft(outerStr, subsequentRules).replace(idStr, innerStr);
+ ? (() => { let [l, r, f] = rule.with; return l + ft((f ? f(match[1]) : match[1]), subsequentRules) + r })()
+ : match[0].replace(rule.re, rule.with)
+ return ft(outerStr, subsequentRules).replace(idStr, innerStr)
}
- return txt;
+ return txt
}
- return ft(htmlEscape(txt), rules); // idStr above relies on the fact the text is escaped
+ return ft(htmlEscape(txt), rules) // idStr above relies on the fact the text is escaped
}
function fixWwwLink(url) {
- return url.replace(/^(?!([a-z]+:)?\/\/)/i, '//');
+ return url.replace(/^(?!([a-z]+:)?\/\/)/i, '//')
}
function makeNewNode(embedType, aNode, reResult) {
const withClasses = el => {
if (embedType.className) {
- el.classList.add(...embedType.className.split(' '));
+ el.classList.add(...embedType.className.split(' '))
}
- return el;
- };
- return embedType.makeNode(aNode, reResult, withClasses(document.createElement('div')));
+ return el
+ }
+ return embedType.makeNode(aNode, reResult, withClasses(document.createElement('div')))
}
function makeIframe(src, w, h, scrolling = 'no') {
- let iframe = document.createElement('iframe');
- iframe.style.width = w;
- iframe.style.height = h;
- iframe.frameBorder = '0';
- iframe.scrolling = scrolling;
- iframe.setAttribute('allowFullScreen', '');
- iframe.src = src;
- iframe.innerHTML = 'Cannot show iframes.';
- return iframe;
+ let iframe = document.createElement('iframe')
+ iframe.style.width = w
+ iframe.style.height = h
+ iframe.frameBorder = '0'
+ iframe.scrolling = scrolling
+ iframe.setAttribute('allowFullScreen', '')
+ iframe.src = src
+ iframe.innerHTML = 'Cannot show iframes.'
+ return iframe
}
function makeResizableToRatio(element, ratio) {
- element.setAttribute('data-ratio', ratio);
- makeResizable(element, w => w * element.getAttribute('data-ratio'));
+ element.setAttribute('data-ratio', ratio)
+ makeResizable(element, w => w * element.getAttribute('data-ratio'))
}
// calcHeight :: Number -> Number -- calculate element height for a given width
function makeResizable(element, calcHeight) {
const setHeight = el => {
if (document.body.contains(el) && (el.offsetWidth > 0)) {
- el.style.height = (calcHeight(el.offsetWidth)).toFixed(2) + 'px';
+ el.style.height = (calcHeight(el.offsetWidth)).toFixed(2) + 'px'
}
- };
- window.addEventListener('resize', () => setHeight(element));
- setHeight(element);
+ }
+ window.addEventListener('resize', () => setHeight(element))
+ setHeight(element)
}
function extractDomain(url) {
- const domainRe = /^(?:https?:\/\/)?(?:[^@/\n]+@)?(?:www\.)?([^:/\n]+)/i;
- let result = domainRe.exec(url) || [];
+ const domainRe = /^(?:https?:\/\/)?(?:[^@/\n]+@)?(?:www\.)?([^:/\n]+)/i
+ let result = domainRe.exec(url) || []
if (result.length > 0) {
- return result[1];
+ return result[1]
}
}
function urlReplace(match, p1, p2, p3) {
- let isBrackets = (p1 !== undefined);
+ let isBrackets = (p1 !== undefined)
return (isBrackets)
? `<a href="${fixWwwLink(p2 || p3)}">${p1}</a>`
- : `<a href="${fixWwwLink(match)}">${extractDomain(match)}</a>`;
+ : `<a href="${fixWwwLink(match)}">${extractDomain(match)}</a>`
}
function urlReplaceInCode(match, p1, p2, p3) {
- let isBrackets = (p1 !== undefined);
+ let isBrackets = (p1 !== undefined)
return (isBrackets)
? `<a href="${fixWwwLink(p2 || p3)}">${match}</a>`
- : `<a href="${fixWwwLink(match)}">${match}</a>`;
+ : `<a href="${fixWwwLink(match)}">${match}</a>`
}
function messageReplyReplace(messageId) {
return function(match, mid, rid) {
- let replyPart = (rid && rid != '0') ? '#' + rid : '';
- return `<a href="/m/${mid || messageId}${replyPart}">${match}</a>`;
- };
+ let replyPart = (rid && rid != '0') ? '#' + rid : ''
+ return `<a href="/m/${mid || messageId}${replyPart}">${match}</a>`
+ }
}
/**
@@ -125,8 +125,8 @@ function messageReplyReplace(messageId) {
* @returns {string} formatted message
*/
function juickFormat(txt, messageId, isCode) {
- const urlRe = /(?:\[([^\][]+)\](?:\[([^\]]+)\]|\(((?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[-\S+*&@#/%=~|$?!:;,.])*(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[\S+*&@#/%=~|$]))\))|\b(?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[-\S+*&@#/%=~|$?!:;,.])*(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[\S+*&@#/%=~|$]))/gi;
- const bqReplace = m => m.replace(/^(?:>|&gt;)\s?/gmi, '');
+ const urlRe = /(?:\[([^\][]+)\](?:\[([^\]]+)\]|\(((?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[-\S+*&@#/%=~|$?!:;,.])*(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[\S+*&@#/%=~|$]))\))|\b(?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[-\S+*&@#/%=~|$?!:;,.])*(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[\S+*&@#/%=~|$]))/gi
+ const bqReplace = m => m.replace(/^(?:>|&gt;)\s?/gmi, '')
return (isCode)
? formatText(txt, [
{ pr: 1, re: urlRe, with: urlReplaceInCode },
@@ -142,7 +142,7 @@ function juickFormat(txt, messageId, isCode) {
{ pr: 2, re: /\B\/([^\n]+?)\/((?=\s)|(?=$)|(?=[!"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<i>', '</i>'] },
{ pr: 2, re: /\b_([^\n]+?)_((?=\s)|(?=$)|(?=[!"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<u>', '</u>'] },
{ pr: 3, re: /\n/g, with: '<br/>' },
- ]);
+ ])
}
/**
* @external RegExpExecArray
@@ -179,9 +179,9 @@ function getEmbeddableLinkTypes() {
re: /\.(jpe?g|png|svg)(:[a-zA-Z]+)?(?:\?[\w&;?=]*)?$/i,
makeNode: function(aNode, reResult, div) {
// dirty fix for dropbox urls
- let url = aNode.href.endsWith('dl=0') ? aNode.href.replace('dl=0', 'raw=1') : aNode.href;
- div.innerHTML = `<a href="${url}"><img src="${url}"></a>`;
- return div;
+ let url = aNode.href.endsWith('dl=0') ? aNode.href.replace('dl=0', 'raw=1') : aNode.href
+ div.innerHTML = `<a href="${url}"><img src="${url}"></a>`
+ return div
}
},
{
@@ -190,8 +190,8 @@ function getEmbeddableLinkTypes() {
className: 'picture compact',
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;
+ div.innerHTML = `<a href="${aNode.href}"><img src="${aNode.href}"></a>`
+ return div
}
},
{
@@ -200,8 +200,8 @@ function getEmbeddableLinkTypes() {
className: 'video compact',
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;
+ div.innerHTML = `<video src="${aNode.href}" title="${aNode.href}" controls></video>`
+ return div
}
},
{
@@ -210,8 +210,8 @@ function getEmbeddableLinkTypes() {
className: 'audio singleColumn',
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;
+ div.innerHTML = `<audio src="${aNode.href}" title="${aNode.href}" controls></audio>`
+ return div
}
},
{
@@ -220,37 +220,37 @@ function getEmbeddableLinkTypes() {
className: 'youtube resizableV singleColumn',
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;
+ let [, v, args, plist] = reResult
+ let iframeUrl
if (plist) {
- iframeUrl = '//www.youtube-nocookie.com/embed/videoseries?list=' + plist;
+ iframeUrl = '//www.youtube-nocookie.com/embed/videoseries?list=' + plist
} else {
let pp = {}; args.replace(/^\?/, '')
.split('&')
.map(s => s.split('='))
- .forEach(z => pp[z[0]] = z[1]);
+ .forEach(z => pp[z[0]] = z[1])
let embedArgs = {
rel: '0',
enablejsapi: '1',
origin: `${window.location.protocol}//${window.location.host}`
- };
+ }
if (pp.t) {
- const tre = /^(?:(\d+)|(?:(\d+)h)?(?:(\d+)m)?(\d+)s|(?:(\d+)h)?(\d+)m|(\d+)h)$/i;
- let [, t, h, m, s, h1, m1, h2] = tre.exec(pp.t);
- embedArgs['start'] = (+t) || ((+(h || h1 || h2 || 0)) * 60 * 60 + (+(m || m1 || 0)) * 60 + (+(s || 0)));
+ const tre = /^(?:(\d+)|(?:(\d+)h)?(?:(\d+)m)?(\d+)s|(?:(\d+)h)?(\d+)m|(\d+)h)$/i
+ let [, t, h, m, s, h1, m1, h2] = tre.exec(pp.t)
+ embedArgs['start'] = (+t) || ((+(h || h1 || h2 || 0)) * 60 * 60 + (+(m || m1 || 0)) * 60 + (+(s || 0)))
}
if (pp.list) {
- embedArgs['list'] = pp.list;
+ embedArgs['list'] = pp.list
}
- v = v || pp.v;
+ v = v || pp.v
let argsStr = Object.keys(embedArgs)
.map(k => `${k}=${embedArgs[k]}`)
- .join('&');
- iframeUrl = `//www.youtube-nocookie.com/embed/${v}?${argsStr}`;
+ .join('&')
+ iframeUrl = `//www.youtube-nocookie.com/embed/${v}?${argsStr}`
}
- let iframe = makeIframe(iframeUrl, '100%', '360px');
- iframe.onload = () => makeResizableToRatio(iframe, 9.0 / 16.0);
- return setContent(div, iframe);
+ let iframe = makeIframe(iframeUrl, '100%', '360px')
+ iframe.onload = () => makeResizableToRatio(iframe, 9.0 / 16.0)
+ return setContent(div, iframe)
}
},
{
@@ -259,9 +259,9 @@ function getEmbeddableLinkTypes() {
className: 'vimeo resizableV',
re: /^(?:https?:)?\/\/(?:www\.)?(?:player\.)?vimeo\.com\/(?:video\/|album\/[\d]+\/video\/)?([\d]+)/i,
makeNode: function(aNode, reResult, div) {
- let iframe = makeIframe('//player.vimeo.com/video/' + reResult[1], '100%', '360px');
- iframe.onload = () => makeResizableToRatio(iframe, 9.0 / 16.0);
- return setContent(div, iframe);
+ let iframe = makeIframe('//player.vimeo.com/video/' + reResult[1], '100%', '360px')
+ iframe.onload = () => makeResizableToRatio(iframe, 9.0 / 16.0)
+ return setContent(div, iframe)
}
},
{
@@ -273,9 +273,9 @@ function getEmbeddableLinkTypes() {
fetch('https://beta.juick.com/api/oembed?url=' + reResult[0])
.then(response => response.json())
.then(json => {
- div.innerHTML = json.html;
- }).catch(console.log);
- return div;
+ div.innerHTML = json.html
+ }).catch(console.log)
+ return div
}
},
{
@@ -284,10 +284,10 @@ function getEmbeddableLinkTypes() {
className: 'picture compact',
re: /https?:\/\/www\.?instagram\.com(\/p\/\w+)\/?/i,
makeNode: function(aNode, reResult, div) {
- let [, postId] = reResult;
- let mediaUrl = `https://instagr.am${postId}/media`;
- div.innerHTML = `<a href="${aNode.href}"><img src="${mediaUrl}"></a>`;
- return div;
+ let [, postId] = reResult
+ let mediaUrl = `https://instagr.am${postId}/media`
+ div.innerHTML = `<a href="${aNode.href}"><img src="${mediaUrl}"></a>`
+ return div
}
},
{
@@ -296,19 +296,19 @@ function getEmbeddableLinkTypes() {
className: 'tg compact',
re: /https?:\/\/t\.me\/(\S+)/i,
makeNode: function(aNode, reResult, div) {
- let [, post] = reResult;
+ let [, post] = reResult
// innerHTML cannot insert scripts, so...
- let script = document.createElement('script');
- script.src = 'https://telegram.org/js/telegram-widget.js?18';
- script.setAttribute('data-telegram-post', post);
- script.setAttribute('data-tme-mode', 'data-tme-mode');
- script.setAttribute('data-width', '100%');
- script.charset = 'utf-8';
- div.appendChild(script);
- return div;
+ let script = document.createElement('script')
+ script.src = 'https://telegram.org/js/telegram-widget.js?18'
+ script.setAttribute('data-telegram-post', post)
+ script.setAttribute('data-tme-mode', 'data-tme-mode')
+ script.setAttribute('data-width', '100%')
+ script.charset = 'utf-8'
+ div.appendChild(script)
+ return div
}
},
- ];
+ ]
}
/**
@@ -320,38 +320,38 @@ function getEmbeddableLinkTypes() {
* @returns { boolean } `true` when some link was embedded
*/
function embedLink(aNode, linkTypes, container, afterNode = false) {
- let anyEmbed = false;
- let linkId = (aNode.href.replace(/^https?:/i, '').replace(/'/gi, ''));
- let sameEmbed = container.querySelector(`*[data-linkid='${linkId}']`); // do not embed the same thing twice
+ let anyEmbed = false
+ 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) {
anyEmbed = linkTypes.some((linkType) => {
- let reResult = linkType.re.exec(aNode.href);
+ let reResult = linkType.re.exec(aNode.href)
if (reResult) {
- if (linkType.match && (linkType.match(aNode, reResult) === false)) { return false; }
- let newNode = makeNewNode(linkType, aNode, reResult);
- if (!newNode) { return false; }
- newNode.setAttribute('data-linkid', linkId);
+ if (linkType.match && (linkType.match(aNode, reResult) === false)) { return false }
+ let newNode = makeNewNode(linkType, aNode, reResult)
+ if (!newNode) { return false }
+ newNode.setAttribute('data-linkid', linkId)
if (afterNode) {
- insertAfter(newNode, afterNode);
+ insertAfter(newNode, afterNode)
} else {
- container.appendChild(newNode);
+ container.appendChild(newNode)
}
- aNode.classList.add('embedLink');
- return true;
+ aNode.classList.add('embedLink')
+ return true
}
- });
+ })
}
- return anyEmbed;
+ return anyEmbed
}
function embedLinks(aNodes, container) {
- let anyEmbed = false;
- let embeddableLinkTypes = getEmbeddableLinkTypes();
+ let anyEmbed = false
+ let embeddableLinkTypes = getEmbeddableLinkTypes()
Array.from(aNodes).forEach(aNode => {
- let isEmbedded = embedLink(aNode, embeddableLinkTypes, container);
- anyEmbed = anyEmbed || isEmbedded;
- });
- return anyEmbed;
+ let isEmbedded = embedLink(aNode, embeddableLinkTypes, container)
+ anyEmbed = anyEmbed || isEmbedded
+ })
+ return anyEmbed
}
/**
@@ -364,19 +364,19 @@ function embedLinks(aNodes, container) {
* @param {string} allLinksSelector
*/
export function embedLinksToX(x, beforeNodeSelector, allLinksSelector) {
- let allLinks = x.querySelectorAll(allLinksSelector);
+ let allLinks = x.querySelectorAll(allLinksSelector)
- let existingContainer = x.querySelector('div.embedContainer');
+ let existingContainer = x.querySelector('div.embedContainer')
if (existingContainer) {
- embedLinks(allLinks, existingContainer);
+ embedLinks(allLinks, existingContainer)
} else {
- let embedContainer = document.createElement('div');
- embedContainer.className = 'embedContainer';
+ let embedContainer = document.createElement('div')
+ embedContainer.className = 'embedContainer'
- let anyEmbed = embedLinks(allLinks, embedContainer);
+ let anyEmbed = embedLinks(allLinks, embedContainer)
if (anyEmbed) {
- let beforeNode = x.querySelector(beforeNodeSelector);
- x.insertBefore(embedContainer, beforeNode);
+ let beforeNode = x.querySelector(beforeNodeSelector)
+ x.insertBefore(embedContainer, beforeNode)
}
}
}
@@ -385,11 +385,11 @@ export function embedLinksToX(x, beforeNodeSelector, allLinksSelector) {
* Embed all the links in all messages/replies on the page.
*/
export function embedAll() {
- let beforeNodeSelector = '.msg-txt + *';
- let allLinksSelector = '.msg-txt a';
+ let beforeNodeSelector = '.msg-txt + *'
+ let allLinksSelector = '.msg-txt a'
Array.from(document.querySelectorAll('#content .msg-cont')).forEach(msg => {
- embedLinksToX(msg, beforeNodeSelector, allLinksSelector);
- });
+ embedLinksToX(msg, beforeNodeSelector, allLinksSelector)
+ })
}
/**
* Embed URLs to container
@@ -397,8 +397,8 @@ export function embedAll() {
* @param {HTMLDivElement} embedContainer
*/
export function embedUrls(urls, embedContainer) {
- embedLinks(urls, embedContainer);
+ embedLinks(urls, embedContainer)
}
-export const format = juickFormat;
+export const format = juickFormat
diff --git a/vnext/webpack.config.js b/vnext/webpack.config.js
index 65f2aad6..241cc431 100644
--- a/vnext/webpack.config.js
+++ b/vnext/webpack.config.js
@@ -1,13 +1,13 @@
/* eslint-disable @typescript-eslint/no-var-requires */
-const ESLintPlugin = require('eslint-webpack-plugin');
-const HtmlWebpackPlugin = require('html-webpack-plugin');
-const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-const TerserPlugin = require('terser-webpack-plugin');
+const ESLintPlugin = require('eslint-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const MiniCssExtractPlugin = require('mini-css-extract-plugin')
+const TerserPlugin = require('terser-webpack-plugin')
module.exports = () => {
- const node_env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development';
- const dev = node_env !== 'production';
+ const node_env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development'
+ const dev = node_env !== 'production'
const config = {
mode: node_env,
devtool: dev ? 'source-map' : false,
@@ -75,7 +75,7 @@ module.exports = () => {
symlinks: false,
extensions: ['.js']
}
- };
+ }
if (dev) {
config.plugins.push(
new ESLintPlugin({
@@ -84,14 +84,14 @@ module.exports = () => {
failOnWarning: false,
failOnError: true,
fix: false
- }));
+ }))
config.devServer = {
hot: true,
historyApiFallback: true,
client: {
overlay: true
}
- };
+ }
}
config.optimization = {
minimize: !dev,
@@ -103,6 +103,6 @@ module.exports = () => {
terserOptions: {},
}),
]
- };
- return config;
-};
+ }
+ return config
+}