From 82bbd04cfb651ad54fc30a4e6f896b691f7bf915 Mon Sep 17 00:00:00 2001 From: alexsparkes Date: Mon, 10 Oct 2022 18:32:26 +0100 Subject: [PATCH] feat: overhaul of quick links --- .../modals/main/settings/sections/Message.jsx | 1 + .../main/settings/sections/QuickLinks.jsx | 352 +++++++++++++++--- .../widgets/quicklinks/QuickLinks.jsx | 30 +- .../widgets/quicklinks/quicklinks.scss | 63 ++++ src/components/widgets/quote/quote.scss | 1 + src/components/widgets/search/Search.jsx | 8 +- 6 files changed, 397 insertions(+), 58 deletions(-) diff --git a/src/components/modals/main/settings/sections/Message.jsx b/src/components/modals/main/settings/sections/Message.jsx index f4a9e32a..fda8e83c 100644 --- a/src/components/modals/main/settings/sections/Message.jsx +++ b/src/components/modals/main/settings/sections/Message.jsx @@ -105,6 +105,7 @@ export default class Message extends PureComponent { className="deleteButton" onClick={() => this.modifyMessage('remove', index)} > + Remove diff --git a/src/components/modals/main/settings/sections/QuickLinks.jsx b/src/components/modals/main/settings/sections/QuickLinks.jsx index 60d26df4..680f2e61 100644 --- a/src/components/modals/main/settings/sections/QuickLinks.jsx +++ b/src/components/modals/main/settings/sections/QuickLinks.jsx @@ -1,53 +1,319 @@ import variables from 'modules/variables'; -import { useState } from 'react'; - +import { PureComponent, createRef } from 'react'; +import { TextareaAutosize } from '@mui/material'; +import { MdAddLink, MdLinkOff, MdClose, MdCancel, MdEdit } from 'react-icons/md'; import Header from '../Header'; import Checkbox from '../Checkbox'; +import Dropdown from '../Dropdown'; +import Modal from 'react-modal'; import SettingsItem from '../SettingsItem'; -export default function QuickLinks() { - const [textOnly, setTextOnly] = useState(localStorage.getItem('quicklinksText') === 'true'); +import Tooltip from 'components/helpers/tooltip/Tooltip'; - return ( - <> -
- - i.key !== key); + + localStorage.setItem('quicklinks', JSON.stringify(data)); + this.setState({ + items: data, + }); + + variables.stats.postEvent('feature', 'Quicklink delete'); + } + + addLink = () => { + const data = JSON.parse(localStorage.getItem('quicklinks')); + let url = this.state.url; + let urlError; + + // regex: https://ihateregex.io/expr/url/ + // eslint-disable-next-line no-useless-escape + if ( + url.length <= 0 || + /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_.~#?&=]*)/.test( + url, + ) === false + ) { + urlError = variables.getMessage('widgets.quicklinks.url_error'); + } + + if (urlError) { + return this.setState({ + urlError, + }); + } + + if (!url.startsWith('http://') && !url.startsWith('https://')) { + url = 'http://' + url; + } + + data.push({ + name: this.state.name || url, + url, + icon: this.state.icon || '', + key: Math.random().toString(36).substring(7) + 1, + }); + + localStorage.setItem('quicklinks', JSON.stringify(data)); + + this.setState({ + items: data, + name: '', + url: '', + }); + + variables.stats.postEvent('feature', 'Quicklink add'); + + this.toggleAdd(); + + // make sure image is correct size + this.setZoom(this.quicklinksContainer.current); + }; + + toggleAdd = () => { + this.setState({ + showAddModal: this.state.showAddLink === 'false' ? 'true' : 'false', + }); + }; + + // widget zoom + setZoom(element) { + const zoom = localStorage.getItem('zoomQuicklinks') || 100; + if (localStorage.getItem('quicklinksText')) { + for (const link of element.getElementsByTagName('a')) { + link.style.fontSize = `${1.4 * Number(zoom / 100)}em`; + } + } else { + for (const img of element.getElementsByTagName('img')) { + img.style.height = `${1.4 * Number(zoom / 100)}em`; + } + } + } + + componentDidMount() { + EventBus.on('refresh', (data) => { + if (data === 'quicklinks') { + if (localStorage.getItem('quicklinksenabled') === 'false') { + return (this.quicklinksContainer.current.style.display = 'none'); + } + + this.quicklinksContainer.current.style.display = 'block'; + this.setZoom(this.quicklinksContainer.current); + + this.setState({ + items: JSON.parse(localStorage.getItem('quicklinks')), + }); + } + }); + + this.setZoom(this.quicklinksContainer.current); + } + + // allows you to add a link by pressing enter + topbarEnter = (e) => { + e = e || window.event; + const code = e.which || e.keyCode; + if (code === 13 && this.state.showAddLink === 'visible') { + this.addLink(); + e.preventDefault(); + } + }; + + componentWillUnmount() { + EventBus.off('refresh'); + } + + render() { + let target, + rel = null; + if (localStorage.getItem('quicklinksnewtab') === 'true') { + target = '_blank'; + rel = 'noopener noreferrer'; + } + + const tooltipEnabled = localStorage.getItem('quicklinkstooltip'); + const useProxy = localStorage.getItem('quicklinksddgProxy') !== 'false'; + const useText = localStorage.getItem('quicklinksText') === 'true'; + + const quickLink = (item) => { + if (useText) { + return ( + this.deleteLink(item.key, e)} + href={item.url} + target={target} + rel={rel} + draggable={false} + > + {item.name} + + ); + } + + const url = useProxy + ? 'https://icons.duckduckgo.com/ip2/' + : 'https://www.google.com/s2/favicons?sz=32&domain='; + const img = + item.icon || + url + item.url.replace('https://', '').replace('http://', '') + (useProxy ? '.ico' : ''); + + const link = ( +
+
+ {item.name} +
+
+
{item.name}
+
{item.url}
+
+
+
+ + +
+
+
+ ); + return link; + }; + + return ( + <> +
setTextOnly(value)} + element=".quicklinks-container" + zoomSetting="zoomQuicklinks" + switch={true} /> - - - - - - ); + + + + + + + + + + + + + + + + + + {this.state.items.length === 0 ? ( +
+
+ + No quicklinks + + {variables.getMessage('modals.main.settings.sections.message.add_some')} + + +
+
+ ) : null} + +
+ {this.state.items.map((item) => quickLink(item))} +
+ this.setState({ showAddModal: false })} + isOpen={this.state.showAddModal} + className="Modal resetmodal mainModal" + overlayClassName="Overlay resetoverlay" + ariaHideApp={false} + > +
+
+ {variables.getMessage('widgets.quicklinks.new')} + +
this.setState({ showAddModal: false })}> + +
+
+
+
+ this.setState({ name: e.target.value })} + /> + + this.setState({ url: e.target.value })} + /> + {this.state.urlError} + this.setState({ icon: e.target.value })} + /> + + +
+
+
+ + ); + } } diff --git a/src/components/widgets/quicklinks/QuickLinks.jsx b/src/components/widgets/quicklinks/QuickLinks.jsx index e3cbbb5e..2e78448b 100644 --- a/src/components/widgets/quicklinks/QuickLinks.jsx +++ b/src/components/widgets/quicklinks/QuickLinks.jsx @@ -101,7 +101,7 @@ export default class QuickLinks extends PureComponent { } } else { for (const img of element.getElementsByTagName('img')) { - img.style.height = `${1.4 * Number(zoom / 100)}em`; + img.style.height = `${30 * Number(zoom / 100)}px`; } } } @@ -149,15 +149,13 @@ export default class QuickLinks extends PureComponent { const tooltipEnabled = localStorage.getItem('quicklinkstooltip'); const useProxy = localStorage.getItem('quicklinksddgProxy') !== 'false'; - const useText = localStorage.getItem('quicklinksText') === 'true'; const quickLink = (item) => { - if (useText) { + if (localStorage.getItem('quickLinksStyle') === 'text') { return ( this.deleteLink(item.key, e)} href={item.url} target={target} rel={rel} @@ -175,10 +173,25 @@ export default class QuickLinks extends PureComponent { item.icon || url + item.url.replace('https://', '').replace('http://', '') + (useProxy ? '.ico' : ''); + if (localStorage.getItem('quickLinksStyle') === 'metro') { + return ( + + {item.name} + {item.name} + + ); + } + const link = ( this.deleteLink(item.key, e)} href={item.url} target={target} rel={rel} @@ -189,7 +202,7 @@ export default class QuickLinks extends PureComponent { ); return tooltipEnabled === 'true' ? ( - + {link} ) : ( @@ -202,11 +215,6 @@ export default class QuickLinks extends PureComponent {
{this.state.items.map((item) => quickLink(item))}
-
- -
) : ( @@ -223,8 +223,8 @@ export default class Search extends PureComponent {
- {localStorage.getItem('searchDropdown') === 'true' ? ( -
+ {localStorage.getItem('searchDropdown') === 'true' && this.state.searchDropdown === true ? ( +
{searchEngines.map(({ name }) => { if (name === this.state.currentSearch) { return null;