From f2a7ea3af919548d41383734e8a3667086a44bcc Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 31 May 2023 06:10:51 +0300 Subject: eslint: enforce semicolons only before statement continuation chars --- vnext/src/App.js | 124 ++++++------- vnext/src/api/index.js | 144 +++++++-------- vnext/src/index.js | 50 +++--- vnext/src/ui/Avatar.js | 10 +- vnext/src/ui/Button.js | 6 +- vnext/src/ui/Chat.js | 70 ++++---- vnext/src/ui/Comment.js | 34 ++-- vnext/src/ui/Contact.js | 8 +- vnext/src/ui/Contacts.js | 24 +-- vnext/src/ui/Feeds.js | 118 ++++++------- vnext/src/ui/Header.js | 22 +-- vnext/src/ui/Icon.js | 28 +-- vnext/src/ui/Login.js | 48 ++--- vnext/src/ui/Message.js | 66 +++---- vnext/src/ui/MessageInput.js | 90 +++++----- vnext/src/ui/PM.js | 18 +- vnext/src/ui/Post.js | 52 +++--- vnext/src/ui/SearchBox.js | 12 +- vnext/src/ui/Settings.js | 100 +++++------ vnext/src/ui/Spinner.js | 10 +- vnext/src/ui/Thread.js | 130 +++++++------- vnext/src/ui/UploadButton.js | 20 +-- vnext/src/ui/UserInfo.js | 70 ++++---- vnext/src/ui/Users.js | 22 +-- vnext/src/ui/VisitorContext.js | 16 +- vnext/src/ui/__tests__/Avatar.test.js | 14 +- vnext/src/ui/__tests__/MessageInput.test.js | 48 ++--- vnext/src/ui/__tests__/UserLink.test.js | 18 +- vnext/src/ui/helpers/BubbleStyle.js | 8 +- vnext/src/utils/embed.js | 260 ++++++++++++++-------------- 30 files changed, 820 insertions(+), 820 deletions(-) (limited to 'vnext/src') 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 }) { - ); + ) } 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} 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 = () => ( @@ -38,11 +38,11 @@ const JuickApp = () => ( -); +) -let root = document.getElementById('app'); +let root = document.getElementById('app') if (window.__PROPS__) { - hydrateRoot(root, ); + hydrateRoot(root, ) } else { - createRoot(root).render(); + createRoot(root).render() } 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} - ); + ) } -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 & import('react').ButtonHTMLAttributes} props @@ -6,7 +6,7 @@ import { memo } from 'react'; function Button(props) { return ( ); + return () }) } } - ); + ) } 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 } */ let onSubmit = ( values ) => { - onSearch(values.search); - }; + onSearch(values.search) + } return (
- ); + ) } -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: '' }; + } + let previewUser = { ...visitor, uname: '' } if (preview) { - previewUser = { ...visitor, avatar: preview, uname: '' }; + previewUser = { ...visitor, avatar: preview, uname: '' } } 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 (
Recommendations: PNG, 96x96, <50Kb. Also, JPG and GIF supported. @@ -50,7 +50,7 @@ function ChangeAvatarForm({ onChange }) { - ); + ) } /** @@ -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 (
@@ -249,7 +249,7 @@ export default function Settings({ onChange }) {
- ); + ) } 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) { - ); + ) } -export default memo(Spinner); +export default memo(Spinner) /** * @@ -42,5 +42,5 @@ export function ChatSpinner(props) { - ); + ) } 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 ( <> @@ -155,5 +155,5 @@ export default function Thread(props) { } - ); + ) } 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} event */ let attachmentChanged = (event) => { - props.onChange(event.target.value); - }; + props.onChange(event.target.value) + } return (
@@ -35,14 +35,14 @@ export default function UploadButton(props) { style={{ display: 'none' }} ref={props.inputRef} value={props.value} onChange={attachmentChanged} />
- ); + ) } 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 ( <>
@@ -59,7 +59,7 @@ export default function UserInfo({ uname, onUpdate, children }) {
{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 && I read: {user.read.length}; - let readers = user.readers && My readers: {user.readers.length}; - let mybl = user.statsMyBL && My blacklist: {user.statsMyBL}; - 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 && I read: {user.read.length} + let readers = user.readers && My readers: {user.readers.length} + let mybl = user.statsMyBL && My blacklist: {user.statsMyBL} + let presentItems = [read, readers, mybl].filter(Boolean) return (
{presentItems.length > 0 && presentItems.reduce((prev, curr) => [prev, ' ', curr])}
- ); + ) } -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 ? @@ -125,5 +125,5 @@ export function UserLink(props) { :
{user.uname} - ); + ) } 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 ( <> @@ -17,14 +17,14 @@ export function Friends() { - ); + ) } /** * Readers feed */ export function Readers() { - const params = useParams(); + const params = useParams() return ( <> @@ -32,7 +32,7 @@ export function 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 (
@@ -52,5 +52,5 @@ function Users({ uname, prop }) { }
- ); + ) } 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 {children}; + const state = useState(unknownUser) + return {children} } /** @@ -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( - ); - 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( { return true; }} />); - expect(screen.getByText(draft)).toHaveFocus(); -}); + let draft = 'draft' + render( { 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(); - 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() + 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( @@ -14,8 +14,8 @@ test('UserLink renders correctly', async () => { - ); - }); - 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, '&') .replace(/"/g, '"') .replace(//g, '>'); + .replace(/>/g, '>') } 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) ? `${p1}` - : `${extractDomain(match)}`; + : `${extractDomain(match)}` } function urlReplaceInCode(match, p1, p2, p3) { - let isBrackets = (p1 !== undefined); + let isBrackets = (p1 !== undefined) return (isBrackets) ? `${match}` - : `${match}`; + : `${match}` } function messageReplyReplace(messageId) { return function(match, mid, rid) { - let replyPart = (rid && rid != '0') ? '#' + rid : ''; - return `${match}`; - }; + let replyPart = (rid && rid != '0') ? '#' + rid : '' + return `${match}` + } } /** @@ -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(/^(?:>|>)\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(/^(?:>|>)\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: ['', ''] }, { pr: 2, re: /\b_([^\n]+?)_((?=\s)|(?=$)|(?=[!"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['', ''] }, { pr: 3, re: /\n/g, with: '
' }, - ]); + ]) } /** * @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 = ``; - return div; + let url = aNode.href.endsWith('dl=0') ? aNode.href.replace('dl=0', 'raw=1') : aNode.href + div.innerHTML = `` + 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 = ``; - return div; + div.innerHTML = `` + 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 = ``; - return div; + div.innerHTML = `` + 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 = ``; - return div; + div.innerHTML = `` + 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 = ``; - return div; + let [, postId] = reResult + let mediaUrl = `https://instagr.am${postId}/media` + div.innerHTML = `` + 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 -- cgit v1.2.3