Merge pull request #9 from Daniel31x13/Add-folder-support-(to-sidebar)
Added collections support (to sidebar)
This commit is contained in:
commit
9cac04eb7b
|
@ -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.
|
||||
|
||||
* 🗂 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).**
|
||||
|
||||
## Installation
|
||||
|
|
|
@ -7,6 +7,8 @@ services:
|
|||
image: mongo
|
||||
volumes:
|
||||
- ./mongo:/data/db
|
||||
ports:
|
||||
- 27017:27017
|
||||
restart: unless-stopped
|
||||
|
||||
link-warden-api:
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "5.0.0",
|
||||
"react-select": "^5.3.2",
|
||||
"sass": "^1.53.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
}
|
||||
},
|
||||
|
@ -8448,6 +8449,11 @@
|
|||
"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": {
|
||||
"version": "3.3.0",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "12.6.0",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "3.3.0",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "12.6.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz",
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "5.0.0",
|
||||
"react-select": "^5.3.2",
|
||||
"sass": "^1.53.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
42
src/App.js
42
src/App.js
|
@ -7,10 +7,11 @@ import Filters from "./componets/Filters";
|
|||
import sortList from "./modules/sortList";
|
||||
import filter from "./modules/filterData";
|
||||
import concatTags from "./modules/concatTags";
|
||||
import NoResults from "./componets/NoResults";
|
||||
import concatCollections from "./modules/concatCollections";
|
||||
import Loader from "./componets/Loader";
|
||||
import SideBar from "./componets/SideBar";
|
||||
import Tags from "./routes/Tags.js";
|
||||
import Collections from "./routes/Collections.js";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
|
@ -18,7 +19,6 @@ function App() {
|
|||
[newBox, setNewBox] = useState(false),
|
||||
[filterBox, setFilterBox] = useState(false),
|
||||
[searchQuery, setSearchQuery] = useState(""),
|
||||
[numberOfResults, setNumberOfResults] = useState(0),
|
||||
[filterCheckbox, setFilterCheckbox] = useState([true, true, true]),
|
||||
[sortBy, setSortBy] = useState(1),
|
||||
[loader, setLoader] = useState(false),
|
||||
|
@ -57,8 +57,6 @@ function App() {
|
|||
|
||||
const filteredData = filter(data, searchQuery, filterCheckbox);
|
||||
|
||||
const tags = concatTags(data);
|
||||
|
||||
async function fetchData() {
|
||||
const res = await fetch(API_HOST + "/api");
|
||||
const resJSON = await res.json();
|
||||
|
@ -78,10 +76,6 @@ function App() {
|
|||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setNumberOfResults(filteredData.length);
|
||||
}, [filteredData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lightMode) {
|
||||
document.body.classList.add("light");
|
||||
|
@ -95,7 +89,8 @@ function App() {
|
|||
return (
|
||||
<div className="App">
|
||||
<SideBar
|
||||
tags={tags}
|
||||
tags={concatTags(data)}
|
||||
collections={concatCollections(data)}
|
||||
handleToggleSidebar={handleToggleSidebar}
|
||||
toggle={toggle}
|
||||
/>
|
||||
|
@ -132,10 +127,6 @@ function App() {
|
|||
></button>
|
||||
</div>
|
||||
|
||||
{numberOfResults > 0 ? (
|
||||
<p className="results">{numberOfResults} Bookmarks found</p>
|
||||
) : null}
|
||||
|
||||
{filterBox ? (
|
||||
<Filters
|
||||
filterCheckbox={filterCheckbox}
|
||||
|
@ -152,11 +143,12 @@ function App() {
|
|||
onExit={exitAdding}
|
||||
reFetch={fetchData}
|
||||
lightMode={lightMode}
|
||||
tags={() => tags}
|
||||
tags={() => concatTags(data)}
|
||||
collections={() => concatCollections(data)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{numberOfResults === 0 ? <NoResults /> : null}
|
||||
|
||||
|
||||
{loader ? <Loader lightMode={lightMode} /> : null}
|
||||
</div>
|
||||
|
@ -170,7 +162,8 @@ function App() {
|
|||
lightMode={lightMode}
|
||||
SetLoader={SetLoader}
|
||||
data={filteredData}
|
||||
tags={tags}
|
||||
tags={concatTags(data)}
|
||||
collections={concatCollections(data)}
|
||||
reFetch={fetchData}
|
||||
/>
|
||||
</div>
|
||||
|
@ -184,7 +177,22 @@ function App() {
|
|||
lightMode={lightMode}
|
||||
SetLoader={SetLoader}
|
||||
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}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -2,15 +2,24 @@ import { useState } from "react";
|
|||
import "../styles/SendItem.css";
|
||||
import TagSelection from "./TagSelection";
|
||||
import addItem from "../modules/send";
|
||||
import CollectionSelection from "./CollectionSelection";
|
||||
|
||||
const AddItem = ({ onExit, reFetch, tags, SetLoader, lightMode }) => {
|
||||
const [name, setName] = useState("");
|
||||
const [link, setLink] = useState("");
|
||||
const [tag, setTag] = useState([]);
|
||||
const AddItem = ({
|
||||
onExit,
|
||||
reFetch,
|
||||
tags,
|
||||
collections,
|
||||
SetLoader,
|
||||
lightMode,
|
||||
}) => {
|
||||
const [name, setName] = useState(""),
|
||||
[link, setLink] = useState(""),
|
||||
[tag, setTag] = useState([]),
|
||||
[collection, setCollection] = useState("Unsorted");
|
||||
|
||||
function newItem() {
|
||||
SetLoader(true);
|
||||
addItem(name, link, tag, reFetch, onExit, SetLoader, "POST");
|
||||
addItem(name, link, tag, collection, reFetch, onExit, SetLoader, "POST");
|
||||
}
|
||||
|
||||
function SetName(e) {
|
||||
|
@ -22,10 +31,13 @@ const AddItem = ({ onExit, reFetch, tags, SetLoader, lightMode }) => {
|
|||
}
|
||||
|
||||
function SetTags(value) {
|
||||
setTag(value);
|
||||
setTag(value.map((e) => e.value.toLowerCase()));
|
||||
}
|
||||
|
||||
function SetCollection(value) {
|
||||
setCollection(value.value);
|
||||
}
|
||||
|
||||
function abort(e) {
|
||||
if (e.target.className === "add-overlay") {
|
||||
onExit();
|
||||
|
@ -61,6 +73,14 @@ const AddItem = ({ onExit, reFetch, tags, SetLoader, lightMode }) => {
|
|||
Tags: <span className="optional">(Optional)</span>
|
||||
</h3>
|
||||
<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">
|
||||
Add 
|
||||
</button>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -3,10 +3,20 @@ import deleteEntity from "../modules/deleteEntity";
|
|||
import "../styles/SendItem.css";
|
||||
import TagSelection from "./TagSelection";
|
||||
import editItem from "../modules/send";
|
||||
import CollectionSelection from "./CollectionSelection";
|
||||
|
||||
const EditItem = ({ tags, item, onExit, SetLoader, reFetch, lightMode }) => {
|
||||
const [name, setName] = useState(item.name);
|
||||
const [tag, setTag] = useState(item.tag);
|
||||
const EditItem = ({
|
||||
tags,
|
||||
collections,
|
||||
item,
|
||||
onExit,
|
||||
SetLoader,
|
||||
reFetch,
|
||||
lightMode,
|
||||
}) => {
|
||||
const [name, setName] = useState(item.name),
|
||||
[tag, setTag] = useState(item.tag),
|
||||
[collection, setCollection] = useState(item.collection);
|
||||
|
||||
function EditItem() {
|
||||
SetLoader(true);
|
||||
|
@ -14,6 +24,7 @@ const EditItem = ({ tags, item, onExit, SetLoader, reFetch, lightMode }) => {
|
|||
name,
|
||||
item.link,
|
||||
tag,
|
||||
collection,
|
||||
reFetch,
|
||||
onExit,
|
||||
SetLoader,
|
||||
|
@ -33,10 +44,13 @@ const EditItem = ({ tags, item, onExit, SetLoader, reFetch, lightMode }) => {
|
|||
}
|
||||
|
||||
function SetTags(value) {
|
||||
setTag(value);
|
||||
setTag(value.map((e) => e.value.toLowerCase()));
|
||||
}
|
||||
|
||||
function SetCollection(value) {
|
||||
setCollection(value.value);
|
||||
}
|
||||
|
||||
function abort(e) {
|
||||
if (e.target.className === "add-overlay") {
|
||||
onExit();
|
||||
|
@ -50,10 +64,12 @@ const EditItem = ({ tags, item, onExit, SetLoader, reFetch, lightMode }) => {
|
|||
<div className="add-overlay" onClick={abort}></div>
|
||||
<div className="send-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>
|
||||
</div>
|
||||
<div className="AddItem-content">
|
||||
<h3>
|
||||
Link:{" "}
|
||||
|
@ -89,6 +105,15 @@ const EditItem = ({ tags, item, onExit, SetLoader, reFetch, lightMode }) => {
|
|||
tag={tag}
|
||||
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">
|
||||
Update 
|
||||
</button>
|
||||
|
|
|
@ -2,12 +2,14 @@ import "../styles/List.css";
|
|||
import LazyLoad from "react-lazyload";
|
||||
import ViewArchived from "./ViewArchived";
|
||||
import EditItem from "./EditItem";
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import NoResults from "./NoResults";
|
||||
|
||||
const List = ({ data, tags, reFetch, SetLoader, lightMode }) => {
|
||||
const [editBox, setEditBox] = useState(false);
|
||||
const [editIndex, setEditIndex] = useState(0);
|
||||
const List = ({ data, tags, collections, reFetch, SetLoader, lightMode }) => {
|
||||
const [editBox, setEditBox] = useState(false),
|
||||
[editIndex, setEditIndex] = useState(0),
|
||||
[numberOfResults, setNumberOfResults] = useState(0);
|
||||
|
||||
function edit(index) {
|
||||
setEditBox(true);
|
||||
|
@ -18,12 +20,23 @@ const List = ({ data, tags, reFetch, SetLoader, lightMode }) => {
|
|||
setEditBox(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setNumberOfResults(data.length);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<div className="list">
|
||||
{numberOfResults > 0 ? (
|
||||
<p className="results">{numberOfResults} Bookmarks found</p>
|
||||
) : null}
|
||||
|
||||
{numberOfResults === 0 ? <NoResults /> : null}
|
||||
|
||||
{editBox ? (
|
||||
<EditItem
|
||||
lightMode={lightMode}
|
||||
tags={() => tags}
|
||||
collections={() => collections}
|
||||
onExit={exitEditing}
|
||||
SetLoader={SetLoader}
|
||||
reFetch={reFetch}
|
||||
|
|
|
@ -7,11 +7,12 @@ import {
|
|||
MenuItem,
|
||||
SubMenu,
|
||||
} 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 { Link } from "react-router-dom";
|
||||
|
||||
const SideBar = ({ tags, handleToggleSidebar, toggle }) => {
|
||||
const SideBar = ({ tags, collections, handleToggleSidebar, toggle }) => {
|
||||
const sortedTags = tags.sort((a, b) => {
|
||||
const A = a.toLowerCase(),
|
||||
B = b.toLowerCase();
|
||||
|
@ -19,6 +20,19 @@ const SideBar = ({ tags, handleToggleSidebar, toggle }) => {
|
|||
if (A > B) return 1;
|
||||
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 (
|
||||
<ProSidebar
|
||||
toggled={toggle}
|
||||
|
@ -27,26 +41,47 @@ const SideBar = ({ tags, handleToggleSidebar, toggle }) => {
|
|||
className="sidebar"
|
||||
>
|
||||
<SidebarHeader>
|
||||
<h1>LinkWarden</h1>
|
||||
<h3>LinkWarden</h3>
|
||||
</SidebarHeader>
|
||||
<SidebarContent className="sidebar-content">
|
||||
<Menu iconShape="circle">
|
||||
<MenuItem icon={<h2 className="sidebar-icon"></h2>}>
|
||||
<MenuItem icon={<h2 className="sidebar-icon"></h2>}>
|
||||
<Link to="/">
|
||||
<h3 className="menu-item">All</h3>
|
||||
<div className="menu-item">All</div>
|
||||
</Link>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem icon={<h2 className="sidebar-icon"></h2>}>
|
||||
<Link to="/collections/Unsorted">
|
||||
<div className="menu-item">Unsorted</div>
|
||||
</Link>
|
||||
</MenuItem>
|
||||
|
||||
<SubMenu
|
||||
icon={<h2 className="sidebar-icon"></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"></div>} key={i}>
|
||||
<Link className="sidebar-entity" to={path}>{e}</Link>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</SubMenu>
|
||||
|
||||
<SubMenu
|
||||
icon={<h2 className="sidebar-icon"></h2>}
|
||||
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) => {
|
||||
const path = `/tags/${e}`;
|
||||
return (
|
||||
<MenuItem prefix={"#"} key={i}>
|
||||
<Link to={path}>{e}</Link>
|
||||
<MenuItem prefix={<div className="sidebar-item-prefix">#</div>} key={i}>
|
||||
<Link className="sidebar-entity" to={path}>{e}</Link>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import CreatableSelect from "react-select/creatable";
|
||||
|
||||
// lightMode ? "Black" : "White"
|
||||
export default function TagSelection({ setTags, tags, tag = [], lightMode }) {
|
||||
const customStyles = {
|
||||
container: (provided) => ({
|
||||
|
@ -13,16 +12,19 @@ export default function TagSelection({ setTags, tags, tag = [], lightMode }) {
|
|||
color: "#a9a9a9",
|
||||
}),
|
||||
|
||||
option: (provided) => ({
|
||||
...provided,
|
||||
':before': {
|
||||
content: '"#"',
|
||||
marginRight: 8,
|
||||
},
|
||||
}),
|
||||
|
||||
multiValueRemove: (provided) => ({
|
||||
...provided,
|
||||
color: "gray",
|
||||
}),
|
||||
|
||||
indicatorSeparator: (provided) => ({
|
||||
...provided,
|
||||
display: "none",
|
||||
}),
|
||||
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
border: "solid",
|
||||
|
@ -41,6 +43,14 @@ export default function TagSelection({ setTags, tags, tag = [], lightMode }) {
|
|||
color: lightMode ? "rgb(64, 64, 64)" : "white",
|
||||
}),
|
||||
|
||||
multiValueLabel: (provided) => ({
|
||||
...provided,
|
||||
':before': {
|
||||
content: '"#"',
|
||||
marginRight: 4,
|
||||
},
|
||||
}),
|
||||
|
||||
control: (provided, state) => ({
|
||||
...provided,
|
||||
background: lightMode ? "lightyellow" : "#273949",
|
||||
|
@ -61,6 +71,7 @@ export default function TagSelection({ setTags, tags, tag = [], lightMode }) {
|
|||
|
||||
return (
|
||||
<CreatableSelect
|
||||
className="select"
|
||||
defaultValue={defaultTags}
|
||||
styles={customStyles}
|
||||
isMulti
|
||||
|
|
|
@ -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;
|
|
@ -4,24 +4,24 @@ const filteredData = (
|
|||
filterCheckbox
|
||||
) => {
|
||||
return data.filter((e) => {
|
||||
const name = e.name.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const title = e.title.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const linkName = e.name.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const websiteTitle = e.title.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const tags = e.tag.some((e) => e.includes(searchQuery.toLowerCase()));
|
||||
|
||||
if (filterCheckbox === [true, true, true]) {
|
||||
return name || title || tags;
|
||||
if (filterCheckbox.every(e => e === true)) {
|
||||
return linkName || websiteTitle || tags;
|
||||
} else if (filterCheckbox[0] && filterCheckbox[2]) {
|
||||
return name || tags;
|
||||
return linkName || tags;
|
||||
} else if (filterCheckbox[0] && filterCheckbox[1]) {
|
||||
return name || title;
|
||||
return linkName || websiteTitle;
|
||||
} else if (filterCheckbox[2] && filterCheckbox[1]) {
|
||||
return tags || title;
|
||||
return tags || websiteTitle;
|
||||
} else if (filterCheckbox[0]) {
|
||||
return name;
|
||||
return linkName;
|
||||
} else if (filterCheckbox[1]) {
|
||||
return tags;
|
||||
return websiteTitle;
|
||||
} else if (filterCheckbox[2]) {
|
||||
return title;
|
||||
return tags;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ const addItem = async (
|
|||
name,
|
||||
link,
|
||||
tag,
|
||||
collection,
|
||||
reFetch,
|
||||
onExit,
|
||||
SetLoader,
|
||||
|
@ -36,6 +37,7 @@ const addItem = async (
|
|||
title: title,
|
||||
link: link,
|
||||
tag: tag,
|
||||
collection: collection,
|
||||
date: dateCreated,
|
||||
}),
|
||||
headers: {
|
||||
|
|
|
@ -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;
|
|
@ -1,7 +1,7 @@
|
|||
import { useParams } from "react-router-dom";
|
||||
import List from "../componets/List";
|
||||
|
||||
const Tags = ({ data, tags, SetLoader, lightMode, reFetch }) => {
|
||||
const Tags = ({ data, tags, collections, SetLoader, lightMode, reFetch }) => {
|
||||
const { tagId } = useParams();
|
||||
const dataWithMatchingTag = data.filter((e) => {
|
||||
return e.tag.includes(tagId);
|
||||
|
@ -13,6 +13,7 @@ const Tags = ({ data, tags, SetLoader, lightMode, reFetch }) => {
|
|||
lightMode={lightMode}
|
||||
data={dataWithMatchingTag}
|
||||
tags={tags}
|
||||
collections={collections}
|
||||
SetLoader={SetLoader}
|
||||
reFetch={reFetch}
|
||||
/>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
}
|
||||
|
||||
.content {
|
||||
padding: 0px 20px 20px 20px;
|
||||
padding: 0px 20px 0 20px;
|
||||
}
|
||||
|
||||
.head {
|
||||
|
@ -86,21 +86,11 @@ input:focus {
|
|||
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 {
|
||||
margin-left: 10px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.select {
|
||||
font-family: "Font Awesome 5 Free"
|
||||
}
|
|
@ -172,9 +172,9 @@
|
|||
}
|
||||
|
||||
.delete {
|
||||
margin: 20px 20px 20px 0px;
|
||||
background-color: #273949;
|
||||
float: right;
|
||||
margin-top: 20px;
|
||||
margin-left: 10px;
|
||||
display: inline;
|
||||
font-size: 1.1rem;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
@ -188,17 +188,13 @@
|
|||
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;
|
||||
background-color: #273949;
|
||||
background-color: rgba(255, 75, 75, 0.8);
|
||||
color: white;
|
||||
border: none;
|
||||
transition: background-color 0.1s;
|
||||
transition: box-shadow 0.1s;
|
||||
}
|
||||
|
||||
.delete:hover {
|
||||
background-color: rgba(255, 75, 75, 0.8);
|
||||
color: #d8d8d8;
|
||||
}
|
||||
|
||||
.delete:active {
|
||||
box-shadow: 0px 0px 10px rgb(255, 83, 140);
|
||||
}
|
||||
|
||||
|
@ -208,3 +204,20 @@
|
|||
opacity: 80%;
|
||||
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;
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
position: fixed;
|
||||
}
|
||||
|
||||
.sidebar h1 {
|
||||
.sidebar h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -17,18 +17,11 @@
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.pro-sidebar-layout {
|
||||
background: #384952;
|
||||
text-shadow: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 3px 10px;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 1px;
|
||||
border-radius: 14px;
|
||||
background-color: rgb(52, 121, 181);
|
||||
}
|
||||
|
||||
.sidebar-icon {
|
||||
|
@ -47,3 +40,16 @@
|
|||
.pro-inner-item {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
$sidebar-bg-color: #373737;
|
||||
$submenu-bg-color: #373737;
|
||||
|
||||
@import '~react-pro-sidebar/dist/scss/styles.scss';
|
|
@ -23,11 +23,6 @@ body {
|
|||
content: "";
|
||||
}
|
||||
|
||||
.delete {
|
||||
background-color: #1f2c38;
|
||||
color: #ffffffb6;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
background-color: #1f2c38;
|
||||
}
|
||||
|
@ -111,11 +106,6 @@ body {
|
|||
color: gray;
|
||||
}
|
||||
|
||||
.light .delete {
|
||||
background-color: lightyellow;
|
||||
color: rgb(176, 176, 176);
|
||||
}
|
||||
|
||||
.light input {
|
||||
background-color: lightyellow;
|
||||
color: black;
|
||||
|
|
Ŝarĝante…
Reference in New Issue