mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 22:53:25 +00:00
feat/fix/chore: refactor project structure + add favicon
- Changement de structure de fichier - Ajout des favicons des sites - Suppression et mise à jour de dépendances - Ajout React-Icons pour gérer les icons - Amélioration du l'UI
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"tabWidth": 4,
|
"tabWidth": 2,
|
||||||
"useTabs": true
|
"useTabs": false
|
||||||
}
|
}
|
||||||
16
components/BlockWrapper/BlockWrapper.tsx
Normal file
16
components/BlockWrapper/BlockWrapper.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { CSSProperties, ReactNode } from "react";
|
||||||
|
|
||||||
|
import styles from "./block-wrapper.module.scss";
|
||||||
|
|
||||||
|
interface BlockWrapperProps {
|
||||||
|
children: ReactNode;
|
||||||
|
style?: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BlockWrapper({ children, style }: BlockWrapperProps) {
|
||||||
|
return (
|
||||||
|
<section className={styles["block-wrapper"]} style={style}>
|
||||||
|
{children}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
components/BlockWrapper/block-wrapper.module.scss
Normal file
47
components/BlockWrapper/block-wrapper.module.scss
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
.block-wrapper {
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
& h4 {
|
||||||
|
user-select: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #bbb;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& ul {
|
||||||
|
animation: fadein 0.3s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
& ul li {
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
height: fit-content;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 7px 12px;
|
||||||
|
border: 1px solid #dadce0;
|
||||||
|
border-bottom: 2px solid #dadce0;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: 0.15s;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadein {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-15px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import LinkTag from "next/link";
|
|
||||||
|
|
||||||
import { Link } from "../../types";
|
|
||||||
|
|
||||||
import styles from "../../styles/home/categories.module.scss";
|
|
||||||
|
|
||||||
export default function Favorites({ favorites }: { favorites: Link[] }) {
|
|
||||||
return (
|
|
||||||
<div className={`${styles["block-wrapper"]} ${styles["favorites"]}`}>
|
|
||||||
<h4>Favoris</h4>
|
|
||||||
<ul className={styles["items"]}>
|
|
||||||
{favorites.length === 0 ? (
|
|
||||||
<NoFavLink />
|
|
||||||
) : (
|
|
||||||
favorites.map((link, key) => <LinkFavorite link={link} key={key} />)
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function NoFavLink(): JSX.Element {
|
|
||||||
return <li className={styles["no-fav-link"]}>Aucun favoris</li>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function LinkFavorite({ link }: { link: Link }): JSX.Element {
|
|
||||||
const { name, url, category } = link;
|
|
||||||
return (
|
|
||||||
<li className={styles["item"]}>
|
|
||||||
<LinkTag href={url} target={"_blank"} rel={"noreferrer"}>
|
|
||||||
{name}
|
|
||||||
<span className={styles["category"]}> - {category.name}</span>
|
|
||||||
</LinkTag>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { Session } from "next-auth";
|
|
||||||
import LinkTag from "next/link";
|
|
||||||
|
|
||||||
import Categories from "./Categories";
|
|
||||||
import Favorites from "./Favorites";
|
|
||||||
import UserCard from "./UserCard";
|
|
||||||
|
|
||||||
import { Category, Link } from "../../types";
|
|
||||||
|
|
||||||
import styles from "../../styles/home/categories.module.scss";
|
|
||||||
|
|
||||||
interface SideMenuProps {
|
|
||||||
categories: Category[];
|
|
||||||
favorites: Link[];
|
|
||||||
handleSelectCategory: (category: Category) => void;
|
|
||||||
categoryActive: Category;
|
|
||||||
session: Session;
|
|
||||||
}
|
|
||||||
export default function SideMenu({
|
|
||||||
categories,
|
|
||||||
favorites,
|
|
||||||
handleSelectCategory,
|
|
||||||
categoryActive,
|
|
||||||
session,
|
|
||||||
}: SideMenuProps) {
|
|
||||||
return (
|
|
||||||
<div className={styles["categories-wrapper"]}>
|
|
||||||
<Favorites favorites={favorites} />
|
|
||||||
<Categories
|
|
||||||
categories={categories}
|
|
||||||
categoryActive={categoryActive}
|
|
||||||
handleSelectCategory={handleSelectCategory}
|
|
||||||
/>
|
|
||||||
<MenuControls />
|
|
||||||
<UserCard session={session} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MenuControls() {
|
|
||||||
return (
|
|
||||||
<div className={styles["controls"]}>
|
|
||||||
<LinkTag href={"/category/create"}>Créer categorie</LinkTag>
|
|
||||||
<LinkTag href={"/link/create"}>Créer lien</LinkTag>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Session } from 'next-auth';
|
|
||||||
import { signOut } from 'next-auth/react';
|
|
||||||
import Image from 'next/image';
|
|
||||||
|
|
||||||
import styles from '../../styles/home/categories.module.scss';
|
|
||||||
|
|
||||||
export default function UserCard({ session }: { session: Session; }) {
|
|
||||||
return (
|
|
||||||
<div className={styles['user-card-wrapper']}>
|
|
||||||
<div className={styles['user-card']}>
|
|
||||||
<Image
|
|
||||||
src={session.user.image}
|
|
||||||
width={28}
|
|
||||||
height={28}
|
|
||||||
alt={`${session.user.name}'s avatar`}
|
|
||||||
/>
|
|
||||||
{session.user.name}
|
|
||||||
</div>
|
|
||||||
<button onClick={() => signOut({ callbackUrl: '/signin' })} className={styles['disconnect-btn']}>
|
|
||||||
Se déconnecter
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import Head from "next/head";
|
import { NextSeo } from "next-seo";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
import MessageManager from "./MessageManager";
|
import MessageManager from "./MessageManager/MessageManager";
|
||||||
|
|
||||||
import styles from "../styles/create.module.scss";
|
import styles from "../styles/create.module.scss";
|
||||||
|
|
||||||
import { config } from "../config";
|
|
||||||
|
|
||||||
interface FormProps {
|
interface FormProps {
|
||||||
title: string;
|
title: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
@@ -34,11 +32,7 @@ export default function Form({
|
|||||||
}: FormProps) {
|
}: FormProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<NextSeo title={title} />
|
||||||
<title>
|
|
||||||
{config.siteName} — {title}
|
|
||||||
</title>
|
|
||||||
</Head>
|
|
||||||
<div className={`App ${styles["create-app"]}`}>
|
<div className={`App ${styles["create-app"]}`}>
|
||||||
<h2>{title}</h2>
|
<h2>{title}</h2>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
|
|||||||
58
components/Links/LinkFavicon.tsx
Normal file
58
components/Links/LinkFavicon.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { TbLoader3 } from "react-icons/tb";
|
||||||
|
import { TfiWorld } from "react-icons/tfi";
|
||||||
|
|
||||||
|
import { faviconLinkBuilder } from "../../utils/link";
|
||||||
|
|
||||||
|
import styles from "./links.module.scss";
|
||||||
|
|
||||||
|
interface LinkFaviconProps {
|
||||||
|
url: string;
|
||||||
|
size?: number;
|
||||||
|
noMargin?: boolean;
|
||||||
|
}
|
||||||
|
export default function LinkFavicon({
|
||||||
|
url,
|
||||||
|
size = 32,
|
||||||
|
noMargin = false,
|
||||||
|
}: LinkFaviconProps) {
|
||||||
|
const [isFailed, setFailed] = useState<boolean>(false);
|
||||||
|
const [isLoading, setLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const setFallbackFavicon = () => setFailed(true);
|
||||||
|
const handleStopLoading = () => setLoading(false);
|
||||||
|
|
||||||
|
const { origin } = new URL(url);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles["favicon"]}
|
||||||
|
style={{ marginRight: !noMargin ? "1em" : "0" }}
|
||||||
|
>
|
||||||
|
{!isFailed ? (
|
||||||
|
<Image
|
||||||
|
src={faviconLinkBuilder(origin)}
|
||||||
|
onError={() => {
|
||||||
|
setFallbackFavicon();
|
||||||
|
handleStopLoading();
|
||||||
|
}}
|
||||||
|
onLoadingComplete={handleStopLoading}
|
||||||
|
height={size}
|
||||||
|
width={size}
|
||||||
|
alt="icon"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TfiWorld size={size} />
|
||||||
|
)}
|
||||||
|
{isLoading && (
|
||||||
|
<span
|
||||||
|
className={styles["favicon-loader"]}
|
||||||
|
style={{ height: `${size}px`, width: `${size}px` }}
|
||||||
|
>
|
||||||
|
<TbLoader3 size={size} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
81
components/Links/LinkItem.tsx
Normal file
81
components/Links/LinkItem.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import LinkTag from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
AiFillDelete,
|
||||||
|
AiFillEdit,
|
||||||
|
AiFillStar,
|
||||||
|
AiOutlineStar,
|
||||||
|
} from "react-icons/ai";
|
||||||
|
|
||||||
|
import { Link } from "../../types";
|
||||||
|
import LinkFavicon from "./LinkFavicon";
|
||||||
|
|
||||||
|
import styles from "./links.module.scss";
|
||||||
|
|
||||||
|
export default function LinkItem({ link }: { link: Link }) {
|
||||||
|
const { id, name, url, favorite } = link;
|
||||||
|
const [isFavorite, setFavorite] = useState(favorite);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={styles["link"]} key={id}>
|
||||||
|
<LinkFavicon url={url} />
|
||||||
|
<LinkTag href={url} target={"_blank"} rel={"noreferrer"}>
|
||||||
|
<span className={styles["link-name"]}>
|
||||||
|
{name} {isFavorite && <AiFillStar color="#ffc107" />}
|
||||||
|
</span>
|
||||||
|
<LinkItemURL url={url} />
|
||||||
|
</LinkTag>
|
||||||
|
<div className={styles["controls"]}>
|
||||||
|
<div onClick={() => setFavorite((v) => !v)} className={styles["edit"]}>
|
||||||
|
{isFavorite ? (
|
||||||
|
<AiFillStar color="#ffc107" />
|
||||||
|
) : (
|
||||||
|
<AiOutlineStar color="#ffc107" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<LinkTag
|
||||||
|
href={`/link/edit/${id}`}
|
||||||
|
className={styles["edit"]}
|
||||||
|
title="Edit link"
|
||||||
|
>
|
||||||
|
<AiFillEdit />
|
||||||
|
</LinkTag>
|
||||||
|
<LinkTag
|
||||||
|
href={`/link/remove/${id}`}
|
||||||
|
className={styles["remove"]}
|
||||||
|
title="Remove link"
|
||||||
|
>
|
||||||
|
<AiFillDelete color="red" />
|
||||||
|
</LinkTag>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LinkItemURL({ url }: { url: string }) {
|
||||||
|
try {
|
||||||
|
const { origin, pathname, search } = new URL(url);
|
||||||
|
let text = "";
|
||||||
|
|
||||||
|
if (pathname !== "/") {
|
||||||
|
text += pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search !== "") {
|
||||||
|
if (text === "") {
|
||||||
|
text += "/";
|
||||||
|
}
|
||||||
|
text += search;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={styles["link-url"]}>
|
||||||
|
{origin}
|
||||||
|
<span className={styles["url-pathname"]}>{text}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("error", error);
|
||||||
|
return <span className={styles["link-url"]}>{url}</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import LinkTag from "next/link";
|
import LinkTag from "next/link";
|
||||||
|
|
||||||
import { Category, Link } from "../../types";
|
import { Category } from "../../types";
|
||||||
|
import LinkItem from "./LinkItem";
|
||||||
|
|
||||||
import EditSVG from "../../public/icons/edit.svg";
|
import styles from "./links.module.scss";
|
||||||
import RemoveSVG from "../../public/icons/remove.svg";
|
|
||||||
|
|
||||||
import styles from "../../styles/home/links.module.scss";
|
|
||||||
|
|
||||||
export default function Links({ category }: { category: Category }) {
|
export default function Links({ category }: { category: Category }) {
|
||||||
if (category === null) {
|
if (category === null) {
|
||||||
@@ -24,7 +22,9 @@ export default function Links({ category }: { category: Category }) {
|
|||||||
<p>
|
<p>
|
||||||
Aucun lien pour <b>{category.name}</b>
|
Aucun lien pour <b>{category.name}</b>
|
||||||
</p>
|
</p>
|
||||||
<LinkTag href="/link/create">Créer un lien</LinkTag>
|
<LinkTag href={`/link/create?categoryId=${category.id}`}>
|
||||||
|
Créer un lien
|
||||||
|
</LinkTag>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -43,54 +43,3 @@ export default function Links({ category }: { category: Category }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function LinkItem({ link }: { link: Link }) {
|
|
||||||
const { id, name, url, category } = link;
|
|
||||||
return (
|
|
||||||
<li className={styles["link"]} key={id}>
|
|
||||||
<LinkTag href={url} target={"_blank"} rel={"noreferrer"}>
|
|
||||||
<span className={styles["link-name"]}>
|
|
||||||
{name}
|
|
||||||
<span className={styles["link-category"]}> — {category.name}</span>
|
|
||||||
</span>
|
|
||||||
<LinkItemURL url={url} />
|
|
||||||
</LinkTag>
|
|
||||||
<div className={styles["controls"]}>
|
|
||||||
<LinkTag href={`/link/edit/${id}`} className={styles["edit"]}>
|
|
||||||
<EditSVG />
|
|
||||||
</LinkTag>
|
|
||||||
<LinkTag href={`/link/remove/${id}`} className={styles["remove"]}>
|
|
||||||
<RemoveSVG />
|
|
||||||
</LinkTag>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function LinkItemURL({ url }: { url: string }) {
|
|
||||||
try {
|
|
||||||
const { origin, pathname, search } = new URL(url);
|
|
||||||
let text = "";
|
|
||||||
|
|
||||||
if (pathname !== "/") {
|
|
||||||
text += pathname;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search !== "") {
|
|
||||||
if (text === "") {
|
|
||||||
text += "/";
|
|
||||||
}
|
|
||||||
text += search;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className={styles["link-url"]}>
|
|
||||||
{origin}
|
|
||||||
<span className={styles["url-pathname"]}>{text}</span>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("error", error);
|
|
||||||
return <span className={styles["link-url"]}>{url}</span>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
174
components/Links/links.module.scss
Normal file
174
components/Links/links.module.scss
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
.no-link,
|
||||||
|
.no-category {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
animation: fadein 0.3s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
& h2 {
|
||||||
|
color: #3f88c5;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
& .links-count {
|
||||||
|
color: #bbb;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.links {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
animation: fadein 0.3s both; // bug on drag start
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
height: fit-content;
|
||||||
|
width: 100%;
|
||||||
|
color: #3f88c5;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: 1px solid #dadce0;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
outline: 3px solid transparent;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
transition: 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
outline: 3px solid #82c5fede;
|
||||||
|
|
||||||
|
& .url-pathname {
|
||||||
|
animation: fadein 0.3s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .controls {
|
||||||
|
display: flex;
|
||||||
|
animation: fadein 0.3s both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.link > a {
|
||||||
|
height: 100%;
|
||||||
|
max-width: calc(100% - 50px);
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: 0.1s;
|
||||||
|
|
||||||
|
&,
|
||||||
|
&:hover {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .link-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .link-name .link-category {
|
||||||
|
color: #bbb;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .link-url {
|
||||||
|
width: 100%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #bbb;
|
||||||
|
font-size: 0.8em;
|
||||||
|
|
||||||
|
& .url-pathname {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.link .controls {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: 0.1s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.favicon {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.favicon-loader {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
animation: rotate 1s both reverse infinite linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
to {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
from {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadein {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-15px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import styles from '../styles/components/message-manager.module.scss';
|
|
||||||
|
|
||||||
interface MessageManagerProps {
|
|
||||||
error?: string;
|
|
||||||
success?: string;
|
|
||||||
info?: string;
|
|
||||||
}
|
|
||||||
export default function MessageManager({ error, success, info }: MessageManagerProps) {
|
|
||||||
return (<>
|
|
||||||
{info && (<div className={styles['info-msg']}>{info}</div>)}
|
|
||||||
{error && (<div className={styles['error-msg']}>{error}</div>)}
|
|
||||||
{success && (<div className={styles['success-msg']}>{success}</div>)}
|
|
||||||
</>);
|
|
||||||
}
|
|
||||||
20
components/MessageManager/MessageManager.tsx
Normal file
20
components/MessageManager/MessageManager.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import styles from "./message-manager.module.scss";
|
||||||
|
|
||||||
|
interface MessageManagerProps {
|
||||||
|
error?: string;
|
||||||
|
success?: string;
|
||||||
|
info?: string;
|
||||||
|
}
|
||||||
|
export default function MessageManager({
|
||||||
|
error,
|
||||||
|
success,
|
||||||
|
info,
|
||||||
|
}: MessageManagerProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{info && <div className={styles["info-msg"]}>{info}</div>}
|
||||||
|
{error && <div className={styles["error-msg"]}>{error}</div>}
|
||||||
|
{success && <div className={styles["success-msg"]}>{success}</div>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
31
components/SideMenu/Categories/Categories.tsx
Normal file
31
components/SideMenu/Categories/Categories.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Category } from "../../../types";
|
||||||
|
import CategoryItem from "./CategoryItem";
|
||||||
|
|
||||||
|
import styles from "./categories.module.scss";
|
||||||
|
|
||||||
|
interface CategoriesProps {
|
||||||
|
categories: Category[];
|
||||||
|
categoryActive: Category;
|
||||||
|
handleSelectCategory: (category: Category) => void;
|
||||||
|
}
|
||||||
|
export default function Categories({
|
||||||
|
categories,
|
||||||
|
categoryActive,
|
||||||
|
handleSelectCategory,
|
||||||
|
}: CategoriesProps) {
|
||||||
|
return (
|
||||||
|
<div className={styles["categories"]}>
|
||||||
|
<h4>Catégories</h4>
|
||||||
|
<ul className={styles["items"]}>
|
||||||
|
{categories.map((category, key) => (
|
||||||
|
<CategoryItem
|
||||||
|
category={category}
|
||||||
|
categoryActive={categoryActive}
|
||||||
|
handleSelectCategory={handleSelectCategory}
|
||||||
|
key={key}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,44 +1,17 @@
|
|||||||
import LinkTag from "next/link";
|
import LinkTag from "next/link";
|
||||||
|
import { AiFillDelete, AiFillEdit } from "react-icons/ai";
|
||||||
|
|
||||||
import styles from "../../styles/home/categories.module.scss";
|
import { Category } from "../../../types";
|
||||||
import { Category } from "../../types";
|
|
||||||
|
|
||||||
import EditSVG from "../../public/icons/edit.svg";
|
import styles from "./categories.module.scss";
|
||||||
import RemoveSVG from "../../public/icons/remove.svg";
|
|
||||||
|
|
||||||
interface CategoriesProps {
|
|
||||||
categories: Category[];
|
|
||||||
categoryActive: Category;
|
|
||||||
handleSelectCategory: (category: Category) => void;
|
|
||||||
}
|
|
||||||
export default function Categories({
|
|
||||||
categories,
|
|
||||||
categoryActive,
|
|
||||||
handleSelectCategory,
|
|
||||||
}: CategoriesProps) {
|
|
||||||
return (
|
|
||||||
<div className={`${styles["block-wrapper"]} ${styles["categories"]}`}>
|
|
||||||
<h4>Catégories</h4>
|
|
||||||
<ul className={styles["items"]}>
|
|
||||||
{categories.map((category, key) => (
|
|
||||||
<CategoryItem
|
|
||||||
category={category}
|
|
||||||
categoryActive={categoryActive}
|
|
||||||
handleSelectCategory={handleSelectCategory}
|
|
||||||
key={key}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CategoryItemProps {
|
interface CategoryItemProps {
|
||||||
category: Category;
|
category: Category;
|
||||||
categoryActive: Category;
|
categoryActive: Category;
|
||||||
handleSelectCategory: (category: Category) => void;
|
handleSelectCategory: (category: Category) => void;
|
||||||
}
|
}
|
||||||
function CategoryItem({
|
|
||||||
|
export default function CategoryItem({
|
||||||
category,
|
category,
|
||||||
categoryActive,
|
categoryActive,
|
||||||
handleSelectCategory,
|
handleSelectCategory,
|
||||||
@@ -63,13 +36,13 @@ function MenuOptions({ id }: { id: number }): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<div className={styles["menu-item"]}>
|
<div className={styles["menu-item"]}>
|
||||||
<LinkTag href={`/category/edit/${id}`} className={styles["option-edit"]}>
|
<LinkTag href={`/category/edit/${id}`} className={styles["option-edit"]}>
|
||||||
<EditSVG />
|
<AiFillEdit />
|
||||||
</LinkTag>
|
</LinkTag>
|
||||||
<LinkTag
|
<LinkTag
|
||||||
href={`/category/remove/${id}`}
|
href={`/category/remove/${id}`}
|
||||||
className={styles["option-remove"]}
|
className={styles["option-remove"]}
|
||||||
>
|
>
|
||||||
<RemoveSVG />
|
<AiFillDelete color="red" />
|
||||||
</LinkTag>
|
</LinkTag>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
90
components/SideMenu/Categories/categories.module.scss
Normal file
90
components/SideMenu/Categories/categories.module.scss
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
.categories {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items {
|
||||||
|
padding-right: 5px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #fff;
|
||||||
|
background: #3f88c5;
|
||||||
|
border-color: #3f88c5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.active) {
|
||||||
|
color: #3f88c5;
|
||||||
|
background: #f0eef6;
|
||||||
|
border-bottom: 2px solid #3f88c5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .menu-item .option-edit svg {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .content {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
& .name {
|
||||||
|
margin-right: 5px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .links-count {
|
||||||
|
min-width: fit-content;
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover .content {
|
||||||
|
width: calc(100% - 42px);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .menu-item {
|
||||||
|
height: 100%;
|
||||||
|
min-width: fit-content;
|
||||||
|
margin-left: 5px;
|
||||||
|
display: none;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
animation: fadein 0.3s both;
|
||||||
|
|
||||||
|
& > a {
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover .menu-item {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
components/SideMenu/Favorites/FavoriteItem.tsx
Normal file
19
components/SideMenu/Favorites/FavoriteItem.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import LinkTag from "next/link";
|
||||||
|
|
||||||
|
import { Link } from "../../../types";
|
||||||
|
import LinkFavicon from "../../Links/LinkFavicon";
|
||||||
|
|
||||||
|
import styles from "./favorites.module.scss";
|
||||||
|
|
||||||
|
export default function FavoriteItem({ link }: { link: Link }): JSX.Element {
|
||||||
|
const { name, url, category } = link;
|
||||||
|
return (
|
||||||
|
<li className={styles["item"]}>
|
||||||
|
<LinkTag href={url} target={"_blank"} rel={"noreferrer"}>
|
||||||
|
<LinkFavicon url={url} size={24} />
|
||||||
|
<span>{name}</span>
|
||||||
|
<span className={styles["category"]}> - {category.name}</span>
|
||||||
|
</LinkTag>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
components/SideMenu/Favorites/Favorites.tsx
Normal file
19
components/SideMenu/Favorites/Favorites.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Link } from "../../../types";
|
||||||
|
import FavoriteItem from "./FavoriteItem";
|
||||||
|
|
||||||
|
import styles from "./favorites.module.scss";
|
||||||
|
|
||||||
|
export default function Favorites({ favorites }: { favorites: Link[] }) {
|
||||||
|
return (
|
||||||
|
favorites.length !== 0 && (
|
||||||
|
<div className={styles["favorites"]}>
|
||||||
|
<h4>Favoris</h4>
|
||||||
|
<ul className={styles["items"]}>
|
||||||
|
{favorites.map((link, key) => (
|
||||||
|
<FavoriteItem link={link} key={key} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
55
components/SideMenu/Favorites/favorites.module.scss
Normal file
55
components/SideMenu/Favorites/favorites.module.scss
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
.favorites {
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
& h4 {
|
||||||
|
user-select: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #bbb;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.favorites ul.items li.item {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid #dadce0;
|
||||||
|
border-bottom: 2px solid #dadce0;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: 0.15s;
|
||||||
|
|
||||||
|
& a {
|
||||||
|
width: 100%;
|
||||||
|
color: inherit;
|
||||||
|
padding: 0.65em 1.15em;
|
||||||
|
border: 0 !important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .category {
|
||||||
|
color: #bbb;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #fff;
|
||||||
|
background: #3f88c5;
|
||||||
|
border-color: #3f88c5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.active) {
|
||||||
|
color: #3f88c5;
|
||||||
|
background: #f0eef6;
|
||||||
|
border-bottom: 2px solid #3f88c5;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
components/SideMenu/SideMenu.tsx
Normal file
62
components/SideMenu/SideMenu.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Session } from "next-auth";
|
||||||
|
import LinkTag from "next/link";
|
||||||
|
|
||||||
|
import BlockWrapper from "../BlockWrapper/BlockWrapper";
|
||||||
|
import Categories from "./Categories/Categories";
|
||||||
|
import Favorites from "./Favorites/Favorites";
|
||||||
|
import UserCard from "./UserCard/UserCard";
|
||||||
|
|
||||||
|
import { Category, Link } from "../../types";
|
||||||
|
|
||||||
|
import styles from "./sidemenu.module.scss";
|
||||||
|
|
||||||
|
interface SideMenuProps {
|
||||||
|
categories: Category[];
|
||||||
|
favorites: Link[];
|
||||||
|
handleSelectCategory: (category: Category) => void;
|
||||||
|
categoryActive: Category;
|
||||||
|
session: Session;
|
||||||
|
}
|
||||||
|
export default function SideMenu({
|
||||||
|
categories,
|
||||||
|
favorites,
|
||||||
|
handleSelectCategory,
|
||||||
|
categoryActive,
|
||||||
|
session,
|
||||||
|
}: SideMenuProps) {
|
||||||
|
return (
|
||||||
|
<div className={styles["side-menu"]}>
|
||||||
|
<BlockWrapper>
|
||||||
|
<Favorites favorites={favorites} />
|
||||||
|
</BlockWrapper>
|
||||||
|
<BlockWrapper style={{ minHeight: "0" }}>
|
||||||
|
<Categories
|
||||||
|
categories={categories}
|
||||||
|
categoryActive={categoryActive}
|
||||||
|
handleSelectCategory={handleSelectCategory}
|
||||||
|
/>
|
||||||
|
</BlockWrapper>
|
||||||
|
<BlockWrapper>
|
||||||
|
<MenuControls categoryActive={categoryActive} />
|
||||||
|
</BlockWrapper>
|
||||||
|
<BlockWrapper>
|
||||||
|
<UserCard session={session} />
|
||||||
|
</BlockWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MenuControls({
|
||||||
|
categoryActive,
|
||||||
|
}: {
|
||||||
|
categoryActive: SideMenuProps["categoryActive"];
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={styles["menu-controls"]}>
|
||||||
|
<LinkTag href={"/category/create"}>Créer categorie</LinkTag>
|
||||||
|
<LinkTag href={`/link/create?categoryId=${categoryActive.id}`}>
|
||||||
|
Créer lien
|
||||||
|
</LinkTag>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
components/SideMenu/UserCard/UserCard.tsx
Normal file
28
components/SideMenu/UserCard/UserCard.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Session } from "next-auth";
|
||||||
|
import { signOut } from "next-auth/react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { FiLogOut } from "react-icons/fi";
|
||||||
|
|
||||||
|
import styles from "./user-card.module.scss";
|
||||||
|
|
||||||
|
export default function UserCard({ session }: { session: Session }) {
|
||||||
|
return (
|
||||||
|
<div className={styles["user-card-wrapper"]}>
|
||||||
|
<div className={styles["user-card"]}>
|
||||||
|
<Image
|
||||||
|
src={session.user.image}
|
||||||
|
width={28}
|
||||||
|
height={28}
|
||||||
|
alt={`${session.user.name}'s avatar`}
|
||||||
|
/>
|
||||||
|
{session.user.name}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => signOut({ callbackUrl: "/signin" })}
|
||||||
|
className="reset"
|
||||||
|
>
|
||||||
|
<FiLogOut size={24} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
components/SideMenu/UserCard/user-card.module.scss
Normal file
33
components/SideMenu/UserCard/user-card.module.scss
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
.user-card-wrapper {
|
||||||
|
user-select: none;
|
||||||
|
height: fit-content;
|
||||||
|
width: 100%;
|
||||||
|
color: #333;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #dadce0;
|
||||||
|
padding: 7px 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
& .user-card {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
& img {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& button {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #3d7bab;
|
||||||
|
display: flex;
|
||||||
|
transition: 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
components/SideMenu/sidemenu.module.scss
Normal file
19
components/SideMenu/sidemenu.module.scss
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
.side-menu {
|
||||||
|
height: 100%;
|
||||||
|
width: 300px;
|
||||||
|
padding: 0 25px 0 10px;
|
||||||
|
border-right: 1px solid #dadce0;
|
||||||
|
margin-right: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-controls {
|
||||||
|
margin: 10px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
@@ -9,5 +9,3 @@ NEXTAUTH_SECRET=
|
|||||||
|
|
||||||
GOOGLE_CLIENT_ID=
|
GOOGLE_CLIENT_ID=
|
||||||
GOOGLE_CLIENT_SECRET=
|
GOOGLE_CLIENT_SECRET=
|
||||||
|
|
||||||
NEXT_PUBLIC_SITE_NAME=
|
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
module.exports = {
|
/** @type {import('next').NextConfig} */
|
||||||
reactStrictMode: true,
|
const config = {
|
||||||
images: {
|
|
||||||
domains: ['lh3.googleusercontent.com']
|
|
||||||
},
|
|
||||||
webpack(config) {
|
webpack(config) {
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
test: /\.svg$/,
|
test: /\.svg$/,
|
||||||
use: ["@svgr/webpack"]
|
use: ["@svgr/webpack"],
|
||||||
});
|
});
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
},
|
||||||
}
|
images: {
|
||||||
|
domains: ["localhost", "t3.gstatic.com", "lh3.googleusercontent.com"],
|
||||||
|
formats: ["image/webp"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
|
|||||||
510
package-lock.json
generated
510
package-lock.json
generated
@@ -12,13 +12,14 @@
|
|||||||
"next": "^13.3.0",
|
"next": "^13.3.0",
|
||||||
"next-auth": "^4.22.0",
|
"next-auth": "^4.22.0",
|
||||||
"next-connect": "^0.13.0",
|
"next-connect": "^0.13.0",
|
||||||
|
"next-seo": "^6.0.0",
|
||||||
|
"node-html-parser": "^6.1.5",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-confirm-alert": "^3.0.6",
|
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-icons": "^4.8.0",
|
||||||
"react-select": "^5.7.2",
|
"react-select": "^5.7.2",
|
||||||
"sass": "^1.62.0",
|
"sass": "^1.62.0",
|
||||||
"sharp": "^0.32.0",
|
|
||||||
"toastr": "^2.1.4"
|
"toastr": "^2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -2921,25 +2922,6 @@
|
|||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/base64-js": {
|
|
||||||
"version": "1.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
|
||||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||||
@@ -2948,16 +2930,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bl": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
|
||||||
"dependencies": {
|
|
||||||
"buffer": "^5.5.0",
|
|
||||||
"inherits": "^2.0.4",
|
|
||||||
"readable-stream": "^3.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/boolbase": {
|
"node_modules/boolbase": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
@@ -3011,29 +2983,6 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/buffer": {
|
|
||||||
"version": "5.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
|
||||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"base64-js": "^1.3.1",
|
|
||||||
"ieee754": "^1.1.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/busboy": {
|
"node_modules/busboy": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||||
@@ -3142,28 +3091,11 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chownr": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
|
||||||
},
|
|
||||||
"node_modules/client-only": {
|
"node_modules/client-only": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||||
},
|
},
|
||||||
"node_modules/color": {
|
|
||||||
"version": "4.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
|
||||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
|
||||||
"dependencies": {
|
|
||||||
"color-convert": "^2.0.1",
|
|
||||||
"color-string": "^1.9.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
@@ -3177,31 +3109,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
|
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
|
||||||
},
|
},
|
||||||
"node_modules/color-string": {
|
|
||||||
"version": "1.9.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
|
||||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
|
||||||
"dependencies": {
|
|
||||||
"color-name": "^1.0.0",
|
|
||||||
"simple-swizzle": "^0.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/color/node_modules/color-convert": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"color-name": "~1.1.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/color/node_modules/color-name": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
|
||||||
},
|
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@@ -3390,28 +3297,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/decompress-response": {
|
|
||||||
"version": "6.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
|
||||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"mimic-response": "^3.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/deep-extend": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
|
||||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
@@ -3459,14 +3344,6 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/detect-libc": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dir-glob": {
|
"node_modules/dir-glob": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||||
@@ -3562,14 +3439,6 @@
|
|||||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/end-of-stream": {
|
|
||||||
"version": "1.4.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
|
||||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"once": "^1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.12.0",
|
"version": "5.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
|
||||||
@@ -4239,14 +4108,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/expand-template": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@@ -4406,11 +4267,6 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fs-constants": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
|
|
||||||
},
|
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
@@ -4509,11 +4365,6 @@
|
|||||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/github-from-package": {
|
|
||||||
"version": "0.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
|
||||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
|
|
||||||
},
|
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "7.1.7",
|
"version": "7.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
|
||||||
@@ -4704,6 +4555,14 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/he": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||||
|
"bin": {
|
||||||
|
"he": "bin/he"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hoist-non-react-statics": {
|
"node_modules/hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
@@ -4712,25 +4571,6 @@
|
|||||||
"react-is": "^16.7.0"
|
"react-is": "^16.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ieee754": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
|
||||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.2.4",
|
"version": "5.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||||
@@ -4782,12 +4622,8 @@
|
|||||||
"node_modules/inherits": {
|
"node_modules/inherits": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
},
|
"dev": true
|
||||||
"node_modules/ini": {
|
|
||||||
"version": "1.3.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
|
||||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
|
|
||||||
},
|
},
|
||||||
"node_modules/internal-slot": {
|
"node_modules/internal-slot": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
@@ -5307,17 +5143,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mimic-response": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
@@ -5333,12 +5158,8 @@
|
|||||||
"node_modules/minimist": {
|
"node_modules/minimist": {
|
||||||
"version": "1.2.6",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||||
},
|
"dev": true
|
||||||
"node_modules/mkdirp-classic": {
|
|
||||||
"version": "0.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
|
||||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
|
||||||
},
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
@@ -5356,11 +5177,6 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/napi-build-utils": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
|
|
||||||
},
|
|
||||||
"node_modules/natural-compare": {
|
"node_modules/natural-compare": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||||
@@ -5454,35 +5270,24 @@
|
|||||||
"trouter": "^3.2.0"
|
"trouter": "^3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-abi": {
|
"node_modules/next-seo": {
|
||||||
"version": "3.24.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.24.0.tgz",
|
|
||||||
"integrity": "sha512-YPG3Co0luSu6GwOBsmIdGW6Wx0NyNDLg/hriIyDllVsNwnI6UeqaWShxC3lbH4LtEQUgoLP3XR1ndXiDAWvmRw==",
|
|
||||||
"dependencies": {
|
|
||||||
"semver": "^7.3.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-abi/node_modules/semver": {
|
|
||||||
"version": "7.3.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
|
||||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
|
||||||
"dependencies": {
|
|
||||||
"lru-cache": "^6.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-addon-api": {
|
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/next-seo/-/next-seo-6.0.0.tgz",
|
||||||
"integrity": "sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA=="
|
"integrity": "sha512-jKKt1p1z4otMA28AyeoAONixVjdYmgFCWwpEFtu+DwRHQDllVX3RjtyXbuCQiUZEfQ9rFPBpAI90vDeLZlMBdg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": "^8.1.1-canary.54 || >=9.0.0",
|
||||||
|
"react": ">=16.0.0",
|
||||||
|
"react-dom": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-html-parser": {
|
||||||
|
"version": "6.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.5.tgz",
|
||||||
|
"integrity": "sha512-fAaM511feX++/Chnhe475a0NHD8M7AxDInsqQpz6x63GRF7xYNdS8Vo5dKsIVPgsOvG7eioRRTZQnWBrhDHBSg==",
|
||||||
|
"dependencies": {
|
||||||
|
"css-select": "^5.1.0",
|
||||||
|
"he": "1.2.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.10",
|
"version": "2.0.10",
|
||||||
@@ -5643,6 +5448,7 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
@@ -5852,31 +5658,6 @@
|
|||||||
"preact": ">=10"
|
"preact": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prebuild-install": {
|
|
||||||
"version": "7.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
|
||||||
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
|
|
||||||
"dependencies": {
|
|
||||||
"detect-libc": "^2.0.0",
|
|
||||||
"expand-template": "^2.0.3",
|
|
||||||
"github-from-package": "0.0.0",
|
|
||||||
"minimist": "^1.2.3",
|
|
||||||
"mkdirp-classic": "^0.5.3",
|
|
||||||
"napi-build-utils": "^1.0.1",
|
|
||||||
"node-abi": "^3.3.0",
|
|
||||||
"pump": "^3.0.0",
|
|
||||||
"rc": "^1.2.7",
|
|
||||||
"simple-get": "^4.0.0",
|
|
||||||
"tar-fs": "^2.0.0",
|
|
||||||
"tunnel-agent": "^0.6.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"prebuild-install": "bin.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@@ -5923,15 +5704,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||||
},
|
},
|
||||||
"node_modules/pump": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
|
||||||
"dependencies": {
|
|
||||||
"end-of-stream": "^1.1.0",
|
|
||||||
"once": "^1.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
||||||
@@ -5961,28 +5733,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/rc": {
|
|
||||||
"version": "1.2.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
|
||||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
|
||||||
"dependencies": {
|
|
||||||
"deep-extend": "^0.6.0",
|
|
||||||
"ini": "~1.3.0",
|
|
||||||
"minimist": "^1.2.0",
|
|
||||||
"strip-json-comments": "~2.0.1"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"rc": "cli.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rc/node_modules/strip-json-comments": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||||
@@ -5994,15 +5744,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-confirm-alert": {
|
|
||||||
"version": "3.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-confirm-alert/-/react-confirm-alert-3.0.6.tgz",
|
|
||||||
"integrity": "sha512-rplP6Ed9ZSNd0KFV5BUzk4EPQ77BxsrayllBXGFuA8xPXc7sbBjgU5KUrNpl7aWFmP7mXRlVXfuy1IT5DbffYw==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=18.0.0",
|
|
||||||
"react-dom": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||||
@@ -6015,6 +5756,14 @@
|
|||||||
"react": "^18.2.0"
|
"react": "^18.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-icons": {
|
||||||
|
"version": "4.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz",
|
||||||
|
"integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
@@ -6055,19 +5804,6 @@
|
|||||||
"react-dom": ">=16.6.0"
|
"react-dom": ">=16.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/readable-stream": {
|
|
||||||
"version": "3.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
|
||||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
|
||||||
"dependencies": {
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"string_decoder": "^1.1.1",
|
|
||||||
"util-deprecate": "^1.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
@@ -6308,42 +6044,6 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sharp": {
|
|
||||||
"version": "0.32.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.0.tgz",
|
|
||||||
"integrity": "sha512-yLAypVcqj1toSAqRSwbs86nEzfyZVDYqjuUX8grhFpeij0DDNagKJXELS/auegDBRDg1XBtELdOGfo2X1cCpeA==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"dependencies": {
|
|
||||||
"color": "^4.2.3",
|
|
||||||
"detect-libc": "^2.0.1",
|
|
||||||
"node-addon-api": "^6.0.0",
|
|
||||||
"prebuild-install": "^7.1.1",
|
|
||||||
"semver": "^7.3.8",
|
|
||||||
"simple-get": "^4.0.1",
|
|
||||||
"tar-fs": "^2.1.1",
|
|
||||||
"tunnel-agent": "^0.6.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.15.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/sharp/node_modules/semver": {
|
|
||||||
"version": "7.3.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
|
||||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
|
||||||
"dependencies": {
|
|
||||||
"lru-cache": "^6.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
@@ -6379,62 +6079,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/simple-concat": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/simple-get": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"decompress-response": "^6.0.0",
|
|
||||||
"once": "^1.3.1",
|
|
||||||
"simple-concat": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/simple-swizzle": {
|
|
||||||
"version": "0.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
|
||||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
|
||||||
"dependencies": {
|
|
||||||
"is-arrayish": "^0.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/simple-swizzle/node_modules/is-arrayish": {
|
|
||||||
"version": "0.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
|
||||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
|
||||||
},
|
|
||||||
"node_modules/slash": {
|
"node_modules/slash": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||||
@@ -6468,33 +6112,6 @@
|
|||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/string_decoder": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
|
||||||
"dependencies": {
|
|
||||||
"safe-buffer": "~5.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/string_decoder/node_modules/safe-buffer": {
|
|
||||||
"version": "5.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
|
||||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/string.prototype.matchall": {
|
"node_modules/string.prototype.matchall": {
|
||||||
"version": "4.0.7",
|
"version": "4.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz",
|
||||||
@@ -6683,32 +6300,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar-fs": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
|
||||||
"dependencies": {
|
|
||||||
"chownr": "^1.1.1",
|
|
||||||
"mkdirp-classic": "^0.5.2",
|
|
||||||
"pump": "^3.0.0",
|
|
||||||
"tar-stream": "^2.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tar-stream": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"bl": "^4.0.3",
|
|
||||||
"end-of-stream": "^1.4.1",
|
|
||||||
"fs-constants": "^1.0.0",
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"readable-stream": "^3.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/text-table": {
|
"node_modules/text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||||
@@ -6813,17 +6404,6 @@
|
|||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/tunnel-agent": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
|
||||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
|
||||||
"dependencies": {
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
@@ -6973,11 +6553,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/util-deprecate": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
|
||||||
},
|
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
"version": "8.3.2",
|
"version": "8.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
@@ -7049,7 +6624,8 @@
|
|||||||
"node_modules/wrappy": {
|
"node_modules/wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
|||||||
@@ -14,13 +14,14 @@
|
|||||||
"next": "^13.3.0",
|
"next": "^13.3.0",
|
||||||
"next-auth": "^4.22.0",
|
"next-auth": "^4.22.0",
|
||||||
"next-connect": "^0.13.0",
|
"next-connect": "^0.13.0",
|
||||||
|
"next-seo": "^6.0.0",
|
||||||
|
"node-html-parser": "^6.1.5",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-confirm-alert": "^3.0.6",
|
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-icons": "^4.8.0",
|
||||||
"react-select": "^5.7.2",
|
"react-select": "^5.7.2",
|
||||||
"sass": "^1.62.0",
|
"sass": "^1.62.0",
|
||||||
"sharp": "^0.32.0",
|
|
||||||
"toastr": "^2.1.4"
|
"toastr": "^2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import Head from 'next/head';
|
import { NextSeo } from 'next-seo';
|
||||||
import styles from '../styles/error-page.module.scss';
|
|
||||||
|
|
||||||
import { config } from '../config';
|
import styles from '../styles/error-page.module.scss';
|
||||||
|
|
||||||
export default function Custom404() {
|
export default function Custom404() {
|
||||||
return (<>
|
return (<>
|
||||||
<Head>
|
<NextSeo
|
||||||
<title>{config.siteName} — Page introuvable</title>
|
title='Page introuvable'
|
||||||
</Head>
|
/>
|
||||||
<div className={styles['App']}>
|
<div className={styles['App']}>
|
||||||
<h1>404</h1>
|
<h1>404</h1>
|
||||||
<h2>Cette page est introuvable.</h2>
|
<h2>Cette page est introuvable.</h2>
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import Head from 'next/head';
|
import { NextSeo } from 'next-seo';
|
||||||
import styles from '../styles/error-page.module.scss';
|
|
||||||
|
|
||||||
import { config } from '../config';
|
import styles from '../styles/error-page.module.scss';
|
||||||
|
|
||||||
export default function Custom500() {
|
export default function Custom500() {
|
||||||
return (<>
|
return (<>
|
||||||
<Head>
|
<NextSeo
|
||||||
<title>{config.siteName} — Une erreur côté serveur est survenue</title>
|
title='Une erreur est survenue'
|
||||||
</Head>
|
/>
|
||||||
<div className={styles['App']}>
|
<div className={styles['App']}>
|
||||||
<h1>500</h1>
|
<h1>500</h1>
|
||||||
<h2>Une erreur côté serveur est survenue.</h2>
|
<h2>Une erreur côté serveur est survenue.</h2>
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
import { useEffect } from 'react';
|
import { SessionProvider } from "next-auth/react";
|
||||||
import { SessionProvider } from 'next-auth/react';
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import nProgress from 'nprogress';
|
import nProgress from "nprogress";
|
||||||
import 'nprogress/nprogress.css';
|
import "nprogress/nprogress.css";
|
||||||
|
|
||||||
import AuthRequired from '../components/AuthRequired';
|
import AuthRequired from "../components/AuthRequired";
|
||||||
|
|
||||||
import '../styles/globals.scss';
|
import { DefaultSeo } from "next-seo";
|
||||||
|
import "../styles/globals.scss";
|
||||||
|
|
||||||
function MyApp({
|
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
|
||||||
Component,
|
const router = useRouter();
|
||||||
pageProps: { session, ...pageProps }
|
|
||||||
}) {
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
useEffect(() => { // Chargement pages
|
useEffect(() => {
|
||||||
router.events.on('routeChangeStart', nProgress.start);
|
// Chargement pages
|
||||||
router.events.on('routeChangeComplete', nProgress.done);
|
router.events.on("routeChangeStart", nProgress.start);
|
||||||
router.events.on('routeChangeError', nProgress.done);
|
router.events.on("routeChangeComplete", nProgress.done);
|
||||||
|
router.events.on("routeChangeError", nProgress.done);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
router.events.off('routeChangeStart', nProgress.start);
|
router.events.off("routeChangeStart", nProgress.start);
|
||||||
router.events.off('routeChangeComplete', nProgress.done);
|
router.events.off("routeChangeComplete", nProgress.done);
|
||||||
router.events.off('routeChangeError', nProgress.done);
|
router.events.off("routeChangeError", nProgress.done);
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SessionProvider session={session}>
|
<SessionProvider session={session}>
|
||||||
{Component.authRequired ? (
|
<DefaultSeo titleTemplate="MyLinks — %s" defaultTitle="MyLinks" />
|
||||||
<AuthRequired>
|
{Component.authRequired ? (
|
||||||
<Component {...pageProps} />
|
<AuthRequired>
|
||||||
</AuthRequired>
|
<Component {...pageProps} />
|
||||||
) : (
|
</AuthRequired>
|
||||||
<Component {...pageProps} />
|
) : (
|
||||||
)}
|
<Component {...pageProps} />
|
||||||
</SessionProvider>
|
)}
|
||||||
);
|
</SessionProvider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyApp;
|
export default MyApp;
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { Html, Head, Main, NextScript } from 'next/document';
|
import { Head, Html, Main, NextScript } from 'next/document';
|
||||||
|
|
||||||
import { config } from '../config';
|
|
||||||
|
|
||||||
const Document = () => (
|
const Document = () => (
|
||||||
<Html lang='fr'>
|
<Html lang='fr'>
|
||||||
<Head>
|
<Head>
|
||||||
|
<meta name="theme-color" content="#f0eef6" />
|
||||||
<link
|
<link
|
||||||
href='https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&family=Rubik:ital,wght@0,400;0,700;1,400;1,700&display=swap'
|
href='https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&family=Rubik:ital,wght@0,400;0,700;1,400;1,700&display=swap'
|
||||||
rel='stylesheet'
|
rel='stylesheet'
|
||||||
/>
|
/>
|
||||||
<meta charSet='UTF-8' />
|
<meta charSet='UTF-8' />
|
||||||
</Head>
|
</Head>
|
||||||
<title>{config.siteName}</title>
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
Vous devez activer JavaScript pour utiliser ce site
|
Vous devez activer JavaScript pour utiliser ce site
|
||||||
|
|||||||
163
pages/api/favicon.ts
Normal file
163
pages/api/favicon.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { parse } from "node-html-parser";
|
||||||
|
|
||||||
|
const USER_AGENT =
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54";
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
const urlRequest = (req.query?.url as string) || "";
|
||||||
|
if (!urlRequest) {
|
||||||
|
throw new Error("URL's missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { favicon, type, size } = await downloadImageFromUrl(
|
||||||
|
urlRequest + "/favicon.ico"
|
||||||
|
);
|
||||||
|
if (size === 0) {
|
||||||
|
throw new Error("Empty favicon");
|
||||||
|
}
|
||||||
|
if (!isImage(type)) {
|
||||||
|
throw new Error("Favicon path does not return an image");
|
||||||
|
}
|
||||||
|
return sendImage({
|
||||||
|
content: favicon,
|
||||||
|
res,
|
||||||
|
type,
|
||||||
|
size,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestDocument = await makeRequest(urlRequest);
|
||||||
|
const text = await requestDocument.text();
|
||||||
|
|
||||||
|
const faviconPath = findFaviconPath(text);
|
||||||
|
if (!faviconPath) {
|
||||||
|
throw new Error("Unable to find favicon path");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBase64Image(faviconPath)) {
|
||||||
|
console.log("base64, convert it to buffer");
|
||||||
|
const buffer = convertBase64ToBuffer(faviconPath);
|
||||||
|
return sendImage({
|
||||||
|
content: buffer,
|
||||||
|
res,
|
||||||
|
type: "image/vnd.microsoft.icon",
|
||||||
|
size: buffer.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathWithoutFile = popLastSegment(requestDocument.url);
|
||||||
|
const finalUrl = buildFaviconUrl(faviconPath, pathWithoutFile);
|
||||||
|
|
||||||
|
const { favicon, type, size } = await downloadImageFromUrl(finalUrl);
|
||||||
|
if (!isImage(type)) {
|
||||||
|
throw new Error("Favicon path does not return an image");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendImage({
|
||||||
|
content: favicon,
|
||||||
|
res,
|
||||||
|
type,
|
||||||
|
size,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(404).send({ error: "Unable to retrieve favicon" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeRequest(url: string) {
|
||||||
|
const headers = new Headers();
|
||||||
|
headers.set("User-Agent", USER_AGENT);
|
||||||
|
|
||||||
|
const request = await fetch(url, { headers });
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadImageFromUrl(url: string): Promise<{
|
||||||
|
favicon: Buffer;
|
||||||
|
url: string;
|
||||||
|
type: string;
|
||||||
|
size: number;
|
||||||
|
}> {
|
||||||
|
const request = await makeRequest(url);
|
||||||
|
const blob = await request.blob();
|
||||||
|
|
||||||
|
return {
|
||||||
|
favicon: Buffer.from(await blob.arrayBuffer()),
|
||||||
|
url: request.url,
|
||||||
|
type: blob.type,
|
||||||
|
size: blob.size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendImage({
|
||||||
|
content,
|
||||||
|
res,
|
||||||
|
type,
|
||||||
|
size,
|
||||||
|
}: {
|
||||||
|
content: Buffer;
|
||||||
|
res: NextApiResponse;
|
||||||
|
type: string;
|
||||||
|
size: number;
|
||||||
|
}) {
|
||||||
|
res.setHeader("Content-Type", type);
|
||||||
|
res.setHeader("Content-Length", size);
|
||||||
|
res.send(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFaviconPath(text) {
|
||||||
|
const document = parse(text);
|
||||||
|
const links = document.querySelectorAll(
|
||||||
|
'link[rel="icon"], link[rel="shortcut icon"]'
|
||||||
|
);
|
||||||
|
const link = links.find(
|
||||||
|
(link) => !link.getAttribute("href").startsWith("data:image/")
|
||||||
|
);
|
||||||
|
if (!link) {
|
||||||
|
return console.warn("nothing, exit");
|
||||||
|
}
|
||||||
|
|
||||||
|
return link.getAttribute("href") || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function popLastSegment(url = "") {
|
||||||
|
const { href } = new URL(url);
|
||||||
|
const pathWithoutFile = href.split("/");
|
||||||
|
pathWithoutFile.pop();
|
||||||
|
return pathWithoutFile.join("/") || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFaviconUrl(faviconPath, pathWithoutFile) {
|
||||||
|
if (faviconPath.startsWith("http")) {
|
||||||
|
console.log("startsWith http, result", faviconPath);
|
||||||
|
return faviconPath;
|
||||||
|
} else if (faviconPath.startsWith("/")) {
|
||||||
|
console.log("startsWith /, result", pathWithoutFile + faviconPath);
|
||||||
|
return pathWithoutFile + faviconPath;
|
||||||
|
} else {
|
||||||
|
console.log("else, result", pathWithoutFile + "/" + faviconPath);
|
||||||
|
return pathWithoutFile + "/" + faviconPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isImage(type: string) {
|
||||||
|
return type.includes("image");
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBase64Image(data) {
|
||||||
|
return data.startsWith("data:image/");
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertBase64ToBuffer(base64 = ""): Buffer {
|
||||||
|
const buffer = Buffer.from(base64, "base64");
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
@@ -1,40 +1,40 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import axios from 'axios';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
import nProgress from 'nprogress';
|
import nProgress from 'nprogress';
|
||||||
import axios, { AxiosResponse } from 'axios';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { redirectWithoutClientCache } from '../../utils/client';
|
||||||
|
import { HandleAxiosError } from '../../utils/front';
|
||||||
|
|
||||||
import FormLayout from '../../components/FormLayout';
|
import FormLayout from '../../components/FormLayout';
|
||||||
import TextBox from '../../components/TextBox';
|
import TextBox from '../../components/TextBox';
|
||||||
|
|
||||||
import styles from '../../styles/create.module.scss';
|
import styles from '../../styles/create.module.scss';
|
||||||
import { HandleAxiosError } from '../../utils/front';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
|
|
||||||
function CreateCategory() {
|
function CreateCategory() {
|
||||||
|
const router = useRouter();
|
||||||
const info = useRouter().query?.info as string;
|
const info = useRouter().query?.info as string;
|
||||||
const [name, setName] = useState<string>('');
|
const [name, setName] = useState<string>('');
|
||||||
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [success, setSuccess] = useState<string | null>(null);
|
const [success, setSuccess] = useState<string | null>(null);
|
||||||
const [canSubmit, setCanSubmit] = useState<boolean>(false);
|
const [submitted, setSubmitted] = useState<boolean>(false);
|
||||||
|
const canSubmit = useMemo<boolean>(() => name.length !== 0 && !submitted, [name.length, submitted]);
|
||||||
useEffect(() => setCanSubmit(name.length !== 0), [name]);
|
|
||||||
|
|
||||||
const handleSubmit = async (event) => {
|
const handleSubmit = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setSuccess(null);
|
setSuccess(null);
|
||||||
setError(null);
|
setError(null);
|
||||||
setCanSubmit(false);
|
setSubmitted(false);
|
||||||
nProgress.start();
|
nProgress.start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = { name };
|
await axios.post('/api/category/create', { name });
|
||||||
const { data }: AxiosResponse<any> = await axios.post('/api/category/create', payload);
|
redirectWithoutClientCache(router, '');
|
||||||
setSuccess(data?.success || 'Categorie créée avec succès');
|
router.push('/')
|
||||||
setCanSubmit(false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(HandleAxiosError(error));
|
setError(HandleAxiosError(error));
|
||||||
setCanSubmit(true);
|
setSubmitted(true);
|
||||||
} finally {
|
} finally {
|
||||||
nProgress.done();
|
nProgress.done();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import nProgress from 'nprogress';
|
|
||||||
import axios, { AxiosResponse } from 'axios';
|
import axios, { AxiosResponse } from 'axios';
|
||||||
|
import nProgress from 'nprogress';
|
||||||
import { confirmAlert } from 'react-confirm-alert';
|
import { useEffect, useState } from 'react';
|
||||||
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
||||||
|
|
||||||
import FormLayout from '../../../components/FormLayout';
|
import FormLayout from '../../../components/FormLayout';
|
||||||
import TextBox from '../../../components/TextBox';
|
import TextBox from '../../../components/TextBox';
|
||||||
|
|
||||||
import styles from '../../../styles/create.module.scss';
|
|
||||||
|
|
||||||
import { Category } from '../../../types';
|
import { Category } from '../../../types';
|
||||||
|
import { prisma } from '../../../utils/back';
|
||||||
import { BuildCategory, HandleAxiosError } from '../../../utils/front';
|
import { BuildCategory, HandleAxiosError } from '../../../utils/front';
|
||||||
|
|
||||||
import { prisma } from '../../../utils/back';
|
import styles from '../../../styles/create.module.scss';
|
||||||
|
|
||||||
function EditCategory({ category }: { category: Category; }) {
|
function EditCategory({ category }: { category: Category; }) {
|
||||||
const [name, setName] = useState<string>(category.name);
|
const [name, setName] = useState<string>(category.name);
|
||||||
@@ -33,32 +28,21 @@ function EditCategory({ category }: { category: Category; }) {
|
|||||||
|
|
||||||
const handleSubmit = async (event) => {
|
const handleSubmit = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
confirmAlert({
|
setSuccess(null);
|
||||||
message: `Confirmer l'édition de la catégorie "${category.name}"`,
|
setError(null);
|
||||||
buttons: [{
|
setCanSubmit(false);
|
||||||
label: 'Yes',
|
nProgress.start();
|
||||||
onClick: async () => {
|
|
||||||
setSuccess(null);
|
|
||||||
setError(null);
|
|
||||||
setCanSubmit(false);
|
|
||||||
nProgress.start();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = { name };
|
const payload = { name };
|
||||||
const { data }: AxiosResponse<any> = await axios.put(`/api/category/edit/${category.id}`, payload);
|
const { data }: AxiosResponse<any> = await axios.put(`/api/category/edit/${category.id}`, payload);
|
||||||
setSuccess(data?.success || 'Catégorie modifiée avec succès');
|
setSuccess(data?.success || 'Catégorie modifiée avec succès');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(HandleAxiosError(error));
|
setError(HandleAxiosError(error));
|
||||||
} finally {
|
} finally {
|
||||||
setCanSubmit(true);
|
setCanSubmit(true);
|
||||||
nProgress.done();
|
nProgress.done();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}, {
|
|
||||||
label: 'No',
|
|
||||||
onClick: () => { }
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import nProgress from 'nprogress';
|
|
||||||
import axios, { AxiosResponse } from 'axios';
|
import axios, { AxiosResponse } from 'axios';
|
||||||
|
import nProgress from 'nprogress';
|
||||||
import { confirmAlert } from 'react-confirm-alert';
|
import { useEffect, useState } from 'react';
|
||||||
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
||||||
|
|
||||||
import FormLayout from '../../../components/FormLayout';
|
import FormLayout from '../../../components/FormLayout';
|
||||||
import TextBox from '../../../components/TextBox';
|
import TextBox from '../../../components/TextBox';
|
||||||
|
|
||||||
import styles from '../../../styles/create.module.scss';
|
|
||||||
|
|
||||||
import { Category } from '../../../types';
|
import { Category } from '../../../types';
|
||||||
import { BuildCategory, HandleAxiosError } from '../../../utils/front';
|
import { BuildCategory, HandleAxiosError } from '../../../utils/front';
|
||||||
|
|
||||||
import { prisma } from '../../../utils/back';
|
import { prisma } from '../../../utils/back';
|
||||||
|
|
||||||
|
import styles from '../../../styles/create.module.scss';
|
||||||
|
|
||||||
function RemoveCategory({ category }: { category: Category; }) {
|
function RemoveCategory({ category }: { category: Category; }) {
|
||||||
const [canSubmit, setCanSubmit] = useState<boolean>(false);
|
const [canSubmit, setCanSubmit] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -36,32 +31,21 @@ function RemoveCategory({ category }: { category: Category; }) {
|
|||||||
|
|
||||||
const handleSubmit = async (event) => {
|
const handleSubmit = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
confirmAlert({
|
setSuccess(null);
|
||||||
message: `Confirmer la suppression du lien "${category.name}"`,
|
setError(null);
|
||||||
buttons: [{
|
setCanSubmit(false);
|
||||||
label: 'Yes',
|
nProgress.start();
|
||||||
onClick: async () => {
|
|
||||||
setSuccess(null);
|
|
||||||
setError(null);
|
|
||||||
setCanSubmit(false);
|
|
||||||
nProgress.start();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data }: AxiosResponse<any> = await axios.delete(`/api/category/remove/${category.id}`);
|
const { data }: AxiosResponse<any> = await axios.delete(`/api/category/remove/${category.id}`);
|
||||||
setSuccess(data?.success || 'Categorie supprimée avec succès');
|
setSuccess(data?.success || 'Categorie supprimée avec succès');
|
||||||
setCanSubmit(false);
|
setCanSubmit(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(HandleAxiosError(error));
|
setError(HandleAxiosError(error));
|
||||||
setCanSubmit(true);
|
setCanSubmit(true);
|
||||||
} finally {
|
} finally {
|
||||||
nProgress.done();
|
nProgress.done();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}, {
|
|
||||||
label: 'No',
|
|
||||||
onClick: () => { }
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
|
|||||||
@@ -1,79 +1,68 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import Head from "next/head";
|
import { useState } from "react";
|
||||||
|
|
||||||
import Menu from "../components/Categories/SideMenu";
|
|
||||||
import Links from "../components/Links/Links";
|
import Links from "../components/Links/Links";
|
||||||
|
import SideMenu from "../components/SideMenu/SideMenu";
|
||||||
|
|
||||||
import { Category, Link } from "../types";
|
import { Category, Link } from "../types";
|
||||||
|
|
||||||
import { BuildCategory } from "../utils/front";
|
|
||||||
import { prisma } from "../utils/back";
|
import { prisma } from "../utils/back";
|
||||||
|
import { BuildCategory } from "../utils/front";
|
||||||
import { config } from "../config";
|
|
||||||
|
|
||||||
interface HomeProps {
|
interface HomeProps {
|
||||||
categories: Category[];
|
categories: Category[];
|
||||||
favorites: Link[];
|
favorites: Link[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function Home({ categories, favorites }: HomeProps) {
|
function Home({ categories, favorites }: HomeProps) {
|
||||||
const { data } = useSession({ required: true });
|
const { data } = useSession({ required: true });
|
||||||
const [categoryActive, setCategoryActive] = useState<Category | null>(
|
const [categoryActive, setCategoryActive] = useState<Category | null>(
|
||||||
categories?.[0]
|
categories?.[0]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelectCategory = (category: Category) =>
|
const handleSelectCategory = (category: Category) =>
|
||||||
setCategoryActive(category);
|
setCategoryActive(category);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="App">
|
||||||
<Head>
|
<SideMenu
|
||||||
<title>{config.siteName}</title>
|
categories={categories}
|
||||||
</Head>
|
favorites={favorites}
|
||||||
<div className="App">
|
handleSelectCategory={handleSelectCategory}
|
||||||
<Menu
|
categoryActive={categoryActive}
|
||||||
categories={categories}
|
session={data}
|
||||||
favorites={favorites}
|
/>
|
||||||
handleSelectCategory={handleSelectCategory}
|
<Links category={categoryActive} />
|
||||||
categoryActive={categoryActive}
|
</div>
|
||||||
session={data}
|
);
|
||||||
/>
|
|
||||||
<Links category={categoryActive} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getServerSideProps() {
|
export async function getServerSideProps() {
|
||||||
const categoriesDB = await prisma.category.findMany({
|
const categoriesDB = await prisma.category.findMany({
|
||||||
include: { links: true },
|
include: { links: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const favorites = [] as Link[];
|
const favorites = [] as Link[];
|
||||||
const categories = categoriesDB.map((categoryDB) => {
|
const categories = categoriesDB.map((categoryDB) => {
|
||||||
const category = BuildCategory(categoryDB);
|
const category = BuildCategory(categoryDB);
|
||||||
category.links.map((link) =>
|
category.links.map((link) => (link.favorite ? favorites.push(link) : null));
|
||||||
link.favorite ? favorites.push(link) : null
|
return category;
|
||||||
);
|
});
|
||||||
return category;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (categories.length === 0) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination:
|
|
||||||
"/category/create?info=Veuillez créer une catégorie",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (categories.length === 0) {
|
||||||
return {
|
return {
|
||||||
props: {
|
redirect: {
|
||||||
categories: JSON.parse(JSON.stringify(categories)),
|
destination: "/category/create",
|
||||||
favorites: JSON.parse(JSON.stringify(favorites)),
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
categories: JSON.parse(JSON.stringify(categories)),
|
||||||
|
favorites: JSON.parse(JSON.stringify(favorites)),
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Home.authRequired = true;
|
Home.authRequired = true;
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import nProgress from 'nprogress';
|
|
||||||
import axios, { AxiosResponse } from 'axios';
|
import axios, { AxiosResponse } from 'axios';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import nProgress from 'nprogress';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import FormLayout from '../../components/FormLayout';
|
|
||||||
import TextBox from '../../components/TextBox';
|
|
||||||
import Selector from '../../components/Selector';
|
|
||||||
import Checkbox from '../../components/Checkbox';
|
import Checkbox from '../../components/Checkbox';
|
||||||
|
import FormLayout from '../../components/FormLayout';
|
||||||
|
import Selector from '../../components/Selector';
|
||||||
|
import TextBox from '../../components/TextBox';
|
||||||
|
|
||||||
import styles from '../../styles/create.module.scss';
|
import { Category, Link } from '../../types';
|
||||||
|
|
||||||
import { Category } from '../../types';
|
|
||||||
import { BuildCategory, HandleAxiosError, IsValidURL } from '../../utils/front';
|
import { BuildCategory, HandleAxiosError, IsValidURL } from '../../utils/front';
|
||||||
|
|
||||||
import { prisma } from '../../utils/back';
|
import { prisma } from '../../utils/back';
|
||||||
|
|
||||||
|
import styles from '../../styles/create.module.scss';
|
||||||
|
|
||||||
function CreateLink({ categories }: { categories: Category[]; }) {
|
function CreateLink({ categories }: { categories: Category[]; }) {
|
||||||
const [name, setName] = useState<string>('');
|
const { query } = useRouter();
|
||||||
const [url, setUrl] = useState<string>('');
|
const categoryIdQuery = Number(query.categoryId?.[0]);
|
||||||
const [favorite, setFavorite] = useState<boolean>(false);
|
|
||||||
const [categoryId, setCategoryId] = useState<number | null>(categories?.[0].id || null);
|
|
||||||
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [name, setName] = useState<Link['name']>('');
|
||||||
const [success, setSuccess] = useState<string | null>(null);
|
const [url, setUrl] = useState<Link['url']>('');
|
||||||
const [canSubmit, setCanSubmit] = useState<boolean>(false);
|
const [favorite, setFavorite] = useState<Link['favorite']>(false);
|
||||||
|
const [categoryId, setCategoryId] = useState<Link['category']['id']>(categoryIdQuery || categories?.[0].id || null);
|
||||||
|
|
||||||
useEffect(() => {
|
const [error, setError] = useState<string>(null);
|
||||||
if (name !== '' && IsValidURL(url) && favorite !== null && categoryId !== null) {
|
const [success, setSuccess] = useState<string>(null);
|
||||||
setCanSubmit(true);
|
const [submitted, setSubmitted] = useState<boolean>(false);
|
||||||
} else {
|
|
||||||
setCanSubmit(false);
|
const canSubmit = useMemo<boolean>(
|
||||||
}
|
() => name !== '' && IsValidURL(url) && favorite !== null && categoryId !== null && !submitted,
|
||||||
}, [name, url, favorite, categoryId]);
|
[name, url, favorite, categoryId, submitted]
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubmit = async (event) => {
|
const handleSubmit = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setSuccess(null);
|
setSuccess(null);
|
||||||
setError(null);
|
setError(null);
|
||||||
setCanSubmit(false);
|
setSubmitted(false);
|
||||||
nProgress.start();
|
nProgress.start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -47,7 +47,7 @@ function CreateLink({ categories }: { categories: Category[]; }) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(HandleAxiosError(error));
|
setError(HandleAxiosError(error));
|
||||||
} finally {
|
} finally {
|
||||||
setCanSubmit(true);
|
setSubmitted(true);
|
||||||
nProgress.done();
|
nProgress.done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,39 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import axios, { AxiosResponse } from 'axios';
|
import axios, { AxiosResponse } from 'axios';
|
||||||
import nProgress from 'nprogress';
|
import nProgress from 'nprogress';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { confirmAlert } from 'react-confirm-alert';
|
import Checkbox from '../../../components/Checkbox';
|
||||||
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
||||||
|
|
||||||
import FormLayout from '../../../components/FormLayout';
|
import FormLayout from '../../../components/FormLayout';
|
||||||
import TextBox from '../../../components/TextBox';
|
import TextBox from '../../../components/TextBox';
|
||||||
import Checkbox from '../../../components/Checkbox';
|
|
||||||
|
|
||||||
import styles from '../../../styles/create.module.scss';
|
|
||||||
|
|
||||||
import { Link } from '../../../types';
|
import { Link } from '../../../types';
|
||||||
import { BuildLink, HandleAxiosError } from '../../../utils/front';
|
import { BuildLink, HandleAxiosError } from '../../../utils/front';
|
||||||
|
|
||||||
import { prisma } from '../../../utils/back';
|
import { prisma } from '../../../utils/back';
|
||||||
|
|
||||||
function RemoveLink({ link }: { link: Link; }) {
|
import styles from '../../../styles/create.module.scss';
|
||||||
|
|
||||||
|
function RemoveLink({ link }: { link: Link; }) {
|
||||||
const [canSubmit, setCanSubmit] = useState<boolean>(true);
|
const [canSubmit, setCanSubmit] = useState<boolean>(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [success, setSuccess] = useState<string | null>(null);
|
const [success, setSuccess] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleSubmit = (event) => {
|
const handleSubmit = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
confirmAlert({
|
setSuccess(null);
|
||||||
message: `Confirmer la suppression du lien "${link.name}"`,
|
setError(null);
|
||||||
buttons: [{
|
setCanSubmit(false);
|
||||||
label: 'Confirmer',
|
nProgress.start();
|
||||||
onClick: async () => {
|
|
||||||
setSuccess(null);
|
|
||||||
setError(null);
|
|
||||||
setCanSubmit(false);
|
|
||||||
nProgress.start();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data }: AxiosResponse<any> = await axios.delete(`/api/link/remove/${link.id}`);
|
const { data }: AxiosResponse<any> = await axios.delete(`/api/link/remove/${link.id}`);
|
||||||
setSuccess(data?.success || 'Lien supprimé avec succès');
|
setSuccess(data?.success || 'Lien supprimé avec succès');
|
||||||
setCanSubmit(false);
|
setCanSubmit(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(HandleAxiosError(error));
|
setError(HandleAxiosError(error));
|
||||||
setCanSubmit(true);
|
setCanSubmit(true);
|
||||||
} finally {
|
} finally {
|
||||||
nProgress.done();
|
nProgress.done();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}, {
|
|
||||||
label: 'Annuler',
|
|
||||||
onClick: () => { }
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Provider } from "next-auth/providers";
|
import { Provider } from "next-auth/providers";
|
||||||
import { getProviders, signIn, useSession } from "next-auth/react";
|
import { getProviders, signIn, useSession } from "next-auth/react";
|
||||||
import Head from "next/head";
|
import { NextSeo } from "next-seo";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import MessageManager from "../components/MessageManager";
|
import MessageManager from "../components/MessageManager/MessageManager";
|
||||||
import { config } from "../config";
|
|
||||||
|
|
||||||
import styles from "../styles/login.module.scss";
|
import styles from "../styles/login.module.scss";
|
||||||
|
|
||||||
@@ -26,9 +25,7 @@ export default function SignIn({ providers }: { providers: Provider[] }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<NextSeo title="Authentification" />
|
||||||
<title>{config.siteName} — Authentification</title>
|
|
||||||
</Head>
|
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<div className={styles["wrapper"]}>
|
<div className={styles["wrapper"]}>
|
||||||
<h2>Se connecter</h2>
|
<h2>Se connecter</h2>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ li {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button:not(.reset) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -172,6 +172,14 @@ select:not(.nostyle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reset {
|
||||||
|
background-color: inherit;
|
||||||
|
color: inherit;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1280px) {
|
@media (max-width: 1280px) {
|
||||||
.App {
|
.App {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,225 +0,0 @@
|
|||||||
.categories-wrapper {
|
|
||||||
height: 100%;
|
|
||||||
width: 300px;
|
|
||||||
padding: 0 25px 0 10px;
|
|
||||||
border-right: 1px solid #dadce0;
|
|
||||||
margin-right: 15px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
& .block-wrapper {
|
|
||||||
height: auto;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
& h4 {
|
|
||||||
user-select: none;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: #bbb;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// List items
|
|
||||||
& .items {
|
|
||||||
animation: fadein 0.3s both;
|
|
||||||
}
|
|
||||||
& .items .item {
|
|
||||||
position: relative;
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
height: fit-content;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 7px 12px;
|
|
||||||
border: 1px solid #dadce0;
|
|
||||||
border-bottom: 2px solid #dadce0;
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: 0.15s;
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: #fff;
|
|
||||||
background: #3f88c5;
|
|
||||||
border-color: #3f88c5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover:not(.active) {
|
|
||||||
color: #3f88c5;
|
|
||||||
background: #f0eef6;
|
|
||||||
border-bottom: 2px solid #3f88c5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Favorites
|
|
||||||
& .block-wrapper.favorites {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
|
|
||||||
& .items .item {
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .items .no-fav-link {
|
|
||||||
user-select: none;
|
|
||||||
text-align: center;
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 0.9em;
|
|
||||||
color: #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .items .item a {
|
|
||||||
width: 100%;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
padding: 7px 12px;
|
|
||||||
border: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .items .item .category {
|
|
||||||
color: #bbb;
|
|
||||||
font-size: 0.85em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Categories
|
|
||||||
& .block-wrapper.categories {
|
|
||||||
min-height: 0;
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
& .items {
|
|
||||||
padding-right: 5px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
|
|
||||||
& .item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
&.active .menu-item .option-edit svg {
|
|
||||||
fill: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .content {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
& .name {
|
|
||||||
margin-right: 5px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .links-count {
|
|
||||||
min-width: fit-content;
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: #bbb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover .content {
|
|
||||||
width: calc(100% - 42px);
|
|
||||||
}
|
|
||||||
|
|
||||||
& .menu-item {
|
|
||||||
height: 100%;
|
|
||||||
min-width: fit-content;
|
|
||||||
margin-left: 5px;
|
|
||||||
display: none;
|
|
||||||
gap: 2px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
animation: fadein 0.3s both;
|
|
||||||
|
|
||||||
& > a {
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
& svg {
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover .menu-item {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Controls
|
|
||||||
.controls {
|
|
||||||
margin: 10px 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
// User Card
|
|
||||||
& .user-card-wrapper {
|
|
||||||
position: relative;
|
|
||||||
user-select: none;
|
|
||||||
height: fit-content;
|
|
||||||
width: 100%;
|
|
||||||
color: #333;
|
|
||||||
background-color: #fff;
|
|
||||||
|
|
||||||
& .user-card {
|
|
||||||
border: 1px solid #dadce0;
|
|
||||||
padding: 7px 12px;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
& img {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& .disconnect-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
color: #fff;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .disconnect-btn {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadein {
|
|
||||||
0% {
|
|
||||||
transform: translateX(-15px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: translateX(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
.no-link,
|
|
||||||
.no-category {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
animation: fadein 0.3s both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.links-wrapper {
|
|
||||||
height: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: scroll;
|
|
||||||
|
|
||||||
& h2 {
|
|
||||||
color: #3f88c5;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
|
|
||||||
& .links-count {
|
|
||||||
color: #bbb;
|
|
||||||
font-weight: 300;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& .links {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
animation: fadein 0.3s both; // bug on drag start
|
|
||||||
}
|
|
||||||
|
|
||||||
& .links .link {
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
height: fit-content;
|
|
||||||
width: 100%;
|
|
||||||
color: #3f88c5;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border: 1px solid #dadce0;
|
|
||||||
border-bottom: 2px solid #dadce0;
|
|
||||||
border-radius: 3px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
transition: 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-bottom-color: #3f88c5;
|
|
||||||
background: #f0eef6;
|
|
||||||
|
|
||||||
& .url-pathname {
|
|
||||||
animation: fadein 0.3s both;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .controls {
|
|
||||||
display: flex;
|
|
||||||
animation: fadein 0.3s both;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& > a {
|
|
||||||
height: 100%;
|
|
||||||
max-width: calc(100% - 50px);
|
|
||||||
text-decoration: none;
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
transition: 0.1s;
|
|
||||||
|
|
||||||
&,
|
|
||||||
&:hover {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .link-name .link-category {
|
|
||||||
color: #bbb;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .link-url {
|
|
||||||
width: 100%;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
color: #bbb;
|
|
||||||
font-size: 0.8em;
|
|
||||||
|
|
||||||
& .url-pathname {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& .controls {
|
|
||||||
display: none;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
& > a {
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
& svg {
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadein {
|
|
||||||
0% {
|
|
||||||
transform: translateX(-15px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: translateX(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
60
test.js
Normal file
60
test.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
(async () => {
|
||||||
|
const request = await fetch("https://sdtream.sonnydata.fr");
|
||||||
|
const text = await request.text();
|
||||||
|
|
||||||
|
const faviconPath = findFaviconPath(text);
|
||||||
|
if (!faviconPath) {
|
||||||
|
return console.log("Unable to find favicon path");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBase64Image(faviconPath)) {
|
||||||
|
console.log("base64, convert it to buffer");
|
||||||
|
const buffer = convertBase64ToBuffer(faviconPath);
|
||||||
|
return console.log(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathWithoutFile = popLastSegment(request.url);
|
||||||
|
console.log("pathWithoutFile", pathWithoutFile);
|
||||||
|
|
||||||
|
const result = buildFaviconUrl(faviconPath, pathWithoutFile);
|
||||||
|
console.log(result);
|
||||||
|
})();
|
||||||
|
|
||||||
|
function findFaviconPath(text) {
|
||||||
|
const regex = /rel=['"](?:shortcut )?icon['"] href=['"]([^?'"]+)[?'"]/i;
|
||||||
|
const found = text.match(regex);
|
||||||
|
if (!found) {
|
||||||
|
return console.warn("nothing, exit");
|
||||||
|
}
|
||||||
|
|
||||||
|
const faviconPath = found?.[1];
|
||||||
|
return faviconPath || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function popLastSegment(url = "") {
|
||||||
|
const { href } = new URL(url);
|
||||||
|
const pathWithoutFile = href.split("/");
|
||||||
|
pathWithoutFile.pop();
|
||||||
|
return pathWithoutFile.join("/") || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFaviconUrl(faviconPath, pathWithoutFile) {
|
||||||
|
if (faviconPath.startsWith("http")) {
|
||||||
|
console.log("startsWith http, result", faviconPath);
|
||||||
|
return faviconPath;
|
||||||
|
} else if (faviconPath.startsWith("/")) {
|
||||||
|
console.log("startsWith /, result", pathWithoutFile + faviconPath);
|
||||||
|
return pathWithoutFile + faviconPath;
|
||||||
|
} else {
|
||||||
|
console.log("else, result", pathWithoutFile + "/" + faviconPath);
|
||||||
|
return pathWithoutFile + "/" + faviconPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBase64Image(data) {
|
||||||
|
return data.startsWith("data:image/");
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertBase64ToBuffer(base64 = "") {
|
||||||
|
return new Buffer.from(base64, "base64");
|
||||||
|
}
|
||||||
@@ -1,31 +1,20 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
"allowJs": true,
|
||||||
"dom.iterable",
|
"skipLibCheck": true,
|
||||||
"esnext"
|
"strict": false,
|
||||||
],
|
"forceConsistentCasingInFileNames": true,
|
||||||
"allowJs": true,
|
"noEmit": true,
|
||||||
"skipLibCheck": true,
|
"esModuleInterop": true,
|
||||||
"strict": false,
|
"module": "esnext",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"moduleResolution": "node",
|
||||||
"noEmit": true,
|
"resolveJsonModule": true,
|
||||||
"esModuleInterop": true,
|
"isolatedModules": true,
|
||||||
"module": "esnext",
|
"jsx": "preserve",
|
||||||
"moduleResolution": "node",
|
"incremental": true
|
||||||
"resolveJsonModule": true,
|
},
|
||||||
"isolatedModules": true,
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
"jsx": "preserve",
|
"exclude": ["node_modules"]
|
||||||
"incremental": true
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"next-env.d.ts",
|
|
||||||
"**/*.ts",
|
|
||||||
"**/*.tsx"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
7
utils/client.ts
Normal file
7
utils/client.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { NextRouter } from 'next/router';
|
||||||
|
|
||||||
|
export function redirectWithoutClientCache(router: NextRouter, url: string) {
|
||||||
|
router.push(url, undefined, {
|
||||||
|
unstable_skipClientCache: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
3
utils/link.ts
Normal file
3
utils/link.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function faviconLinkBuilder(origin: string, size: number = 32) {
|
||||||
|
return `http://localhost:3000/api/favicon?url=${origin}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user