Cleaner code with prettier.

This commit is contained in:
Daniel 2022-06-16 13:43:44 +04:30
parent e2db7e71ac
commit 10d3a05c1d
32 changed files with 1021 additions and 754 deletions

View File

@ -4,7 +4,6 @@ LinkWarden
<sub>A place for your useful links.</sub> <sub>A place for your useful links.</sub>
<img src="assets/LinkWarden.png" alt="LinkWarden.png" width="500px"/> <img src="assets/LinkWarden.png" alt="LinkWarden.png" width="500px"/>
<a href="https://twitter.com/Daniel31X13" target="_blank" rel="noopener noreferrer"><img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/Daniel31X13?label=twitter&amp;style=social"></a> <a href="https://twitter.com/Daniel31X13" target="_blank" rel="noopener noreferrer"><img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/Daniel31X13?label=twitter&amp;style=social"></a>
@ -13,16 +12,18 @@ LinkWarden
</h1> </h1>
[Demo](https://linkwarden.netlify.app/) | [Intro & Motivation](https://github.com/Daniel31x13/link-warden#intro--motivation) | [Features](https://github.com/Daniel31x13/link-warden#features) | [Setup](https://github.com/Daniel31x13/link-warden#setup) | [Development](https://github.com/Daniel31x13/link-warden#linkwarden-development) [Demo](https://linkwarden.netlify.app/) | [Intro & Motivation](https://github.com/Daniel31x13/link-warden#intro--motivation) | [Features](https://github.com/Daniel31x13/link-warden#features) | [Roadmap](https://github.com/Daniel31x13/link-warden/wiki#project-roadmap) | [Setup](https://github.com/Daniel31x13/link-warden#setup) | [Development](https://github.com/Daniel31x13/link-warden#linkwarden-development)
</div> </div>
## Intro & Motivation ## Intro & Motivation
**LinkWarden is a self-hosted, open-source bookmark + archive manager to collect, and save websites for offline use.** **LinkWarden is a self-hosted, open-source bookmark + archive manager to collect, and save websites for offline use.**
The objective is to have a self-hosted place to keep useful links in one place, and since useful links can go away (see the inevitability of [Link Rot](https://www.howtogeek.com/786227/what-is-link-rot-and-how-does-it-threaten-the-web/)), LinkWarden also saves a copy of the link as screenshot and PDF. The objective is to have a self-hosted place to keep useful links in one place, and since useful links can go away (see the inevitability of [Link Rot](https://www.howtogeek.com/786227/what-is-link-rot-and-how-does-it-threaten-the-web/)), LinkWarden also saves a copy of the link as screenshot and PDF.
## Features ## Features
- [x] Sleek, minimalist design. - [x] Sleek, minimalist design.
- [x] Save a copy of each link as screenshot and PDF. - [x] Save a copy of each link as screenshot and PDF.
- [x] Dark/Light mode support. - [x] Dark/Light mode support.
@ -34,7 +35,9 @@ The objective is to have a self-hosted place to keep useful links in one place,
**Also take a look at our planned features in the [project roadmap section](https://github.com/Daniel31x13/link-warden/wiki#project-roadmap).** **Also take a look at our planned features in the [project roadmap section](https://github.com/Daniel31x13/link-warden/wiki#project-roadmap).**
## Setup ## Setup
### Linux/MacOS ### Linux/MacOS
1. Make sure your MongoDB database and collection is up and running. 1. Make sure your MongoDB database and collection is up and running.
2. Edit [/src/config.js](src/config.js) accordingly. 2. Edit [/src/config.js](src/config.js) accordingly.
@ -44,6 +47,7 @@ The objective is to have a self-hosted place to keep useful links in one place,
4. Run `npm start` to start the application. 4. Run `npm start` to start the application.
## LinkWarden Development ## LinkWarden Development
All contributions are welcomed! Please take a look at [how to contribute](.github/CONTRIBUTING.md). All contributions are welcomed! Please take a look at [how to contribute](.github/CONTRIBUTING.md).
> **For questions/help, feature requests and bug reports please create an [issue](https://github.com/Daniel31x13/link-warden/issues) (please use the right lable).** > **For questions/help, feature requests and bug reports please create an [issue](https://github.com/Daniel31x13/link-warden/issues) (please use the right lable).**

View File

@ -1,17 +1,18 @@
const puppeteer = require('puppeteer'); const puppeteer = require("puppeteer");
const { PuppeteerBlocker } = require('@cliqz/adblocker-puppeteer'); const { PuppeteerBlocker } = require("@cliqz/adblocker-puppeteer");
const fetch = require('cross-fetch'); const fetch = require("cross-fetch");
const config = require('../../src/config.js'); const config = require("../../src/config.js");
const fs = require('fs'); const fs = require("fs");
const screenshotDirectory = config.API.STORAGE_LOCATION + '/LinkWarden/screenshot\'s/'; const screenshotDirectory =
const pdfDirectory = config.API.STORAGE_LOCATION + '/LinkWarden/pdf\'s/'; config.API.STORAGE_LOCATION + "/LinkWarden/screenshot's/";
const pdfDirectory = config.API.STORAGE_LOCATION + "/LinkWarden/pdf's/";
if (!fs.existsSync(screenshotDirectory)){ if (!fs.existsSync(screenshotDirectory)) {
fs.mkdirSync(screenshotDirectory, { recursive: true }); fs.mkdirSync(screenshotDirectory, { recursive: true });
} }
if (!fs.existsSync(pdfDirectory)){ if (!fs.existsSync(pdfDirectory)) {
fs.mkdirSync(pdfDirectory, { recursive: true }); fs.mkdirSync(pdfDirectory, { recursive: true });
} }
@ -22,11 +23,14 @@ module.exports = async (link, id) => {
await PuppeteerBlocker.fromPrebuiltAdsAndTracking(fetch).then((blocker) => { await PuppeteerBlocker.fromPrebuiltAdsAndTracking(fetch).then((blocker) => {
blocker.enableBlockingInPage(page); blocker.enableBlockingInPage(page);
}); });
await page.goto(link, { waitUntil: 'load', timeout: 0 });
await page.screenshot({ path: screenshotDirectory + id + '.png', fullPage: true}); await page.goto(link, { waitUntil: "load", timeout: 0 });
await page.pdf({ path: pdfDirectory + id + '.pdf', format: 'a4' });
await page.screenshot({
path: screenshotDirectory + id + ".png",
fullPage: true,
});
await page.pdf({ path: pdfDirectory + id + ".pdf", format: "a4" });
await browser.close(); await browser.close();
} };

View File

@ -1,15 +1,20 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title> <title>Document</title>
</head> </head>
<body style="text-align: center; background-color: rgb(0, 51, 78); color:white;"> <body
style="text-align: center; background-color: rgb(0, 51, 78); color: white"
>
<h1>404: NOT FOUND</h1> <h1>404: NOT FOUND</h1>
<h3>If you are trying to access a recently added Screenshot/PDF, just wait a bit more for it to be uploaded.</h3> <h3>
If you are trying to access a recently added Screenshot/PDF, just wait a
bit more for it to be uploaded.
</h3>
<h3>If the problem persists, looks like the file wasn't created...</h3> <h3>If the problem persists, looks like the file wasn't created...</h3>
<h1>¯\_(ツ)_/¯</h1> <h1>¯\_(ツ)_/¯</h1>
</body> </body>
</html> </html>

View File

@ -1,11 +1,11 @@
const express = require('express'); const express = require("express");
const app = express(); const app = express();
const { MongoClient } = require('mongodb'); const { MongoClient } = require("mongodb");
const cors = require('cors'); const cors = require("cors");
const config = require('../src/config.js'); const config = require("../src/config.js");
const getData = require('./modules/getData.js'); const getData = require("./modules/getData.js");
const fs = require('fs'); const fs = require("fs");
const fetch = require('cross-fetch'); const fetch = require("cross-fetch");
const port = config.API.PORT; const port = config.API.PORT;
@ -21,39 +21,45 @@ app.use(cors());
app.use(express.json()); app.use(express.json());
app.get('/api', async (req, res) => { app.get("/api", async (req, res) => {
const data = await getDoc(); const data = await getDoc();
res.send(data); res.send(data);
}); });
app.get('/screenshots/:id', async (req, res) => { app.get("/screenshots/:id", async (req, res) => {
res.sendFile(config.API.STORAGE_LOCATION + '/LinkWarden/screenshot\'s/' + req.params.id, (err) => { res.sendFile(
if (err) { config.API.STORAGE_LOCATION + "/LinkWarden/screenshot's/" + req.params.id,
res.sendFile(__dirname +'/pages/404.html'); (err) => {
if (err) {
res.sendFile(__dirname + "/pages/404.html");
}
} }
}); );
}); });
app.get('/pdfs/:id', async (req, res) => { app.get("/pdfs/:id", async (req, res) => {
res.sendFile(config.API.STORAGE_LOCATION + '/LinkWarden/pdf\'s/' + req.params.id, (err) => { res.sendFile(
if (err) { config.API.STORAGE_LOCATION + "/LinkWarden/pdf's/" + req.params.id,
res.sendFile(__dirname +'/pages/404.html'); (err) => {
if (err) {
res.sendFile(__dirname + "/pages/404.html");
}
} }
}); );
}); });
app.post('/api', async (req, res) => { app.post("/api", async (req, res) => {
const pageToVisit = req.body.link; const pageToVisit = req.body.link;
const id = req.body._id; const id = req.body._id;
const getTitle = async(url) => { const getTitle = async (url) => {
let body; let body;
await fetch(url) await fetch(url)
.then(res => res.text()) .then((res) => res.text())
.then(text => body = text) .then((text) => (body = text));
// regular expression to parse contents of the <title> tag // regular expression to parse contents of the <title> tag
let match = body.match(/<title>([^<]*)<\/title>/); let match = body.match(/<title>([^<]*)<\/title>/);
return match[1]; return match[1];
} };
try { try {
req.body.title = await getTitle(req.body.link); req.body.title = await getTitle(req.body.link);
@ -67,15 +73,15 @@ app.post('/api', async (req, res) => {
} }
}); });
app.put('/api', async (req, res) => { app.put("/api", async (req, res) => {
const id = req.body._id; const id = req.body._id;
await updateDoc(id, req.body); await updateDoc(id, req.body);
res.send('Updated!'); res.send("Updated!");
}); });
app.delete('/api', async (req, res) => { app.delete("/api", async (req, res) => {
const id = req.body.id; const id = req.body.id;
await deleteDoc(id); await deleteDoc(id);
@ -85,10 +91,8 @@ app.delete('/api', async (req, res) => {
async function updateDoc(id, updatedListing) { async function updateDoc(id, updatedListing) {
try { try {
await list.updateOne({ _id: id }, { $set: updatedListing }); await list.updateOne({ _id: id }, { $set: updatedListing });
} } catch (err) {
catch(err) {
console.log(err); console.log(err);
} }
} }
@ -96,9 +100,7 @@ async function updateDoc(id, updatedListing) {
async function insertDoc(doc) { async function insertDoc(doc) {
try { try {
await list.insertOne(doc); await list.insertOne(doc);
} } catch (err) {
catch(err) {
console.log(err); console.log(err);
} }
} }
@ -108,34 +110,35 @@ async function getDoc() {
const result = await list.find({}).toArray(); const result = await list.find({}).toArray();
return result; return result;
} } catch (err) {
catch(err) {
console.log(err); console.log(err);
} }
} }
async function deleteDoc(doc) { async function deleteDoc(doc) {
try { try {
const result = await list.deleteOne({"_id": doc}); const result = await list.deleteOne({ _id: doc });
fs.unlink(config.API.STORAGE_LOCATION + '/LinkWarden/screenshot\'s/' + doc + '.png', (err) => { fs.unlink(
if (err) { config.API.STORAGE_LOCATION + "/LinkWarden/screenshot's/" + doc + ".png",
console.log(err); (err) => {
} if (err) {
});
fs.unlink(config.API.STORAGE_LOCATION + '/LinkWarden/pdf\'s/' + doc + '.pdf', (err) => {
if (err) {
console.log(err); console.log(err);
}
} }
}); );
fs.unlink(
config.API.STORAGE_LOCATION + "/LinkWarden/pdf's/" + doc + ".pdf",
(err) => {
if (err) {
console.log(err);
}
}
);
return result; return result;
} } catch (err) {
catch(err) {
console.log(err); console.log(err);
} }
} }

View File

@ -1,17 +1,17 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import './styles/App.css'; import "./styles/App.css";
import List from './componets/List'; import List from "./componets/List";
import AddItem from './componets/AddItem'; import AddItem from "./componets/AddItem";
import config from './config'; import config from "./config";
import Filters from './componets/Filters'; import Filters from "./componets/Filters";
import Sort from './componets/Sort'; import Sort from "./componets/Sort";
import sortList from './modules/sortList'; import sortList from "./modules/sortList";
import filter from './modules/filterData'; import filter from "./modules/filterData";
import concatTags from './modules/concatTags'; import concatTags from "./modules/concatTags";
import NoResults from './componets/NoResults'; import NoResults from "./componets/NoResults";
import Loader from './componets/Loader'; import Loader from "./componets/Loader";
import SideBar from './componets/SideBar'; import SideBar from "./componets/SideBar";
import Tags from './routes/Tags.js'; import Tags from "./routes/Tags.js";
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
function App() { function App() {
@ -19,14 +19,16 @@ function App() {
[newBox, setNewBox] = useState(false), [newBox, setNewBox] = useState(false),
[filterBox, setFilterBox] = useState(false), [filterBox, setFilterBox] = useState(false),
[sortBox, setSortBox] = useState(false), [sortBox, setSortBox] = useState(false),
[searchQuery, setSearchQuery] = useState(''), [searchQuery, setSearchQuery] = useState(""),
[numberOfResults, setNumberOfResults] = useState(0), [numberOfResults, setNumberOfResults] = useState(0),
[nameChecked, setNameChecked] = useState(true), [nameChecked, setNameChecked] = useState(true),
[descriptionChecked, setDescriptionChecked] = useState(true), [descriptionChecked, setDescriptionChecked] = useState(true),
[tagsChecked, setTagsChecked] = useState(true), [tagsChecked, setTagsChecked] = useState(true),
[sortBy, setSortBy] = useState('Default'), [sortBy, setSortBy] = useState("Default"),
[loader, setLoader] = useState(false), [loader, setLoader] = useState(false),
[lightMode, setLightMode] = useState(localStorage.getItem('light-mode') === 'true'), [lightMode, setLightMode] = useState(
localStorage.getItem("light-mode") === "true"
),
[toggle, setToggle] = useState(false); [toggle, setToggle] = useState(false);
function SetLoader(x) { function SetLoader(x) {
@ -66,16 +68,22 @@ function App() {
} }
function handleToggleSidebar() { function handleToggleSidebar() {
setToggle(!toggle) setToggle(!toggle);
} }
const filteredData = filter(data, searchQuery, nameChecked, tagsChecked, descriptionChecked); const filteredData = filter(
data,
searchQuery,
nameChecked,
tagsChecked,
descriptionChecked
);
const tags = concatTags(data); const tags = concatTags(data);
async function fetchData() { async function fetchData() {
const ADDRESS = config.API.ADDRESS + ":" + config.API.PORT; const ADDRESS = config.API.ADDRESS + ":" + config.API.PORT;
const res = await fetch(ADDRESS + '/api'); const res = await fetch(ADDRESS + "/api");
const resJSON = await res.json(); const resJSON = await res.json();
const data = resJSON.reverse(); const data = resJSON.reverse();
setData(data); setData(data);
@ -104,73 +112,117 @@ function App() {
document.body.classList.remove("light"); document.body.classList.remove("light");
} }
localStorage.setItem('light-mode', lightMode); localStorage.setItem("light-mode", lightMode);
}, [lightMode]); }, [lightMode]);
return ( return (
<div className="App"> <div className="App">
<SideBar tags={tags} handleToggleSidebar={handleToggleSidebar} toggle={toggle} /> <SideBar
tags={tags}
handleToggleSidebar={handleToggleSidebar}
toggle={toggle}
/>
<div className='content'> <div className="content">
<div className="head"> <div className="head">
<button className='sidebar-btn btn' style={{marginRight: '10px'}} onClick={handleToggleSidebar}>&#xf0c9;</button> <button
<input className="search" type="search" placeholder="&#xf002; Search" onChange={search}/> className="sidebar-btn btn"
<button className="add-btn btn" onClick={() => setNewBox(true)}>&#xf067;</button> style={{ marginRight: "10px" }}
<button className="dark-light-btn btn" onClick={() => setLightMode(!lightMode)}></button> onClick={handleToggleSidebar}
>
&#xf0c9;
</button>
<input
className="search"
type="search"
placeholder="&#xf002; Search"
onChange={search}
/>
<button className="add-btn btn" onClick={() => setNewBox(true)}>
&#xf067;
</button>
<button
className="dark-light-btn btn"
onClick={() => setLightMode(!lightMode)}
></button>
</div> </div>
{numberOfResults > 0 ? <p className="results">{numberOfResults} Bookmarks found</p> : null} {numberOfResults > 0 ? (
<p className="results">{numberOfResults} Bookmarks found</p>
) : null}
<button
className="btn"
style={{ marginTop: "10px" }}
onClick={() => setFilterBox(true)}
>
&#xf0b0;
</button>
<button
className="btn"
style={{ marginLeft: "10px" }}
onClick={() => setSortBox(true)}
>
&#xf0dc;
</button>
<button className='btn' style={{marginTop: '10px'}} onClick={() => setFilterBox(true)}>&#xf0b0;</button>
<button className='btn' style={{marginLeft: '10px'}} onClick={() => setSortBox(true)}>&#xf0dc;</button>
{numberOfResults === 0 ? <NoResults /> : null} {numberOfResults === 0 ? <NoResults /> : null}
{sortBox ? <Sort {sortBox ? <Sort sortBy={sortByFunc} onExit={exitSorting} /> : null}
sortBy={sortByFunc}
onExit={exitSorting}
/> : null}
{filterBox ? <Filters {filterBox ? (
nameChecked={nameChecked} <Filters
handleNameCheckbox={handleNameCheckbox} nameChecked={nameChecked}
descriptionChecked={descriptionChecked} handleNameCheckbox={handleNameCheckbox}
handleDescriptionCheckbox={handleDescriptionCheckbox} descriptionChecked={descriptionChecked}
tagsChecked={tagsChecked} handleDescriptionCheckbox={handleDescriptionCheckbox}
handleTagsCheckbox={handleTagsCheckbox} tagsChecked={tagsChecked}
onExit={exitFilter} handleTagsCheckbox={handleTagsCheckbox}
/> : null} onExit={exitFilter}
/>
) : null}
{newBox ? <AddItem {newBox ? (
SetLoader={SetLoader} <AddItem
onExit={exitAdding} SetLoader={SetLoader}
reFetch={fetchData} onExit={exitAdding}
lightMode={lightMode} reFetch={fetchData}
tags={() => tags} lightMode={lightMode}
/> : null} tags={() => tags}
/>
) : null}
{loader ? <Loader lightMode={lightMode} /> : null} {loader ? <Loader lightMode={lightMode} /> : null}
</div> </div>
<Routes> <Routes>
<Route path="/" element={<div className='content'> <Route
<List path="/"
lightMode={lightMode} element={
SetLoader={SetLoader} <div className="content">
data={filteredData} <List
tags={tags} lightMode={lightMode}
reFetch={fetchData} SetLoader={SetLoader}
/> data={filteredData}
</div>} /> tags={tags}
reFetch={fetchData}
/>
</div>
}
/>
<Route path="tags/:tagId" element={<Tags <Route
lightMode={lightMode} path="tags/:tagId"
SetLoader={SetLoader} element={
data={filteredData} <Tags
tags={tags} lightMode={lightMode}
reFetch={fetchData} SetLoader={SetLoader}
/>} /> data={filteredData}
tags={tags}
reFetch={fetchData}
/>
}
/>
</Routes> </Routes>
</div> </div>
); );

View File

@ -1,15 +1,15 @@
import { useState } from 'react'; import { useState } from "react";
import '../styles/SendItem.css'; import "../styles/SendItem.css";
import TagSelection from './TagSelection'; import TagSelection from "./TagSelection";
import addItem from '../modules/send'; import addItem from "../modules/send";
const AddItem = ({onExit, reFetch, tags, SetLoader, lightMode}) => { const AddItem = ({ onExit, reFetch, tags, SetLoader, lightMode }) => {
const [name, setName] = useState(''); const [name, setName] = useState("");
const [link, setLink] = useState(''); const [link, setLink] = useState("");
const [tag, setTag] = useState([]); const [tag, setTag] = useState([]);
function newItem() { function newItem() {
SetLoader(true) SetLoader(true);
addItem(name, link, tag, reFetch, onExit, SetLoader, "POST"); addItem(name, link, tag, reFetch, onExit, SetLoader, "POST");
} }
@ -23,7 +23,7 @@ const AddItem = ({onExit, reFetch, tags, SetLoader, lightMode}) => {
function SetTags(value) { function SetTags(value) {
setTag(value); setTag(value);
setTag(value.map(e => e.value.toLowerCase())); setTag(value.map((e) => e.value.toLowerCase()));
} }
function abort(e) { function abort(e) {
@ -34,23 +34,41 @@ const AddItem = ({onExit, reFetch, tags, SetLoader, lightMode}) => {
return ( return (
<> <>
<div className='add-overlay' onClick={abort}></div> <div className="add-overlay" onClick={abort}></div>
<div className='send-box'> <div className="send-box">
<fieldset className='box'> <fieldset className="box">
<legend>New bookmark</legend> <legend>New bookmark</legend>
<div className='AddItem-content'> <div className="AddItem-content">
<h3><span style={{color:"red"}}>* </span>Link:</h3> <h3>
<input onChange={SetLink} className="AddItem-input" type="search" placeholder="e.g. https://example.com/"/> <span style={{ color: "red" }}>* </span>Link:
<h3>Name: <span className='optional'>(Optional)</span></h3> </h3>
<input onChange={SetName} className="AddItem-input" type="search" placeholder="e.g. Example Tutorial"/> <input
<h3>Tags: <span className='optional'>(Optional)</span></h3> onChange={SetLink}
className="AddItem-input"
type="search"
placeholder="e.g. https://example.com/"
/>
<h3>
Name: <span className="optional">(Optional)</span>
</h3>
<input
onChange={SetName}
className="AddItem-input"
type="search"
placeholder="e.g. Example Tutorial"
/>
<h3>
Tags: <span className="optional">(Optional)</span>
</h3>
<TagSelection setTags={SetTags} tags={tags} lightMode={lightMode} /> <TagSelection setTags={SetTags} tags={tags} lightMode={lightMode} />
<button onClick={newItem} className="send-btn">Add &#xf067;</button> <button onClick={newItem} className="send-btn">
Add &#xf067;
</button>
</div> </div>
</fieldset> </fieldset>
</div> </div>
</> </>
) );
} };
export default AddItem; export default AddItem;

View File

@ -1,16 +1,26 @@
import { useState } from 'react'; import { useState } from "react";
import deleteEntity from '../modules/deleteEntity'; import deleteEntity from "../modules/deleteEntity";
import '../styles/SendItem.css'; import "../styles/SendItem.css";
import TagSelection from './TagSelection'; import TagSelection from "./TagSelection";
import editItem from '../modules/send'; import editItem from "../modules/send";
const EditItem = ({tags, item, onExit, SetLoader, reFetch, lightMode }) => { const EditItem = ({ tags, item, onExit, SetLoader, reFetch, lightMode }) => {
const [name, setName] = useState(item.name); const [name, setName] = useState(item.name);
const [tag, setTag] = useState(item.tag); const [tag, setTag] = useState(item.tag);
function EditItem() { function EditItem() {
SetLoader(true); SetLoader(true);
editItem(name, item.link, tag, reFetch, onExit, SetLoader, "PUT", item._id, item.title); editItem(
name,
item.link,
tag,
reFetch,
onExit,
SetLoader,
"PUT",
item._id,
item.title
);
} }
function deleteItem() { function deleteItem() {
@ -24,7 +34,7 @@ const EditItem = ({tags, item, onExit, SetLoader, reFetch, lightMode }) => {
function SetTags(value) { function SetTags(value) {
setTag(value); setTag(value);
setTag(value.map(e => e.value.toLowerCase())); setTag(value.map((e) => e.value.toLowerCase()));
} }
function abort(e) { function abort(e) {
@ -37,25 +47,56 @@ const EditItem = ({tags, item, onExit, SetLoader, reFetch, lightMode }) => {
return ( return (
<> <>
<div className='add-overlay' onClick={abort}></div> <div className="add-overlay" onClick={abort}></div>
<div className='send-box'> <div className="send-box">
<fieldset className='box'> <fieldset className="box">
<legend >Edit bookmark</legend> <legend>Edit bookmark</legend>
<button className="delete" onClick={deleteItem}>&#xf2ed;</button> <button className="delete" onClick={deleteItem}>
<div className='AddItem-content'> &#xf2ed;
<h3>Link: <a className='link' target="_blank" rel="noreferrer" href={item.link}>{url.hostname}</a></h3> </button>
<h3 className='title'><b>{item.title}</b></h3> <div className="AddItem-content">
<h3>
<h3>Name: <span className='optional'>(Optional)</span></h3> Link:{" "}
<input onChange={SetName} className="AddItem-input" type="search" value={name} placeholder={"e.g. Example Tutorial"} /> <a
<h3>Tags: <span className='optional'>(Optional)</span></h3> className="link"
<TagSelection setTags={SetTags} tags={tags} tag={tag} lightMode={lightMode} /> target="_blank"
<button onClick={EditItem} className="send-btn">Update &#xf303;</button> rel="noreferrer"
href={item.link}
>
{url.hostname}
</a>
</h3>
<h3 className="title">
<b>{item.title}</b>
</h3>
<h3>
Name: <span className="optional">(Optional)</span>
</h3>
<input
onChange={SetName}
className="AddItem-input"
type="search"
value={name}
placeholder={"e.g. Example Tutorial"}
/>
<h3>
Tags: <span className="optional">(Optional)</span>
</h3>
<TagSelection
setTags={SetTags}
tags={tags}
tag={tag}
lightMode={lightMode}
/>
<button onClick={EditItem} className="send-btn">
Update &#xf303;
</button>
</div> </div>
</fieldset> </fieldset>
</div> </div>
</> </>
) );
} };
export default EditItem export default EditItem;

View File

@ -1,25 +1,54 @@
import '../styles/Filters.css' import "../styles/Filters.css";
const Filters = ({nameChecked, handleNameCheckbox, descriptionChecked, handleDescriptionCheckbox, tagsChecked, handleTagsCheckbox, onExit}) => { const Filters = ({
function abort(e) { nameChecked,
if (e.target.className === "filter-overlay") { handleNameCheckbox,
onExit(); descriptionChecked,
} handleDescriptionCheckbox,
tagsChecked,
handleTagsCheckbox,
onExit,
}) => {
function abort(e) {
if (e.target.className === "filter-overlay") {
onExit();
} }
}
return ( return (
<> <>
<div className='filter-overlay' onClick={abort}></div> <div className="filter-overlay" onClick={abort}></div>
<div className='filter-box'> <div className="filter-box">
<fieldset className='filter'> <fieldset className="filter">
<legend >Filter by</legend> <legend>Filter by</legend>
<label><input type="checkbox" checked={nameChecked} onChange={handleNameCheckbox} />Name</label> <label>
<label><input type="checkbox" checked={descriptionChecked} onChange={handleDescriptionCheckbox} />Website title</label> <input
<label><input type="checkbox" checked={tagsChecked} onChange={handleTagsCheckbox} />Tags</label> type="checkbox"
</fieldset> checked={nameChecked}
</div> onChange={handleNameCheckbox}
/>
Name
</label>
<label>
<input
type="checkbox"
checked={descriptionChecked}
onChange={handleDescriptionCheckbox}
/>
Website title
</label>
<label>
<input
type="checkbox"
checked={tagsChecked}
onChange={handleTagsCheckbox}
/>
Tags
</label>
</fieldset>
</div>
</> </>
) );
} };
export default Filters export default Filters;

View File

@ -1,77 +1,90 @@
import '../styles/List.css'; import "../styles/List.css";
import LazyLoad from 'react-lazyload'; import LazyLoad from "react-lazyload";
import ViewArchived from './ViewArchived'; import ViewArchived from "./ViewArchived";
import EditItem from './EditItem'; import EditItem from "./EditItem";
import { useState } from 'react' import { useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
const List = ({data, tags, reFetch, SetLoader, lightMode}) => { const List = ({ data, tags, reFetch, SetLoader, lightMode }) => {
const [editBox, setEditBox] = useState(false) const [editBox, setEditBox] = useState(false);
const [editIndex, setEditIndex] = useState(0) const [editIndex, setEditIndex] = useState(0);
function edit(index) { function edit(index) {
setEditBox(true); setEditBox(true);
setEditIndex(index); setEditIndex(index);
} }
function exitEditing() { function exitEditing() {
setEditBox(false); setEditBox(false);
} }
return ( return (
<div className="list"> <div className="list">
{editBox ? <EditItem {editBox ? (
lightMode={lightMode} <EditItem
tags={() => tags} lightMode={lightMode}
onExit={exitEditing} tags={() => tags}
SetLoader={SetLoader} onExit={exitEditing}
reFetch={reFetch} SetLoader={SetLoader}
item={data[editIndex]} reFetch={reFetch}
/> : null} item={data[editIndex]}
{/* eslint-disable-next-line */} />
{data.map((e, i, array) => { ) : null}
try { {/* eslint-disable-next-line */}
const url = new URL(e.link); {data.map((e, i, array) => {
const favicon = 'https://www.google.com/s2/favicons?domain=' + url.hostname; try {
return (<LazyLoad key={i} height={200} offset={200}> const url = new URL(e.link);
<div className="list-row"> const favicon =
<div className="img-content-grp"> "https://www.google.com/s2/favicons?domain=" + url.hostname;
<img alt='' src={favicon} /> return (
<div className="list-entity-content"> <LazyLoad key={i} height={200} offset={200}>
<div className='row-name'> <div className="list-row">
<span className="num">{i + 1}.</span> <div className="img-content-grp">
{e.name} <img alt="" src={favicon} />
<a <div className="list-entity-content">
className='link' <div className="row-name">
target="_blank" <span className="num">{i + 1}.</span>
rel="noreferrer" {e.name}
href={e.link} <a
> className="link"
({url.hostname}) target="_blank"
</a> rel="noreferrer"
</div> href={e.link}
<div className='title'>{e.title}</div> >
<div className="tags"> ({url.hostname})
{e.tag.map((e, i) => { </a>
const tagPath = `/tags/${e}`; </div>
return (<Link to={tagPath} key={i}>{e}</Link>) <div className="title">{e.title}</div>
})} <div className="tags">
</div> {e.tag.map((e, i) => {
<div className='date'>{new Date(e.date).toDateString()}</div> const tagPath = `/tags/${e}`;
</div> return (
</div> <Link to={tagPath} key={i}>
<div className='etc'> {e}
<ViewArchived className='view-archived' id={e._id} /> </Link>
<button className="btn edit-btn" onClick={() => edit(i)}>&#xf303;</button> );
</div> })}
</div> </div>
</LazyLoad>) <div className="date">
} catch (e) { {new Date(e.date).toDateString()}
console.log(e); </div>
} </div>
})} </div>
</div> <div className="etc">
) <ViewArchived className="view-archived" id={e._id} />
} <button className="btn edit-btn" onClick={() => edit(i)}>
&#xf303;
</button>
</div>
</div>
</LazyLoad>
);
} catch (e) {
console.log(e);
}
})}
</div>
);
};
export default List export default List;

View File

@ -1,13 +1,12 @@
import '../styles/Loader.css'; import "../styles/Loader.css";
import { InfinitySpin } from 'react-loader-spinner' import { InfinitySpin } from "react-loader-spinner";
const Loader = ({ lightMode }) => { const Loader = ({ lightMode }) => {
return ( return (
<div className='loader'> <div className="loader">
<InfinitySpin color={lightMode ? "Black" : "White"} /> <InfinitySpin color={lightMode ? "Black" : "White"} />
</div> </div>
) );
} };
export default Loader export default Loader;

View File

@ -1,12 +1,12 @@
import React from 'react' import React from "react";
const NoResults = () => { const NoResults = () => {
return ( return (
<div className='no-results'> <div className="no-results">
<h1>¯\_()_/¯</h1> <h1>¯\_()_/¯</h1>
<p>Nothing found.</p> <p>Nothing found.</p>
</div> </div>
) );
} };
export default NoResults; export default NoResults;

View File

@ -1,47 +1,62 @@
import { ProSidebar, SidebarHeader, SidebarFooter, SidebarContent, Menu, MenuItem, SubMenu } from 'react-pro-sidebar'; import {
import 'react-pro-sidebar/dist/css/styles.css'; ProSidebar,
import '../styles/SideBar.css'; SidebarHeader,
SidebarFooter,
SidebarContent,
Menu,
MenuItem,
SubMenu,
} from "react-pro-sidebar";
import "react-pro-sidebar/dist/css/styles.css";
import "../styles/SideBar.css";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
const SideBar = ({ tags, handleToggleSidebar, toggle }) => { const SideBar = ({ tags, handleToggleSidebar, toggle }) => {
const sortedTags = tags.sort((a, b) => { const sortedTags = tags.sort((a, b) => {
const A = a.toLowerCase(), B = b.toLowerCase(); const A = a.toLowerCase(),
if (A < B) B = b.toLowerCase();
return -1; if (A < B) return -1;
if (A > B) if (A > B) return 1;
return 1; return 0;
return 0; });
});
return ( return (
<ProSidebar <ProSidebar
toggled={toggle} toggled={toggle}
breakPoint="lg" breakPoint="lg"
onToggle={handleToggleSidebar} onToggle={handleToggleSidebar}
className='sidebar'> className="sidebar"
<SidebarHeader> >
<SidebarHeader>
<h1>LinkWarden</h1> <h1>LinkWarden</h1>
</SidebarHeader> </SidebarHeader>
<SidebarContent className='sidebar-content'> <SidebarContent className="sidebar-content">
<Menu iconShape="circle"> <Menu iconShape="circle">
<MenuItem>
<MenuItem><Link to="/"><h3>Show Everything</h3></Link></MenuItem> <Link to="/">
<h3>Show Everything</h3>
<SubMenu icon='#' defaultOpen={true} title='Tags'> </Link>
{sortedTags.map((e, i) => { </MenuItem>
const path = `/tags/${e}`;
return <MenuItem key={i}><Link to={path}>{e}</Link></MenuItem>
})}
</SubMenu>
</Menu>
</SidebarContent>
<SidebarFooter>
<p className='credits'>©{new Date().getFullYear()} Made with 💙 by <a href='https://github.com/Daniel31x13'>Daniel 31X13</a></p>
</SidebarFooter>
</ProSidebar>
)
}
export default SideBar <SubMenu icon="#" defaultOpen={true} title="Tags">
{sortedTags.map((e, i) => {
const path = `/tags/${e}`;
return (
<MenuItem key={i}>
<Link to={path}>{e}</Link>
</MenuItem>
);
})}
</SubMenu>
</Menu>
</SidebarContent>
<SidebarFooter>
<p className="credits">
©{new Date().getFullYear()} Made with 💙 by{" "}
<a href="https://github.com/Daniel31x13">Daniel 31X13</a>
</p>
</SidebarFooter>
</ProSidebar>
);
};
export default SideBar;

View File

@ -1,32 +1,44 @@
import '../styles/Sort.css' import "../styles/Sort.css";
const Sort = ({ sortBy, onExit }) => { const Sort = ({ sortBy, onExit }) => {
function abort(e) { function abort(e) {
if (e.target.className === "sort-overlay") { if (e.target.className === "sort-overlay") {
onExit(); onExit();
}
} }
}
function sort(e) { function sort(e) {
sortBy(e.target.value); sortBy(e.target.value);
} }
return ( return (
<> <>
<div className='sort-overlay' onClick={abort}></div> <div className="sort-overlay" onClick={abort}></div>
<div className='sort-box'> <div className="sort-box">
<fieldset className='sort' onClick={sort}> <fieldset className="sort" onClick={sort}>
<legend>Sort by</legend> <legend>Sort by</legend>
<button className='sort-by-btn' value='Default'>&#xf271; Date (Newest first)</button> <button className="sort-by-btn" value="Default">
<button className='sort-by-btn' value='Date (Oldest first)'>&#xf272; Date (Oldest first)</button> &#xf271; Date (Newest first)
<button className='sort-by-btn' value='Name (A-Z)'>&#xf15d; Name (A-Z)</button> </button>
<button className='sort-by-btn' value='Name (Z-A)'>&#xf15e; Name (Z-A)</button> <button className="sort-by-btn" value="Date (Oldest first)">
<button className='sort-by-btn' value='Title (A-Z)'>&#xf15d; Website title (A-Z)</button> &#xf272; Date (Oldest first)
<button className='sort-by-btn' value='Title (Z-A)'>&#xf15e; Website title (Z-A)</button> </button>
</fieldset> <button className="sort-by-btn" value="Name (A-Z)">
</div> &#xf15d; Name (A-Z)
</button>
<button className="sort-by-btn" value="Name (Z-A)">
&#xf15e; Name (Z-A)
</button>
<button className="sort-by-btn" value="Title (A-Z)">
&#xf15d; Website title (A-Z)
</button>
<button className="sort-by-btn" value="Title (Z-A)">
&#xf15e; Website title (Z-A)
</button>
</fieldset>
</div>
</> </>
) );
} };
export default Sort export default Sort;

View File

@ -1,68 +1,71 @@
import CreatableSelect from "react-select/creatable"; import CreatableSelect from "react-select/creatable";
// lightMode ? "Black" : "White" // lightMode ? "Black" : "White"
export default function TagSelection({setTags, tags, tag=[], lightMode}) { export default function TagSelection({ setTags, tags, tag = [], lightMode }) {
const customStyles = { const customStyles = {
container: (provided) => ({ container: (provided) => ({
...provided, ...provided,
textShadow: 'none', textShadow: "none",
}), }),
placeholder: (provided) => ({ placeholder: (provided) => ({
...provided, ...provided,
color: '#a9a9a9', color: "#a9a9a9",
}), }),
multiValueRemove: (provided) => ({ multiValueRemove: (provided) => ({
...provided, ...provided,
color: 'gray', color: "gray",
}), }),
indicatorSeparator: (provided) => ({ indicatorSeparator: (provided) => ({
...provided, ...provided,
display: 'none', display: "none",
}), }),
menu: (provided) => ({ menu: (provided) => ({
...provided, ...provided,
border: 'solid', border: "solid",
borderWidth: '1px', borderWidth: "1px",
borderRadius: '0px', borderRadius: "0px",
borderColor: 'rgb(141, 141, 141)', borderColor: "rgb(141, 141, 141)",
opacity: '90%', opacity: "90%",
color: 'gray', color: "gray",
background: lightMode ? "lightyellow" : "#273949", background: lightMode ? "lightyellow" : "#273949",
boxShadow: 'rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px', boxShadow:
"rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px",
}), }),
input: (provided) => ({ input: (provided) => ({
...provided, ...provided,
color: lightMode ? "rgb(64, 64, 64)" : "white", color: lightMode ? "rgb(64, 64, 64)" : "white",
}), }),
control: (provided, state) => ({ control: (provided, state) => ({
...provided, ...provided,
background: lightMode ? "lightyellow" : "#273949", background: lightMode ? "lightyellow" : "#273949",
border: 'none', border: "none",
borderRadius: '0px', borderRadius: "0px",
boxShadow: state.isFocused ? 'rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px' : 'rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset', boxShadow: state.isFocused
? "rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px"
: "rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset",
}), }),
} };
const data = tags().map((e) => { const data = tags().map((e) => {
return { value: e, label: e } return { value: e, label: e };
}) });
const defaultTags = tag.map((e) => { const defaultTags = tag.map((e) => {
return { value: e, label: e } return { value: e, label: e };
}) });
return ( return (
<CreatableSelect <CreatableSelect
defaultValue={defaultTags} defaultValue={defaultTags}
styles={customStyles} styles={customStyles}
isMulti isMulti
onChange={setTags} onChange={setTags}
options={data} options={data}
/> />
); );
} }

View File

@ -1,17 +1,28 @@
import '../styles/ViewArchived.css'; import "../styles/ViewArchived.css";
import config from '../config'; import config from "../config";
const ViewArchived = ({ id }) => { const ViewArchived = ({ id }) => {
const screenshotPath = config.API.ADDRESS + ":" + config.API.PORT + '/screenshots/' + id + '.png'; const screenshotPath =
const pdfPath = config.API.ADDRESS + ":" + config.API.PORT + '/pdfs/' + id + '.pdf'; config.API.ADDRESS + ":" + config.API.PORT + "/screenshots/" + id + ".png";
const pdfPath =
config.API.ADDRESS + ":" + config.API.PORT + "/pdfs/" + id + ".pdf";
return ( return (
<div className='view-archived'> <div className="view-archived">
<a className='link' href={screenshotPath} target='_blank' rel="noreferrer">Screenshot</a> <a
<hr className='seperator' /> className="link"
<a className='link' href={pdfPath} target='_blank' rel="noreferrer">PDF</a> href={screenshotPath}
target="_blank"
rel="noreferrer"
>
Screenshot
</a>
<hr className="seperator" />
<a className="link" href={pdfPath} target="_blank" rel="noreferrer">
PDF
</a>
</div> </div>
) );
} };
export default ViewArchived; export default ViewArchived;

View File

@ -1,12 +1,12 @@
// Note: the formatting are really sensitive so for example DO NOT end // Note: the formatting are really sensitive so for example DO NOT end
// the "STORAGE_LOCATION" path with an extra slash "/" (i.e. "/home/") // the "STORAGE_LOCATION" path with an extra slash "/" (i.e. "/home/")
module.exports = { module.exports = {
"API": { API: {
"ADDRESS": "http://192.168.1.7", // IP address of the computer which LinkWarden is running ADDRESS: "http://192.168.1.7", // IP address of the computer which LinkWarden is running
"PORT": 5000, // The api port PORT: 5000, // The api port
"MONGODB_URI": "mongodb://localhost:27017", // MongoDB link MONGODB_URI: "mongodb://localhost:27017", // MongoDB link
"DB_NAME": "sample_db", // MongoDB database name DB_NAME: "sample_db", // MongoDB database name
"COLLECTION_NAME": "list", // MongoDB collection name COLLECTION_NAME: "list", // MongoDB collection name
"STORAGE_LOCATION": "/home/danny/Documents" // The path to store the archived data STORAGE_LOCATION: "/home/danny/Documents", // The path to store the archived data
} },
} };

View File

@ -11,4 +11,4 @@ root.render(
<App /> <App />
</BrowserRouter> </BrowserRouter>
</React.StrictMode> </React.StrictMode>
); );

View File

@ -1,13 +1,13 @@
const concatTags = (data) => { const concatTags = (data) => {
let tags = []; let tags = [];
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
tags = tags.concat(data[i].tag) tags = tags.concat(data[i].tag);
} }
tags = tags.filter((v, i, a) => a.indexOf(v) === i); tags = tags.filter((v, i, a) => a.indexOf(v) === i);
return tags; return tags;
} };
export default concatTags; export default concatTags;

View File

@ -1,19 +1,23 @@
import config from '../config'; import config from "../config";
const deleteEntity = (id, reFetch, onExit, SetLoader) => { const deleteEntity = (id, reFetch, onExit, SetLoader) => {
const ADDRESS = config.API.ADDRESS + ":" + config.API.PORT; const ADDRESS = config.API.ADDRESS + ":" + config.API.PORT;
fetch(ADDRESS + "/api", { fetch(ADDRESS + "/api", {
method: "DELETE", method: "DELETE",
body: JSON.stringify({id}), body: JSON.stringify({ id }),
headers: { headers: {
"Content-type": "application/json; charset=UTF-8", "Content-type": "application/json; charset=UTF-8",
} },
})
.then((res) => res.text())
.then((message) => {
console.log(message);
}) })
.then(res => res.text())
.then(message => {console.log(message)})
.then(() => onExit()) .then(() => onExit())
.then(() => reFetch()) .then(() => reFetch())
.then(() => {SetLoader(false)}); .then(() => {
} SetLoader(false);
});
};
export default deleteEntity; export default deleteEntity;

View File

@ -1,22 +1,31 @@
const filteredData = (data, searchQuery, nameChecked, tagsChecked, descriptionChecked) => { const filteredData = (
return data.filter((e) => { data,
const name = e.name.toLowerCase().includes(searchQuery.toLowerCase()); searchQuery,
const title = e.title.toLowerCase().includes(searchQuery.toLowerCase()); nameChecked,
const tags = e.tag.some((e) => e.includes(searchQuery.toLowerCase())); tagsChecked,
descriptionChecked
if((nameChecked && tagsChecked && descriptionChecked)) { ) => {
return (name || title || tags); return data.filter((e) => {
} else if(nameChecked && tagsChecked) { const name = e.name.toLowerCase().includes(searchQuery.toLowerCase());
return (name || tags); const title = e.title.toLowerCase().includes(searchQuery.toLowerCase());
} else if(nameChecked && descriptionChecked) { const tags = e.tag.some((e) => e.includes(searchQuery.toLowerCase()));
return (name || title);
} else if(tagsChecked && descriptionChecked) {
return (tags || title);
}
else if(nameChecked) { return name }
else if(tagsChecked) { return tags }
else if(descriptionChecked) { return title }
});
}
export default filteredData; if (nameChecked && tagsChecked && descriptionChecked) {
return name || title || tags;
} else if (nameChecked && tagsChecked) {
return name || tags;
} else if (nameChecked && descriptionChecked) {
return name || title;
} else if (tagsChecked && descriptionChecked) {
return tags || title;
} else if (nameChecked) {
return name;
} else if (tagsChecked) {
return tags;
} else if (descriptionChecked) {
return title;
}
});
};
export default filteredData;

View File

@ -1,48 +1,63 @@
import config from '../config'; import config from "../config";
import { nanoid } from 'nanoid'; import { nanoid } from "nanoid";
const addItem = async (name, link, tag, reFetch, onExit, SetLoader, method, id=nanoid(), title='', date=new Date()) => { const addItem = async (
name,
link,
tag,
reFetch,
onExit,
SetLoader,
method,
id = nanoid(),
title = "",
date = new Date()
) => {
const dateCreated = date.toString(); const dateCreated = date.toString();
function isValidHttpUrl(string) { function isValidHttpUrl(string) {
let url; let url;
try { try {
url = new URL(string); url = new URL(string);
} catch (_) { } catch (_) {
return false; return false;
} }
return url.protocol === "http:" || url.protocol === "https:"; return url.protocol === "http:" || url.protocol === "https:";
} }
if(isValidHttpUrl(link)) { if (isValidHttpUrl(link)) {
const ADDRESS = config.API.ADDRESS + ":" + config.API.PORT; const ADDRESS = config.API.ADDRESS + ":" + config.API.PORT;
fetch(ADDRESS + "/api", { fetch(ADDRESS + "/api", {
method: method, method: method,
body: JSON.stringify({ body: JSON.stringify({
_id: id, _id: id,
name: name, name: name,
title: title, title: title,
link: link, link: link,
tag: tag, tag: tag,
date: dateCreated date: dateCreated,
}), }),
headers: { headers: {
"Content-type": "application/json; charset=UTF-8" "Content-type": "application/json; charset=UTF-8",
} },
}) })
.then(res => res.text()) .then((res) => res.text())
.then(() => reFetch()) .then(() => reFetch())
.then(() => {SetLoader(false)}); .then(() => {
SetLoader(false);
});
onExit(); onExit();
} else if(!isValidHttpUrl(link) && link !== '') { } else if (!isValidHttpUrl(link) && link !== "") {
SetLoader(false) SetLoader(false);
alert('Please make sure the link is valid.\n\n(i.e. starts with "http"/"https")'); alert(
'Please make sure the link is valid.\n\n(i.e. starts with "http"/"https")'
);
} else { } else {
SetLoader(false) SetLoader(false);
} }
} };
export default addItem; export default addItem;

View File

@ -1,52 +1,48 @@
const sortList = (data, sortBy) => { const sortList = (data, sortBy) => {
let sortedData = data; let sortedData = data;
if(sortBy === 'Default') { if (sortBy === "Default") {
sortedData.sort((a, b) => { sortedData.sort((a, b) => {
return new Date(b.date) - new Date(a.date); return new Date(b.date) - new Date(a.date);
}); });
} else if(sortBy === 'Date (Oldest first)') { } else if (sortBy === "Date (Oldest first)") {
sortedData.sort((a,b) => { sortedData.sort((a, b) => {
return new Date(a.date) - new Date(b.date); return new Date(a.date) - new Date(b.date);
}); });
} else if(sortBy === 'Name (A-Z)') { } else if (sortBy === "Name (A-Z)") {
sortedData.sort((a, b) => { sortedData.sort((a, b) => {
const A = a.name.toLowerCase(), B = b.name.toLowerCase(); const A = a.name.toLowerCase(),
if (A < B) B = b.name.toLowerCase();
return -1; if (A < B) return -1;
if (A > B) if (A > B) return 1;
return 1; return 0;
return 0; });
}); } else if (sortBy === "Name (Z-A)") {
} else if(sortBy === 'Name (Z-A)') { sortedData.sort((a, b) => {
sortedData.sort((a, b) => { const A = a.name.toLowerCase(),
const A = a.name.toLowerCase(), B = b.name.toLowerCase(); B = b.name.toLowerCase();
if (A > B) if (A > B) return -1;
return -1; if (A < B) return 1;
if (A < B) return 0;
return 1; });
return 0; } else if (sortBy === "Title (A-Z)") {
}); sortedData.sort((a, b) => {
} else if(sortBy === 'Title (A-Z)') { const A = a.title.toLowerCase(),
sortedData.sort((a, b) => { B = b.title.toLowerCase();
const A = a.title.toLowerCase(), B = b.title.toLowerCase(); if (A < B) return -1;
if (A < B) if (A > B) return 1;
return -1; return 0;
if (A > B) });
return 1; } else if (sortBy === "Title (Z-A)") {
return 0; sortedData.sort((a, b) => {
}); const A = a.title.toLowerCase(),
} else if(sortBy === 'Title (Z-A)') { B = b.title.toLowerCase();
sortedData.sort((a, b) => { if (A > B) return -1;
const A = a.title.toLowerCase(), B = b.title.toLowerCase(); if (A < B) return 1;
if (A > B) return 0;
return -1; });
if (A < B) }
return 1;
return 0;
});
}
return sortedData; return sortedData;
} };
export default sortList; export default sortList;

View File

@ -1,23 +1,23 @@
import { useParams } from 'react-router-dom'; import { useParams } from "react-router-dom";
import List from '../componets/List'; import List from "../componets/List";
const Tags = ({ data, tags, SetLoader, lightMode, reFetch }) => { const Tags = ({ data, tags, SetLoader, lightMode, reFetch }) => {
const { tagId } = useParams(); const { tagId } = useParams();
const dataWithMatchingTag = data.filter((e) => { const dataWithMatchingTag = data.filter((e) => {
return e.tag.includes(tagId) return e.tag.includes(tagId);
}); });
return ( return (
<div className="content"> <div className="content">
<List <List
lightMode={lightMode} lightMode={lightMode}
data={dataWithMatchingTag} data={dataWithMatchingTag}
tags={tags} tags={tags}
SetLoader={SetLoader} SetLoader={SetLoader}
reFetch={reFetch} reFetch={reFetch}
/> />
</div> </div>
) );
} };
export default Tags export default Tags;

View File

@ -35,14 +35,16 @@
.search { .search {
padding: 10px; padding: 10px;
font-family: 'Font Awesome 5 Free'; font-family: "Font Awesome 5 Free";
font-size: 1.2rem; font-size: 1.2rem;
padding-left: 10px; padding-left: 10px;
border: none; border: none;
border-radius: 0; border-radius: 0;
-webkit-appearance: none; -webkit-appearance: none;
-webkit-box-shadow:rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; -webkit-box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
} }
.search:focus { .search:focus {
@ -52,28 +54,32 @@
.btn { .btn {
position: relative; position: relative;
border-radius: 100%; border-radius: 100%;
font-family: 'Font Awesome 5 Free'; font-family: "Font Awesome 5 Free";
width: 40px; width: 40px;
height: 40px; height: 40px;
padding: 10px; padding: 10px;
font-size: 1.1rem; font-size: 1.1rem;
cursor: pointer; cursor: pointer;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
border: none; border: none;
transition: background-color 0.1s; transition: background-color 0.1s;
} }
.btn:active { box-shadow: 0px 0px 10px rgb(83, 143, 255); } .btn:active {
box-shadow: 0px 0px 10px rgb(83, 143, 255);
}
.add-btn { .add-btn {
margin-left: auto; margin-left: auto;
} }
textarea:focus, input:focus{ textarea:focus,
input:focus {
outline: none; outline: none;
} }
.results { .results {
margin: 20px 20px 0px 5px; margin: 20px 20px 0px 5px;
display: inline-block; display: inline-block;
} }
@ -82,11 +88,12 @@ textarea:focus, input:focus{
text-align: center; text-align: center;
padding-top: 5%; padding-top: 5%;
padding-bottom: 5%; padding-bottom: 5%;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
margin-top: 10px; margin-top: 10px;
} }
.dark-light-btn { .dark-light-btn {
margin-left: 10px; margin-left: 10px;
font-size: 1.2em; font-size: 1.2em;
} }

View File

@ -1,73 +1,77 @@
@media (min-width: 800px) { @media (min-width: 800px) {
.filter { .filter {
left: 35%; left: 35%;
right: 35%; right: 35%;
min-width: 200px; min-width: 200px;
} }
} }
@media (max-width: 800px) { @media (max-width: 800px) {
.filter { .filter {
left: 20%; left: 20%;
right: 20%; right: 20%;
min-width: 100px; min-width: 100px;
} }
} }
.filter-overlay { .filter-overlay {
animation: fadein 0.2s; animation: fadein 0.2s;
background-color: black; background-color: black;
opacity: 10%; opacity: 10%;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
width: 100vw; width: 100vw;
z-index: 1; z-index: 1;
} }
.filter-box { .filter-box {
position: relative; position: relative;
} }
.filter { .filter {
animation: fadein 0.3s; animation: fadein 0.3s;
border: solid; border: solid;
border-width: 1px; border-width: 1px;
font-weight: 300; font-weight: 300;
border-color: rgb(141, 141, 141); border-color: rgb(141, 141, 141);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
padding: 5px; padding: 5px;
position: absolute; position: absolute;
-moz-user-select: none; -moz-user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
user-select: none; user-select: none;
z-index: 2; z-index: 2;
} }
.filter legend { .filter legend {
font-weight: 600; font-weight: 600;
} }
.filter > label { .filter > label {
margin: 5px; margin: 5px;
text-align: left; text-align: left;
margin-bottom: 5px; margin-bottom: 5px;
font-family: 'Font Awesome 5 Free'; font-family: "Font Awesome 5 Free";
padding: 10px; padding: 10px;
font-size: 1.1rem; font-size: 1.1rem;
cursor: pointer; cursor: pointer;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
border: none; rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
border: none;
} }
.filter label:active { .filter label:active {
box-shadow: 0px 0px 10px rgb(83, 143, 255); box-shadow: 0px 0px 10px rgb(83, 143, 255);
} }
@keyframes fadein { @keyframes fadein {
from { opacity: 0%; } from {
to { } opacity: 0%;
} }
to {
}
}

View File

@ -23,7 +23,9 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
rgba(0, 0, 0, 0.3) 0px 7px 13px -3px,
rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
} }
.list a { .list a {
@ -39,7 +41,7 @@
.tags { .tags {
margin: 10px auto 10px auto; margin: 10px auto 10px auto;
} }
.list-row { .list-row {
margin-top: 20px; margin-top: 20px;
display: flex; display: flex;
@ -47,7 +49,9 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
text-align: center; text-align: center;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
rgba(0, 0, 0, 0.3) 0px 7px 13px -3px,
rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
} }
.etc { .etc {
display: flex; display: flex;
@ -108,7 +112,7 @@
.link { .link {
white-space: nowrap; white-space: nowrap;
font-family: 'Font Awesome 5 Free'; font-family: "Font Awesome 5 Free";
pointer-events: all; pointer-events: all;
font-size: 1rem; font-size: 1rem;
} }
@ -123,7 +127,6 @@
opacity: 100%; opacity: 100%;
} }
.edit-btn { .edit-btn {
margin: 20px 20px 20px 0px; margin: 20px 20px 20px 0px;
width: 50px; width: 50px;
@ -169,21 +172,22 @@
.delete { .delete {
margin: 20px 20px 20px 0px; margin: 20px 20px 20px 0px;
background-color:#273949; background-color: #273949;
float: right; float: right;
font-size: 1.1rem; font-size: 1.1rem;
width: 40px; width: 40px;
height: 40px; height: 40px;
position: relative; position: relative;
border-radius: 100%; border-radius: 100%;
font-family: 'Font Awesome 5 Free'; font-family: "Font Awesome 5 Free";
width: 40px; width: 40px;
height: 40px; height: 40px;
padding: 10px; padding: 10px;
font-size: 1.1rem; font-size: 1.1rem;
cursor: pointer; cursor: pointer;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
background-color:#273949; rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
background-color: #273949;
border: none; border: none;
transition: background-color 0.1s; transition: background-color 0.1s;
} }

View File

@ -1,7 +1,7 @@
.loader { .loader {
position: fixed; position: fixed;
bottom: 10%; bottom: 10%;
left: 30%; left: 30%;
right: 30%; right: 30%;
text-align: center; text-align: center;
} }

View File

@ -1,103 +1,111 @@
@media (min-width: 800px) { @media (min-width: 800px) {
.box { .box {
left: 30%; left: 30%;
right: 30%; right: 30%;
min-width: 300px; min-width: 300px;
} }
} }
@media (max-width: 800px) { @media (max-width: 800px) {
.box { .box {
left: 15%; left: 15%;
right: 15%; right: 15%;
min-width: 200px; min-width: 200px;
} }
} }
.add-overlay { .add-overlay {
animation: fadein 0.2s; animation: fadein 0.2s;
background-color: black; background-color: black;
opacity: 10%; opacity: 10%;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
width: 100vw; width: 100vw;
} }
.send-box { .send-box {
position: relative; position: relative;
} }
.box { .box {
animation: fadein 0.3s; animation: fadein 0.3s;
border: solid; border: solid;
border-width: 1px; border-width: 1px;
border-color: rgb(141, 141, 141); border-color: rgb(141, 141, 141);
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
position: absolute; position: absolute;
z-index: 2; z-index: 2;
background-color: #1f2c38; background-color: #1f2c38;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
} }
.box legend { .box legend {
font-weight: 600; font-weight: 600;
} }
.box h3 { .box h3 {
font-weight: 300; font-weight: 300;
} }
.AddItem-content { .AddItem-content {
padding: 20px; padding: 20px;
} }
.AddItem-input { .AddItem-input {
font-size: 1rem; font-size: 1rem;
padding: 10px; padding: 10px;
border: none; border: none;
width: 100%; width: 100%;
color: white; color: white;
background-color:#273949; background-color: #273949;
border-radius: 0; border-radius: 0;
-webkit-appearance: none; -webkit-appearance: none;
-webkit-box-shadow:rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; -webkit-box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
} }
.AddItem-input:focus { .AddItem-input:focus {
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
} }
.send-btn { .send-btn {
font-family: 'Font Awesome 5 Free'; font-family: "Font Awesome 5 Free";
font-size: 1.2rem; font-size: 1.2rem;
padding: 10px; padding: 10px;
cursor: pointer; cursor: pointer;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
border: none; rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
margin-top: 20px; border: none;
display: block; margin-top: 20px;
margin-left: auto; display: block;
margin-right: auto; margin-left: auto;
transition: background-color 0.1s; margin-right: auto;
transition: background-color 0.1s;
} }
.send-btn:active { box-shadow: 0px 0px 10px rgb(83, 143, 255); } .send-btn:active {
box-shadow: 0px 0px 10px rgb(83, 143, 255);
}
@keyframes fadein { @keyframes fadein {
from { opacity: 0%; } from {
to { } opacity: 0%;
}
to {
}
} }
.optional { .optional {
color: gray; color: gray;
font-size: 0.8em; font-size: 0.8em;
float: right; float: right;
} }
.title { .title {
font-size: 0.9em; font-size: 0.9em;
} }

View File

@ -1,27 +1,27 @@
.sidebar { .sidebar {
height: 100vh; height: 100vh;
position: fixed; position: fixed;
border-right: solid; border-right: solid;
border-width: 1px; border-width: 1px;
border-color: gray; border-color: gray;
} }
.sidebar h1 { .sidebar h1 {
text-align: center; text-align: center;
} }
.credits { .credits {
text-align: center; text-align: center;
font-size: small; font-size: small;
} }
.credits a { .credits a {
color: inherit; color: inherit;
text-decoration: underline; text-decoration: underline;
} }
.pro-sidebar-layout { .pro-sidebar-layout {
background: #384952; background: #384952;
text-shadow: none; text-shadow: none;
color: white; color: white;
} }

View File

@ -1,73 +1,77 @@
@media (min-width: 800px) { @media (min-width: 800px) {
.sort { .sort {
left: 30%; left: 30%;
right: 30%; right: 30%;
min-width: 200px; min-width: 200px;
} }
} }
@media (max-width: 800px) { @media (max-width: 800px) {
.sort { .sort {
left: 20%; left: 20%;
right: 20%; right: 20%;
min-width: 100px; min-width: 100px;
} }
} }
.sort-overlay { .sort-overlay {
animation: fadein 0.2s; animation: fadein 0.2s;
background-color: black; background-color: black;
opacity: 10%; opacity: 10%;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
width: 100vw; width: 100vw;
z-index: 1; z-index: 1;
} }
.sort-box { .sort-box {
position: relative; position: relative;
} }
.sort { .sort {
animation: fadein 0.3s; animation: fadein 0.3s;
border: solid; border: solid;
border-width: 1px; border-width: 1px;
font-weight: 300; font-weight: 300;
border-color: rgb(141, 141, 141); border-color: rgb(141, 141, 141);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
padding: 5px; padding: 5px;
position: absolute; position: absolute;
-moz-user-select: none; -moz-user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
user-select: none; user-select: none;
z-index: 2; z-index: 2;
} }
.sort legend { .sort legend {
font-weight: 600; font-weight: 600;
} }
.sort-by-btn { .sort-by-btn {
margin: 5px; margin: 5px;
text-align: left; text-align: left;
font-family: 'Font Awesome 5 Free'; font-family: "Font Awesome 5 Free";
padding: 10px; padding: 10px;
font-size: 1.1rem; font-size: 1.1rem;
cursor: pointer; cursor: pointer;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
border: none; rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
transition: background-color 0.1s; border: none;
transition: background-color 0.1s;
} }
.sort-by-btn:active { .sort-by-btn:active {
box-shadow: 0px 0px 10px rgb(83, 143, 255); box-shadow: 0px 0px 10px rgb(83, 143, 255);
} }
@keyframes fadein { @keyframes fadein {
from { opacity: 0%; } from {
to { } opacity: 0%;
} }
to {
}
}

View File

@ -1,10 +1,10 @@
.view-archived { .view-archived {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
text-align: left; text-align: left;
} }
.seperator { .seperator {
width: 100%; width: 100%;
color: #1f2c38; color: #1f2c38;
} }

View File

@ -1,7 +1,7 @@
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif; sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@ -20,7 +20,7 @@ body {
/* Dark Mode settings (Default) */ /* Dark Mode settings (Default) */
.dark-light-btn::before { .dark-light-btn::before {
content: ''; content: "";
} }
.delete { .delete {
@ -30,10 +30,10 @@ body {
.no-results { .no-results {
background-color: #1f2c38; background-color: #1f2c38;
} }
.send-btn { .send-btn {
background-color:#273949; background-color: #273949;
color: #ffffffb6; color: #ffffffb6;
} }
@ -42,11 +42,13 @@ body {
} }
.sort-by-btn { .sort-by-btn {
background-color:#273949; background-color: #273949;
color: #ffffffb6; color: #ffffffb6;
} }
.btn:hover, .sort-by-btn:hover, .send-btn:hover { .btn:hover,
.sort-by-btn:hover,
.send-btn:hover {
background-color: rgb(76, 117, 170); background-color: rgb(76, 117, 170);
} }
@ -64,21 +66,22 @@ body {
.btn { .btn {
color: #ffffffb6; color: #ffffffb6;
background-color:#273949; background-color: #273949;
} }
.edit-btn { .edit-btn {
background-color: #1f2c38; background-color: #1f2c38;
} }
.no-results, .list-row { .no-results,
.list-row {
transition: background-color 0.1s; transition: background-color 0.1s;
background-color:#273949; background-color: #273949;
} }
.search { .search {
transition: background-color 0.1s; transition: background-color 0.1s;
background-color:#273949; background-color: #273949;
color: white; color: white;
} }
@ -87,19 +90,19 @@ body {
} }
.filter > label { .filter > label {
background-color:#273949; background-color: #273949;
color: #ffffffb6; color: #ffffffb6;
} }
/* Light Mode settings */ /* Light Mode settings */
.light .dark-light-btn::before { .light .dark-light-btn::before {
content: ''; content: "";
} }
.light { .light {
text-shadow: 0px 1px 2px #ffffff; text-shadow: 0px 1px 2px #ffffff;
background-color:rgb(233, 220, 179); background-color: rgb(233, 220, 179);
color: rgb(64, 64, 64); color: rgb(64, 64, 64);
} }
@ -122,8 +125,9 @@ body {
color: black; color: black;
} }
.light .box, .light .edit-btn { .light .box,
background-color:rgb(233, 220, 179); .light .edit-btn {
background-color: rgb(233, 220, 179);
} }
.light .title { .light .title {
@ -138,7 +142,8 @@ body {
color: rgb(9, 139, 214); color: rgb(9, 139, 214);
} }
.light .filter, .light .sort { .light .filter,
.light .sort {
background-color: rgb(233, 220, 179); background-color: rgb(233, 220, 179);
} }
@ -148,7 +153,7 @@ body {
} }
.light .sort-by-btn { .light .sort-by-btn {
background-color:lightyellow; background-color: lightyellow;
color: #4b4b4bb6; color: #4b4b4bb6;
} }
@ -157,11 +162,13 @@ body {
color: #717171b6; color: #717171b6;
} }
.light .sort-by-btn:hover, .light .btn:hover, .light .send-btn:hover { .light .sort-by-btn:hover,
.light .btn:hover,
.light .send-btn:hover {
background-color: rgb(55, 131, 237); background-color: rgb(55, 131, 237);
color: #d8d8d8; color: #d8d8d8;
} }
.light .no-results { .light .no-results {
background-color: lightyellow; background-color: lightyellow;
} }