Cleaner code with prettier.
This commit is contained in:
parent
e2db7e71ac
commit
10d3a05c1d
|
@ -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&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&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).**
|
||||||
|
|
|
@ -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();
|
||||||
}
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
101
api/server.js
101
api/server.js
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
188
src/App.js
188
src/App.js
|
@ -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}></button>
|
<button
|
||||||
<input className="search" type="search" placeholder=" Search" onChange={search}/>
|
className="sidebar-btn btn"
|
||||||
<button className="add-btn btn" onClick={() => setNewBox(true)}></button>
|
style={{ marginRight: "10px" }}
|
||||||
<button className="dark-light-btn btn" onClick={() => setLightMode(!lightMode)}></button>
|
onClick={handleToggleSidebar}
|
||||||
|
>
|
||||||
|

|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
className="search"
|
||||||
|
type="search"
|
||||||
|
placeholder=" Search"
|
||||||
|
onChange={search}
|
||||||
|
/>
|
||||||
|
<button className="add-btn btn" onClick={() => setNewBox(true)}>
|
||||||
|

|
||||||
|
</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)}
|
||||||
|
>
|
||||||
|

|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
style={{ marginLeft: "10px" }}
|
||||||
|
onClick={() => setSortBox(true)}
|
||||||
|
>
|
||||||
|

|
||||||
|
</button>
|
||||||
|
|
||||||
<button className='btn' style={{marginTop: '10px'}} onClick={() => setFilterBox(true)}></button>
|
|
||||||
<button className='btn' style={{marginLeft: '10px'}} onClick={() => setSortBox(true)}></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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 </button>
|
<button onClick={newItem} className="send-btn">
|
||||||
|
Add 
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default AddItem;
|
export default AddItem;
|
||||||
|
|
|
@ -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}></button>
|
<button className="delete" onClick={deleteItem}>
|
||||||
<div className='AddItem-content'>
|

|
||||||
<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 </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 
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default EditItem
|
export default EditItem;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)}></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)}>
|
||||||
|

|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LazyLoad>
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default List
|
export default List;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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'> Date (Newest first)</button>
|
<button className="sort-by-btn" value="Default">
|
||||||
<button className='sort-by-btn' value='Date (Oldest first)'> Date (Oldest first)</button>
|
 Date (Newest first)
|
||||||
<button className='sort-by-btn' value='Name (A-Z)'> Name (A-Z)</button>
|
</button>
|
||||||
<button className='sort-by-btn' value='Name (Z-A)'> Name (Z-A)</button>
|
<button className="sort-by-btn" value="Date (Oldest first)">
|
||||||
<button className='sort-by-btn' value='Title (A-Z)'> Website title (A-Z)</button>
|
 Date (Oldest first)
|
||||||
<button className='sort-by-btn' value='Title (Z-A)'> Website title (Z-A)</button>
|
</button>
|
||||||
</fieldset>
|
<button className="sort-by-btn" value="Name (A-Z)">
|
||||||
</div>
|
 Name (A-Z)
|
||||||
|
</button>
|
||||||
|
<button className="sort-by-btn" value="Name (Z-A)">
|
||||||
|
 Name (Z-A)
|
||||||
|
</button>
|
||||||
|
<button className="sort-by-btn" value="Title (A-Z)">
|
||||||
|
 Website title (A-Z)
|
||||||
|
</button>
|
||||||
|
<button className="sort-by-btn" value="Title (Z-A)">
|
||||||
|
 Website title (Z-A)
|
||||||
|
</button>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Sort
|
export default Sort;
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
|
@ -11,4 +11,4 @@ root.render(
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Ŝarĝante…
Reference in New Issue