Merge pull request #9 from Daniel31x13/Add-folder-support-(to-sidebar)

Added collections support (to sidebar)
This commit is contained in:
Daniel 2022-06-24 01:05:21 +04:30 committed by GitHub
commit 9cac04eb7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 380 additions and 101 deletions

View File

@ -38,6 +38,8 @@ The objective is to have a self-hosted place to keep useful links in one place,
* 🏷 Set multiple tags to each link. * 🏷 Set multiple tags to each link.
* 🗂 Assign each link to a collection where we can further group links.
**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).**
## Installation ## Installation

View File

@ -7,6 +7,8 @@ services:
image: mongo image: mongo
volumes: volumes:
- ./mongo:/data/db - ./mongo:/data/db
ports:
- 27017:27017
restart: unless-stopped restart: unless-stopped
link-warden-api: link-warden-api:

37
package-lock.json generated
View File

@ -20,6 +20,7 @@
"react-router-dom": "^6.3.0", "react-router-dom": "^6.3.0",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"react-select": "^5.3.2", "react-select": "^5.3.2",
"sass": "^1.53.0",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
} }
}, },
@ -8448,6 +8449,11 @@
"url": "https://opencollective.com/immer" "url": "https://opencollective.com/immer"
} }
}, },
"node_modules/immutable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
"integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ=="
},
"node_modules/import-fresh": { "node_modules/import-fresh": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -14030,6 +14036,22 @@
"resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz",
"integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA=="
}, },
"node_modules/sass": {
"version": "1.53.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.53.0.tgz",
"integrity": "sha512-zb/oMirbKhUgRQ0/GFz8TSAwRq2IlR29vOUJZOx0l8sV+CkHUfHa4u5nqrG+1VceZp7Jfj59SVW9ogdhTvJDcQ==",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/sass-loader": { "node_modules/sass-loader": {
"version": "12.6.0", "version": "12.6.0",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz",
@ -22432,6 +22454,11 @@
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz",
"integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==" "integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA=="
}, },
"immutable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
"integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ=="
},
"import-fresh": { "import-fresh": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -26292,6 +26319,16 @@
"resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz",
"integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA=="
}, },
"sass": {
"version": "1.53.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.53.0.tgz",
"integrity": "sha512-zb/oMirbKhUgRQ0/GFz8TSAwRq2IlR29vOUJZOx0l8sV+CkHUfHa4u5nqrG+1VceZp7Jfj59SVW9ogdhTvJDcQ==",
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
}
},
"sass-loader": { "sass-loader": {
"version": "12.6.0", "version": "12.6.0",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz",

View File

@ -16,6 +16,7 @@
"react-router-dom": "^6.3.0", "react-router-dom": "^6.3.0",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"react-select": "^5.3.2", "react-select": "^5.3.2",
"sass": "^1.53.0",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
"scripts": { "scripts": {

View File

@ -7,10 +7,11 @@ import Filters from "./componets/Filters";
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 concatCollections from "./modules/concatCollections";
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 Collections from "./routes/Collections.js";
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
function App() { function App() {
@ -18,7 +19,6 @@ function App() {
[newBox, setNewBox] = useState(false), [newBox, setNewBox] = useState(false),
[filterBox, setFilterBox] = useState(false), [filterBox, setFilterBox] = useState(false),
[searchQuery, setSearchQuery] = useState(""), [searchQuery, setSearchQuery] = useState(""),
[numberOfResults, setNumberOfResults] = useState(0),
[filterCheckbox, setFilterCheckbox] = useState([true, true, true]), [filterCheckbox, setFilterCheckbox] = useState([true, true, true]),
[sortBy, setSortBy] = useState(1), [sortBy, setSortBy] = useState(1),
[loader, setLoader] = useState(false), [loader, setLoader] = useState(false),
@ -57,8 +57,6 @@ function App() {
const filteredData = filter(data, searchQuery, filterCheckbox); const filteredData = filter(data, searchQuery, filterCheckbox);
const tags = concatTags(data);
async function fetchData() { async function fetchData() {
const res = await fetch(API_HOST + "/api"); const res = await fetch(API_HOST + "/api");
const resJSON = await res.json(); const resJSON = await res.json();
@ -78,10 +76,6 @@ function App() {
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
useEffect(() => {
setNumberOfResults(filteredData.length);
}, [filteredData]);
useEffect(() => { useEffect(() => {
if (lightMode) { if (lightMode) {
document.body.classList.add("light"); document.body.classList.add("light");
@ -95,7 +89,8 @@ function App() {
return ( return (
<div className="App"> <div className="App">
<SideBar <SideBar
tags={tags} tags={concatTags(data)}
collections={concatCollections(data)}
handleToggleSidebar={handleToggleSidebar} handleToggleSidebar={handleToggleSidebar}
toggle={toggle} toggle={toggle}
/> />
@ -132,10 +127,6 @@ function App() {
></button> ></button>
</div> </div>
{numberOfResults > 0 ? (
<p className="results">{numberOfResults} Bookmarks found</p>
) : null}
{filterBox ? ( {filterBox ? (
<Filters <Filters
filterCheckbox={filterCheckbox} filterCheckbox={filterCheckbox}
@ -152,11 +143,12 @@ function App() {
onExit={exitAdding} onExit={exitAdding}
reFetch={fetchData} reFetch={fetchData}
lightMode={lightMode} lightMode={lightMode}
tags={() => tags} tags={() => concatTags(data)}
collections={() => concatCollections(data)}
/> />
) : null} ) : null}
{numberOfResults === 0 ? <NoResults /> : null}
{loader ? <Loader lightMode={lightMode} /> : null} {loader ? <Loader lightMode={lightMode} /> : null}
</div> </div>
@ -170,7 +162,8 @@ function App() {
lightMode={lightMode} lightMode={lightMode}
SetLoader={SetLoader} SetLoader={SetLoader}
data={filteredData} data={filteredData}
tags={tags} tags={concatTags(data)}
collections={concatCollections(data)}
reFetch={fetchData} reFetch={fetchData}
/> />
</div> </div>
@ -184,7 +177,22 @@ function App() {
lightMode={lightMode} lightMode={lightMode}
SetLoader={SetLoader} SetLoader={SetLoader}
data={filteredData} data={filteredData}
tags={tags} tags={concatTags(data)}
collections={concatCollections(data)}
reFetch={fetchData}
/>
}
/>
<Route
path="collections/:collectionId"
element={
<Collections
lightMode={lightMode}
SetLoader={SetLoader}
data={filteredData}
tags={concatTags(data)}
collections={concatCollections(data)}
reFetch={fetchData} reFetch={fetchData}
/> />
} }

View File

@ -2,15 +2,24 @@ 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";
import CollectionSelection from "./CollectionSelection";
const AddItem = ({ onExit, reFetch, tags, SetLoader, lightMode }) => { const AddItem = ({
const [name, setName] = useState(""); onExit,
const [link, setLink] = useState(""); reFetch,
const [tag, setTag] = useState([]); tags,
collections,
SetLoader,
lightMode,
}) => {
const [name, setName] = useState(""),
[link, setLink] = useState(""),
[tag, setTag] = useState([]),
[collection, setCollection] = useState("Unsorted");
function newItem() { function newItem() {
SetLoader(true); SetLoader(true);
addItem(name, link, tag, reFetch, onExit, SetLoader, "POST"); addItem(name, link, tag, collection, reFetch, onExit, SetLoader, "POST");
} }
function SetName(e) { function SetName(e) {
@ -22,10 +31,13 @@ const AddItem = ({ onExit, reFetch, tags, SetLoader, lightMode }) => {
} }
function SetTags(value) { function SetTags(value) {
setTag(value);
setTag(value.map((e) => e.value.toLowerCase())); setTag(value.map((e) => e.value.toLowerCase()));
} }
function SetCollection(value) {
setCollection(value.value);
}
function abort(e) { function abort(e) {
if (e.target.className === "add-overlay") { if (e.target.className === "add-overlay") {
onExit(); onExit();
@ -61,6 +73,14 @@ const AddItem = ({ onExit, reFetch, tags, SetLoader, lightMode }) => {
Tags: <span className="optional">(Optional)</span> Tags: <span className="optional">(Optional)</span>
</h3> </h3>
<TagSelection setTags={SetTags} tags={tags} lightMode={lightMode} /> <TagSelection setTags={SetTags} tags={tags} lightMode={lightMode} />
<h3>
Collections: <span className="optional">(Optional)</span>
</h3>
<CollectionSelection
setCollection={SetCollection}
collections={collections}
lightMode={lightMode}
/>
<button onClick={newItem} className="send-btn"> <button onClick={newItem} className="send-btn">
Add &#xf067; Add &#xf067;
</button> </button>

View File

@ -0,0 +1,82 @@
import CreatableSelect from "react-select/creatable";
export default function CollectionSelection({
setCollection,
collections,
collection = "Unsorted",
lightMode,
}) {
const customStyles = {
container: (provided) => ({
...provided,
textShadow: "none",
}),
placeholder: (provided) => ({
...provided,
color: "#a9a9a9",
}),
option: (provided) => ({
...provided,
':before': {
content: '""',
marginRight: 8,
},
}),
menu: (provided) => ({
...provided,
border: "solid",
borderWidth: "1px",
borderRadius: "0px",
borderColor: "rgb(141, 141, 141)",
opacity: "90%",
color: "gray",
background: lightMode ? "lightyellow" : "#273949",
boxShadow:
"rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px",
}),
input: (provided) => ({
...provided,
color: lightMode ? "rgb(64, 64, 64)" : "white",
}),
singleValue: (provided) => ({
...provided,
':before': {
content: '""',
marginRight: 8,
},
color: lightMode ? "rgb(64, 64, 64)" : "white",
}),
control: (provided, state) => ({
...provided,
background: lightMode ? "lightyellow" : "#273949",
border: "none",
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",
}),
};
const data = collections().map((e) => {
return { value: e, label: e };
});
const defaultCollection = { value: collection, label: collection };
return (
<CreatableSelect
className="select"
defaultValue={defaultCollection}
styles={customStyles}
onChange={setCollection}
options={data}
/>
);
}

View File

@ -3,10 +3,20 @@ 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";
import CollectionSelection from "./CollectionSelection";
const EditItem = ({ tags, item, onExit, SetLoader, reFetch, lightMode }) => { const EditItem = ({
const [name, setName] = useState(item.name); tags,
const [tag, setTag] = useState(item.tag); collections,
item,
onExit,
SetLoader,
reFetch,
lightMode,
}) => {
const [name, setName] = useState(item.name),
[tag, setTag] = useState(item.tag),
[collection, setCollection] = useState(item.collection);
function EditItem() { function EditItem() {
SetLoader(true); SetLoader(true);
@ -14,6 +24,7 @@ const EditItem = ({ tags, item, onExit, SetLoader, reFetch, lightMode }) => {
name, name,
item.link, item.link,
tag, tag,
collection,
reFetch, reFetch,
onExit, onExit,
SetLoader, SetLoader,
@ -33,10 +44,13 @@ const EditItem = ({ tags, item, onExit, SetLoader, reFetch, lightMode }) => {
} }
function SetTags(value) { function SetTags(value) {
setTag(value);
setTag(value.map((e) => e.value.toLowerCase())); setTag(value.map((e) => e.value.toLowerCase()));
} }
function SetCollection(value) {
setCollection(value.value);
}
function abort(e) { function abort(e) {
if (e.target.className === "add-overlay") { if (e.target.className === "add-overlay") {
onExit(); onExit();
@ -50,10 +64,12 @@ const EditItem = ({ tags, item, onExit, SetLoader, reFetch, lightMode }) => {
<div className="add-overlay" onClick={abort}></div> <div className="add-overlay" onClick={abort}></div>
<div className="send-box"> <div className="send-box">
<div className="box"> <div className="box">
<h2>Edit bookmark</h2> <div className="title-delete-group">
<h2 className="edit-title">Edit bookmark</h2>
<button className="delete" onClick={deleteItem}> <button className="delete" onClick={deleteItem}>
&#xf2ed; &#xf2ed;
</button> </button>
</div>
<div className="AddItem-content"> <div className="AddItem-content">
<h3> <h3>
Link:{" "} Link:{" "}
@ -89,6 +105,15 @@ const EditItem = ({ tags, item, onExit, SetLoader, reFetch, lightMode }) => {
tag={tag} tag={tag}
lightMode={lightMode} lightMode={lightMode}
/> />
<h3>
Collection: <span className="optional">(Optional)</span>
</h3>
<CollectionSelection
setCollection={SetCollection}
collections={collections}
collection={collection}
lightMode={lightMode}
/>
<button onClick={EditItem} className="send-btn"> <button onClick={EditItem} className="send-btn">
Update &#xf303; Update &#xf303;
</button> </button>

View File

@ -2,12 +2,14 @@ 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, useEffect } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import NoResults from "./NoResults";
const List = ({ data, tags, reFetch, SetLoader, lightMode }) => { const List = ({ data, tags, collections, reFetch, SetLoader, lightMode }) => {
const [editBox, setEditBox] = useState(false); const [editBox, setEditBox] = useState(false),
const [editIndex, setEditIndex] = useState(0); [editIndex, setEditIndex] = useState(0),
[numberOfResults, setNumberOfResults] = useState(0);
function edit(index) { function edit(index) {
setEditBox(true); setEditBox(true);
@ -18,12 +20,23 @@ const List = ({ data, tags, reFetch, SetLoader, lightMode }) => {
setEditBox(false); setEditBox(false);
} }
useEffect(() => {
setNumberOfResults(data.length);
}, [data]);
return ( return (
<div className="list"> <div className="list">
{numberOfResults > 0 ? (
<p className="results">{numberOfResults} Bookmarks found</p>
) : null}
{numberOfResults === 0 ? <NoResults /> : null}
{editBox ? ( {editBox ? (
<EditItem <EditItem
lightMode={lightMode} lightMode={lightMode}
tags={() => tags} tags={() => tags}
collections={() => collections}
onExit={exitEditing} onExit={exitEditing}
SetLoader={SetLoader} SetLoader={SetLoader}
reFetch={reFetch} reFetch={reFetch}

View File

@ -7,11 +7,12 @@ import {
MenuItem, MenuItem,
SubMenu, SubMenu,
} from "react-pro-sidebar"; } from "react-pro-sidebar";
import "react-pro-sidebar/dist/css/styles.css"; // import "react-pro-sidebar/dist/css/styles.css";
import "../styles/SideBar_S.scss";
import "../styles/SideBar.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, collections, handleToggleSidebar, toggle }) => {
const sortedTags = tags.sort((a, b) => { const sortedTags = tags.sort((a, b) => {
const A = a.toLowerCase(), const A = a.toLowerCase(),
B = b.toLowerCase(); B = b.toLowerCase();
@ -19,6 +20,19 @@ const SideBar = ({ tags, handleToggleSidebar, toggle }) => {
if (A > B) return 1; if (A > B) return 1;
return 0; return 0;
}); });
const sortedCollections = collections
.sort((a, b) => {
const A = a.toLowerCase(),
B = b.toLowerCase();
if (A < B) return -1;
if (A > B) return 1;
return 0;
})
.filter((e) => {
return e !== "Unsorted";
});
return ( return (
<ProSidebar <ProSidebar
toggled={toggle} toggled={toggle}
@ -27,26 +41,47 @@ const SideBar = ({ tags, handleToggleSidebar, toggle }) => {
className="sidebar" className="sidebar"
> >
<SidebarHeader> <SidebarHeader>
<h1>LinkWarden</h1> <h3>LinkWarden</h3>
</SidebarHeader> </SidebarHeader>
<SidebarContent className="sidebar-content"> <SidebarContent className="sidebar-content">
<Menu iconShape="circle"> <Menu iconShape="circle">
<MenuItem icon={<h2 className="sidebar-icon">&#xf015;</h2>}> <MenuItem icon={<h2 className="sidebar-icon">&#xf49e;</h2>}>
<Link to="/"> <Link to="/">
<h3 className="menu-item">All</h3> <div className="menu-item">All</div>
</Link>
</MenuItem>
<MenuItem icon={<h2 className="sidebar-icon">&#xf01c;</h2>}>
<Link to="/collections/Unsorted">
<div className="menu-item">Unsorted</div>
</Link> </Link>
</MenuItem> </MenuItem>
<SubMenu
icon={<h2 className="sidebar-icon">&#xf5fd;</h2>}
suffix={<span className="badge">{sortedCollections.length}</span>}
title={<div className="menu-item">Collections</div>}
>
{sortedCollections.map((e, i) => {
const path = `/collections/${e}`;
return (
<MenuItem prefix={<div className="sidebar-item-prefix">&#xf07b;</div>} key={i}>
<Link className="sidebar-entity" to={path}>{e}</Link>
</MenuItem>
);
})}
</SubMenu>
<SubMenu <SubMenu
icon={<h2 className="sidebar-icon">&#xf02c;</h2>} icon={<h2 className="sidebar-icon">&#xf02c;</h2>}
suffix={<span className="badge">{sortedTags.length}</span>} suffix={<span className="badge">{sortedTags.length}</span>}
title={<h3 className="menu-item">Tags</h3>} title={<div className="menu-item">Tags</div>}
> >
{sortedTags.map((e, i) => { {sortedTags.map((e, i) => {
const path = `/tags/${e}`; const path = `/tags/${e}`;
return ( return (
<MenuItem prefix={"#"} key={i}> <MenuItem prefix={<div className="sidebar-item-prefix">&#x23;</div>} key={i}>
<Link to={path}>{e}</Link> <Link className="sidebar-entity" to={path}>{e}</Link>
</MenuItem> </MenuItem>
); );
})} })}

View File

@ -1,6 +1,5 @@
import CreatableSelect from "react-select/creatable"; import CreatableSelect from "react-select/creatable";
// 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) => ({
@ -13,16 +12,19 @@ export default function TagSelection({ setTags, tags, tag = [], lightMode }) {
color: "#a9a9a9", color: "#a9a9a9",
}), }),
option: (provided) => ({
...provided,
':before': {
content: '"#"',
marginRight: 8,
},
}),
multiValueRemove: (provided) => ({ multiValueRemove: (provided) => ({
...provided, ...provided,
color: "gray", color: "gray",
}), }),
indicatorSeparator: (provided) => ({
...provided,
display: "none",
}),
menu: (provided) => ({ menu: (provided) => ({
...provided, ...provided,
border: "solid", border: "solid",
@ -41,6 +43,14 @@ export default function TagSelection({ setTags, tags, tag = [], lightMode }) {
color: lightMode ? "rgb(64, 64, 64)" : "white", color: lightMode ? "rgb(64, 64, 64)" : "white",
}), }),
multiValueLabel: (provided) => ({
...provided,
':before': {
content: '"#"',
marginRight: 4,
},
}),
control: (provided, state) => ({ control: (provided, state) => ({
...provided, ...provided,
background: lightMode ? "lightyellow" : "#273949", background: lightMode ? "lightyellow" : "#273949",
@ -61,6 +71,7 @@ export default function TagSelection({ setTags, tags, tag = [], lightMode }) {
return ( return (
<CreatableSelect <CreatableSelect
className="select"
defaultValue={defaultTags} defaultValue={defaultTags}
styles={customStyles} styles={customStyles}
isMulti isMulti

View File

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

View File

@ -4,24 +4,24 @@ const filteredData = (
filterCheckbox filterCheckbox
) => { ) => {
return data.filter((e) => { return data.filter((e) => {
const name = e.name.toLowerCase().includes(searchQuery.toLowerCase()); const linkName = e.name.toLowerCase().includes(searchQuery.toLowerCase());
const title = e.title.toLowerCase().includes(searchQuery.toLowerCase()); const websiteTitle = e.title.toLowerCase().includes(searchQuery.toLowerCase());
const tags = e.tag.some((e) => e.includes(searchQuery.toLowerCase())); const tags = e.tag.some((e) => e.includes(searchQuery.toLowerCase()));
if (filterCheckbox === [true, true, true]) { if (filterCheckbox.every(e => e === true)) {
return name || title || tags; return linkName || websiteTitle || tags;
} else if (filterCheckbox[0] && filterCheckbox[2]) { } else if (filterCheckbox[0] && filterCheckbox[2]) {
return name || tags; return linkName || tags;
} else if (filterCheckbox[0] && filterCheckbox[1]) { } else if (filterCheckbox[0] && filterCheckbox[1]) {
return name || title; return linkName || websiteTitle;
} else if (filterCheckbox[2] && filterCheckbox[1]) { } else if (filterCheckbox[2] && filterCheckbox[1]) {
return tags || title; return tags || websiteTitle;
} else if (filterCheckbox[0]) { } else if (filterCheckbox[0]) {
return name; return linkName;
} else if (filterCheckbox[1]) { } else if (filterCheckbox[1]) {
return tags; return websiteTitle;
} else if (filterCheckbox[2]) { } else if (filterCheckbox[2]) {
return title; return tags;
} }
}); });
}; };

View File

@ -5,6 +5,7 @@ const addItem = async (
name, name,
link, link,
tag, tag,
collection,
reFetch, reFetch,
onExit, onExit,
SetLoader, SetLoader,
@ -36,6 +37,7 @@ const addItem = async (
title: title, title: title,
link: link, link: link,
tag: tag, tag: tag,
collection: collection,
date: dateCreated, date: dateCreated,
}), }),
headers: { headers: {

24
src/routes/Collections.js Normal file
View File

@ -0,0 +1,24 @@
import { useParams } from "react-router-dom";
import List from "../componets/List";
const Collections = ({ data, tags, collections, SetLoader, lightMode, reFetch }) => {
const { collectionId } = useParams();
const dataWithMatchingTag = data.filter((e) => {
return e.collection.includes(collectionId);
});
return (
<div className="content">
<List
lightMode={lightMode}
data={dataWithMatchingTag}
tags={tags}
collections={collections}
SetLoader={SetLoader}
reFetch={reFetch}
/>
</div>
);
};
export default Collections;

View File

@ -1,7 +1,7 @@
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, collections, 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);
@ -13,6 +13,7 @@ const Tags = ({ data, tags, SetLoader, lightMode, reFetch }) => {
lightMode={lightMode} lightMode={lightMode}
data={dataWithMatchingTag} data={dataWithMatchingTag}
tags={tags} tags={tags}
collections={collections}
SetLoader={SetLoader} SetLoader={SetLoader}
reFetch={reFetch} reFetch={reFetch}
/> />

View File

@ -32,7 +32,7 @@
} }
.content { .content {
padding: 0px 20px 20px 20px; padding: 0px 20px 0 20px;
} }
.head { .head {
@ -86,21 +86,11 @@ input:focus {
outline: none; outline: none;
} }
.results {
margin: 20px 20px 0px 0px;
display: inline-block;
}
.no-results {
text-align: center;
padding-top: 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;
margin-top: 10px;
}
.dark-light-btn { .dark-light-btn {
margin-left: 10px; margin-left: 10px;
font-size: 1.2em; font-size: 1.2em;
} }
.select {
font-family: "Font Awesome 5 Free"
}

View File

@ -172,9 +172,9 @@
} }
.delete { .delete {
margin: 20px 20px 20px 0px; margin-top: 20px;
background-color: #273949; margin-left: 10px;
float: right; display: inline;
font-size: 1.1rem; font-size: 1.1rem;
width: 40px; width: 40px;
height: 40px; height: 40px;
@ -188,17 +188,13 @@
cursor: pointer; cursor: pointer;
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;
background-color: #273949; background-color: rgba(255, 75, 75, 0.8);
color: white;
border: none; border: none;
transition: background-color 0.1s; transition: box-shadow 0.1s;
} }
.delete:hover { .delete:hover {
background-color: rgba(255, 75, 75, 0.8);
color: #d8d8d8;
}
.delete:active {
box-shadow: 0px 0px 10px rgb(255, 83, 140); box-shadow: 0px 0px 10px rgb(255, 83, 140);
} }
@ -208,3 +204,20 @@
opacity: 80%; opacity: 80%;
margin-right: auto; margin-right: auto;
} }
.no-results {
text-align: center;
padding-top: 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;
margin-top: 10px;
}
.edit-title {
display: inline;
}
.title-delete-group {
text-align: center;
}

View File

@ -3,7 +3,7 @@
position: fixed; position: fixed;
} }
.sidebar h1 { .sidebar h3 {
text-align: center; text-align: center;
} }
@ -17,18 +17,11 @@
text-decoration: underline; text-decoration: underline;
} }
.pro-sidebar-layout {
background: #384952;
text-shadow: none;
color: white;
}
.badge { .badge {
padding: 3px 10px; padding: 3px 10px;
font-size: 0.8rem; font-size: 0.8rem;
letter-spacing: 1px; letter-spacing: 1px;
border-radius: 14px; border-radius: 14px;
background-color: rgb(52, 121, 181);
} }
.sidebar-icon { .sidebar-icon {
@ -47,3 +40,16 @@
.pro-inner-item { .pro-inner-item {
margin-bottom: 10px; margin-bottom: 10px;
} }
.sidebar-entity {
font-size: 1.2rem;
}
.sidebar-item-prefix {
font-family: "Font Awesome 5 Free";
}
.pro-sidebar-layout * {
color: white;
text-shadow: none;
}

View File

@ -0,0 +1,4 @@
$sidebar-bg-color: #373737;
$submenu-bg-color: #373737;
@import '~react-pro-sidebar/dist/scss/styles.scss';

View File

@ -23,11 +23,6 @@ body {
content: ""; content: "";
} }
.delete {
background-color: #1f2c38;
color: #ffffffb6;
}
.no-results { .no-results {
background-color: #1f2c38; background-color: #1f2c38;
} }
@ -111,11 +106,6 @@ body {
color: gray; color: gray;
} }
.light .delete {
background-color: lightyellow;
color: rgb(176, 176, 176);
}
.light input { .light input {
background-color: lightyellow; background-color: lightyellow;
color: black; color: black;