Added archive support! (Beta) + UI change

This commit is contained in:
Daniel 2022-05-26 20:45:07 +04:30
parent f3f36a9b96
commit d010e351b5
13 changed files with 1168 additions and 1936 deletions

35
api/modules/getData.js Normal file
View File

@ -0,0 +1,35 @@
const puppeteer = require('puppeteer');
const { PuppeteerBlocker } = require('@cliqz/adblocker-puppeteer');
const fetch = require('cross-fetch');
const config = require('../../src/config.json');
const fs = require('fs');
const screenshotDirectory = config.api.storage_location + '/Webmarker/screenshot\'s/';
const pdfDirectory = config.api.storage_location + '/Webmarker/pdf\'s/';
if (!fs.existsSync(screenshotDirectory)){
fs.mkdirSync(screenshotDirectory, { recursive: true });
}
if (!fs.existsSync(pdfDirectory)){
fs.mkdirSync(pdfDirectory, { recursive: true });
}
module.exports = async (link, id) => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await PuppeteerBlocker.fromPrebuiltAdsAndTracking(fetch).then((blocker) => {
blocker.enableBlockingInPage(page);
});
await page.goto(link, { waitUntil: 'load', timeout: 0 });
const title = await page.title();
await page.screenshot({ path: screenshotDirectory + id + '.png', fullPage: true});
await page.pdf({ path: pdfDirectory + id + '.pdf', format: 'a4' });
await browser.close();
return title;
}

2811
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,12 +9,13 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cheerio": "^1.0.0-rc.10", "@cliqz/adblocker-puppeteer": "^1.23.8",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-fetch": "^3.1.5",
"express": "^4.17.3", "express": "^4.17.3",
"mongodb": "^4.5.0", "mongodb": "^4.5.0",
"phantom": "^6.3.0", "puppeteer": "^14.1.1",
"url-parse": "^1.5.10" "uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^2.0.15" "nodemon": "^2.0.15"

View File

@ -1,58 +1,48 @@
const express = require('express'); const express = require('express');
const app = express(); const app = express();
const { MongoClient, ObjectId } = require('mongodb'); const { MongoClient } = require('mongodb');
const request = require('request');
const cheerio = require('cheerio');
const URL = require('url-parse');
const cors = require('cors'); const cors = require('cors');
const config = require('../src/config.json') const config = require('../src/config.json');
const getData = require('./modules/getData.js')
const port = config.server.port; const port = config.api.port;
const uri = config.server.mongodb_full_address; const URI = config.api.mongodb_URI;
const database = config.server.database_name; const database = config.api.database_name;
const collection = config.server.collection_name; const collection = config.api.collection_name;
const client = new MongoClient(uri); const client = new MongoClient(URI);
app.use(cors()); app.use(cors());
app.use(express.json()); app.use(express.json());
app.get('/get', async (req, res) => { app.get('/api', async (req, res) => {
const data = await getDoc(); const data = await getDoc();
res.send(data); res.send(data);
}); });
app.post('/post', (req, res) => { app.post('/api', async (req, res) => {
let title;
const pageToVisit = req.body.link; const pageToVisit = req.body.link;
request(pageToVisit, (error, response, body) => {
try {
if(response.statusCode === 200) {
// Parse the document body
const $ = cheerio.load(body);
req.body.title = $('title').text(); try {
insertDoc(req.body); const dataResult = await getData(pageToVisit, req.body._id);
} else { req.body.title = dataResult;
req.body.title = null; insertDoc(req.body);
insertDoc(req.body); } catch (err) {
} console.log(err);
} catch (error) { insertDoc(req.body);
console.log(error); } finally {
req.body.title = null; res.send('DONE!');
insertDoc(req.body); }
}
});
}); });
app.delete('/delete', async (req, res) => { app.delete('/api', async (req, res) => {
const id = req.body.id.toString(); const id = req.body.id.toString();
await deleteDoc(id); await deleteDoc(id);
res.send(`Bookmark with ObjectId "${id}" deleted.`); res.send(`Bookmark with _id:${id} deleted.`);
}); });
async function insertDoc(doc) { async function insertDoc(doc) {
@ -85,7 +75,7 @@ async function deleteDoc(doc) {
try { try {
const db = client.db(database); const db = client.db(database);
const list = db.collection(collection); const list = db.collection(collection);
const result = await list.deleteOne({"_id": ObjectId(doc)}); const result = await list.deleteOne({"_id": doc});
return result; return result;
} }

25
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^12.1.4", "@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"nanoid": "^3.3.4",
"react": "^18.0.0", "react": "^18.0.0",
"react-dom": "^18.0.0", "react-dom": "^18.0.0",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
@ -8098,9 +8099,9 @@
} }
}, },
"node_modules/https-proxy-agent": { "node_modules/https-proxy-agent": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dependencies": { "dependencies": {
"agent-base": "6", "agent-base": "6",
"debug": "4" "debug": "4"
@ -11013,9 +11014,9 @@
} }
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.2", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==", "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
}, },
@ -21721,9 +21722,9 @@
} }
}, },
"https-proxy-agent": { "https-proxy-agent": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"requires": { "requires": {
"agent-base": "6", "agent-base": "6",
"debug": "4" "debug": "4"
@ -23813,9 +23814,9 @@
} }
}, },
"nanoid": { "nanoid": {
"version": "3.3.2", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==" "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
}, },
"natural-compare": { "natural-compare": {
"version": "1.4.0", "version": "1.4.0",

View File

@ -6,6 +6,7 @@
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^12.1.4", "@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"nanoid": "^3.3.4",
"react": "^18.0.0", "react": "^18.0.0",
"react-dom": "^18.0.0", "react-dom": "^18.0.0",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",

View File

@ -21,17 +21,17 @@ function App() {
return (e.name.toLowerCase().includes(searchQuery.toLowerCase()) || e.title.toLowerCase().includes(searchQuery.toLowerCase()) || e.tag.toLowerCase().includes(searchQuery.toLowerCase())) return (e.name.toLowerCase().includes(searchQuery.toLowerCase()) || e.title.toLowerCase().includes(searchQuery.toLowerCase()) || e.tag.toLowerCase().includes(searchQuery.toLowerCase()))
}); });
useEffect(() => { async function fetchData() {
async function fetchData() { const address = config.api.address + ":" + config.api.port;
const address = config.client.api_address + ":" + config.server.port; const res = await fetch(address + '/api');
const res = await fetch(address + '/get'); const resJSON = await res.json();
const resJSON = await res.json(); const Data = resJSON.sort((a, b) => { return b-a });
const Data = resJSON.sort((a, b) => { return b-a }); setData(Data);
setData(Data); }
}
useEffect(() => {
fetchData(); fetchData();
}, [data]); }, []);
return ( return (
<div className="App"> <div className="App">
@ -39,8 +39,8 @@ function App() {
<input className="search" type="search" placeholder="&#xf002; Search for Name / Title / Tag" onChange={search}/> <input className="search" type="search" placeholder="&#xf002; Search for Name / Title / Tag" onChange={search}/>
<button className="add-btn" onClick={() => setIsAdding(true)}>&#xf067;</button> <button className="add-btn" onClick={() => setIsAdding(true)}>&#xf067;</button>
</div> </div>
<List data={filteredData} /> <List data={filteredData} reFetch={fetchData} />
{isAdding ? <AddModal onExit={exitAdding} /> : null} {isAdding ? <AddModal onExit={exitAdding} reFetch={fetchData} /> : null}
</div> </div>
); );
} }

View File

@ -1,8 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { nanoid } from 'nanoid'
import '../styles/Modal.css'; import '../styles/Modal.css';
import config from '../config.json'; import config from '../config.json';
const AddModal = ({onExit}) => { const AddModal = ({onExit, reFetch}) => {
const [name, setName] = useState(''); const [name, setName] = useState('');
const [link, setLink] = useState(''); const [link, setLink] = useState('');
const [tag, setTag] = useState(''); const [tag, setTag] = useState('');
@ -33,16 +34,17 @@ const AddModal = ({onExit}) => {
} }
if(name !== '' && isValidHttpUrl(link) && tag !== '') { if(name !== '' && isValidHttpUrl(link) && tag !== '') {
const address = config.client.api_address + ":" + config.server.port; const address = config.api.address + ":" + config.api.port;
fetch(address + "/post", { fetch(address + "/api", {
// Adding method type // Adding method type
method: "POST", method: "POST",
// Adding body or contents to send // Adding body or contents to send
body: JSON.stringify({ body: JSON.stringify({
_id: nanoid(),
name: name, name: name,
title: "foo", title: null,
link: link, link: link,
tag: tag tag: tag
}), }),
@ -51,7 +53,10 @@ const AddModal = ({onExit}) => {
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(() => reFetch());
onExit(); onExit();
} else if(name !== '' && link !== '' && tag !== '') { } else if(name !== '' && link !== '' && tag !== '') {

View File

@ -1,10 +1,10 @@
import '../styles/List.css'; import '../styles/List.css';
import config from '../config.json'; import config from '../config.json';
const List = ({data}) => { const List = ({data, reFetch}) => {
function deleteEntity(id) { function deleteEntity(id) {
const address = config.client.api_address + ":" + config.server.port; const address = config.api.address + ":" + config.api.port;
fetch(address + "/delete", { fetch(address + "/api", {
// Adding method type // Adding method type
method: "DELETE", method: "DELETE",
@ -19,37 +19,32 @@ const List = ({data}) => {
}) })
.then(res => res.text()) .then(res => res.text())
.then(message => {console.log(message)}) .then(message => {console.log(message)})
.then(() => reFetch())
} }
return ( return (
<table className="table"> <div className="list">
<thead>
<tr>
<th className='number'>#</th>
<th>Name</th>
<th>Title</th>
<th>Link</th>
<th>Tag</th>
</tr>
</thead>
<tbody>
{data.map((e, i) => { {data.map((e, i) => {
try { try {
const url = new URL(e.link) const url = new URL(e.link);
return <tr key={i}> const favicon = 'http://www.google.com/s2/favicons?domain=' + url.hostname;
<td className='number'>{i + 1}</td> return <div className="list-row">
<td>{e.name}</td> <div className="img-content-grp">
<td>{e.title}</td> <img src={favicon} />
<td><a href={e.link}>{url.hostname}</a></td> <div className="list-entity-content" key={i}>
<td>{e.tag}</td> <div className='row-name'><span className="num">{i + 1}.</span> {e.name}</div>
<td className="delete" onClick={() => deleteEntity(e._id)}>&#xf2ed;</td> <div>{e.title}</div>
</tr> <div><a href={e.link}>{url.hostname}</a></div>
<div className="tag">{e.tag}</div>
</div>
</div>
<div className="delete" onClick={() => deleteEntity(e._id)}>&#xf2ed;</div>
</div>
} catch (e) { } catch (e) {
console.log(e) console.log(e);
} }
})} })}
</tbody> </div>
</table>
) )
} }

View File

@ -1,11 +1,10 @@
{ {
"client": { "api": {
"api_address": "http://localhost" "address": "http://localhost",
}, "port": 5002,
"server": { "mongodb_URI": "mongodb://localhost:27017",
"port": 5000,
"mongodb_full_address": "mongodb://localhost:27017",
"database_name": "sample_db", "database_name": "sample_db",
"collection_name": "list" "collection_name": "list",
"storage_location": "/home/danny/Documents"
} }
} }

View File

@ -1,6 +1,5 @@
.App { .App {
min-height: 96vh; min-height: 100vh;
padding: 2vh;
background-color: #1f2c38; background-color: #1f2c38;
color: white; color: white;
} }
@ -10,11 +9,15 @@
} }
.search { .search {
border-radius: 10px;
margin: 20px 20px 0px 20px;
padding: 10px;
font-family: 'Font Awesome 5 Free'; font-family: 'Font Awesome 5 Free';
font-size: 1.5rem; font-size: 1.5rem;
padding-left: 10px; padding-left: 10px;
border: none; border: none;
width: 50%; width: 30%;
min-width: 450px;
color: white; color: white;
background-color:#273949; background-color:#273949;
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;
@ -29,6 +32,8 @@
} }
.add-btn { .add-btn {
border-radius: 10px;
margin: 20px 20px 0px 20px;
font-family: 'Font Awesome 5 Free'; font-family: 'Font Awesome 5 Free';
padding: 10px; padding: 10px;
font-size: 1.5rem; font-size: 1.5rem;
@ -38,7 +43,6 @@
background-color:#273949; background-color:#273949;
border: none; border: none;
margin-left: auto; margin-left: auto;
margin-right: 10px;
transition: background-color 0.1s; transition: background-color 0.1s;
} }

View File

@ -1,32 +1,47 @@
.table { .list {
width: 100%; width: 100%;
text-align: left; text-align: left;
padding-top: 20px; padding-top: 20px;
border-spacing: 10px 10px; border-spacing: 10px 10px;
} }
.table td { .list img {
font-size: 1.3rem; margin: 20px;
padding: 10px; width: 50px;
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; height: 50px;
border-radius: 10px;
box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 28px, rgba(0, 0, 0, 0.22) 0px 10px 10px;
} }
.table th { .img-content-grp {
font-size: 1.6rem; display: flex;
align-items: center;
} }
.table tbody tr:nth-of-type(2n-1) { .list-row {
border-radius: 10px;
display: flex;
justify-content: space-between;
align-items: center;
background-color:#273949; background-color:#273949;
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: 20px;
} }
.table a { .list-entity-content {
padding: 20px;
justify-content: space-between;
display: flex;
flex-direction: column;
}
.list a {
text-decoration: none; text-decoration: none;
color: rgb(194, 193, 193); color: rgb(194, 193, 193);
font-size: 1rem; font-size: 1rem;
} }
.table a:hover { .list a:hover {
text-decoration: underline; text-decoration: underline;
} }
@ -35,6 +50,11 @@
cursor: pointer; cursor: pointer;
transition: background-color 0.1s; transition: background-color 0.1s;
font-family: 'Font Awesome 5 Free'; font-family: 'Font Awesome 5 Free';
padding: 10px;
width: fit-content;
height: fit-content;
margin: 10px;
border-radius: 10px;
} }
.delete:hover { .delete:hover {
@ -45,6 +65,19 @@
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;
} }
.number { .row-name {
text-align: center; font-size: 2rem;
} }
.tag {
margin: 10px;
border: solid;
border-width: 1px;
width: fit-content;
padding: 10px;
font-size: 1rem;
}
.num {
font-size: 1rem;
}

View File

@ -5,4 +5,5 @@ body {
sans-serif; sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
background-color: #1f2c38;
} }