diff --git a/components/Modal/Collection/DeleteCollection.tsx b/components/Modal/Collection/DeleteCollection.tsx index 58941d7..0af00b2 100644 --- a/components/Modal/Collection/DeleteCollection.tsx +++ b/components/Modal/Collection/DeleteCollection.tsx @@ -82,7 +82,7 @@ export default function DeleteCollection({
-

+

To confirm, type " {collection.name} " in the box below: diff --git a/components/Modal/Link/AddOrEditLink.tsx b/components/Modal/Link/AddOrEditLink.tsx index 7b72a20..942f92a 100644 --- a/components/Modal/Link/AddOrEditLink.tsx +++ b/components/Modal/Link/AddOrEditLink.tsx @@ -7,8 +7,8 @@ import useLinkStore from "@/store/links"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import RequiredBadge from "../../RequiredBadge"; import { useSession } from "next-auth/react"; -// import useCollectionStore from "@/store/collections"; -// import { useRouter } from "next/router"; +import useCollectionStore from "@/store/collections"; +import { useRouter } from "next/router"; import SubmitButton from "../../SubmitButton"; import { toast } from "react-hot-toast"; import Link from "next/link"; @@ -55,27 +55,40 @@ export default function AddOrEditLink({ const { updateLink, addLink } = useLinkStore(); - // const router = useRouter(); + const router = useRouter(); + const { collections } = useCollectionStore(); - // const { collections } = useCollectionStore(); + useEffect(() => { + if (method === "CREATE") { + if (router.query.id) { + const currentCollection = collections.find( + (e) => e.id == Number(router.query.id) + ); - // useEffect(() => { - // if (router.query.id) { - // const currentCollection = collections.find( - // (e) => e.id == Number(router.query.id) - // ); - - // if (currentCollection && currentCollection.ownerId) - // setLink({ - // ...link, - // collection: { - // id: currentCollection.id, - // name: currentCollection.name, - // ownerId: currentCollection.ownerId, - // }, - // }); - // } - // }, []); + if ( + currentCollection && + currentCollection.ownerId && + router.asPath.startsWith("/collections/") + ) + setLink({ + ...link, + collection: { + id: currentCollection.id, + name: currentCollection.name, + ownerId: currentCollection.ownerId, + }, + }); + } else + setLink({ + ...link, + collection: { + // id: , + name: "Unorganized", + ownerId: data?.user.id as number, + }, + }); + } + }, []); const setTags = (e: any) => { const tagNames = e.map((e: any) => { @@ -147,27 +160,29 @@ export default function AddOrEditLink({

Collection

- + {link.collection.name ? ( + + ) : null}
- ) : null} + ) : undefined} {optionsExpanded ? (
@@ -187,20 +202,22 @@ export default function AddOrEditLink({

Collection

- + {link.collection.name ? ( + + ) : undefined}
) : undefined} diff --git a/components/Modal/User/PrivacySettings.tsx b/components/Modal/User/PrivacySettings.tsx index 44188d3..b068363 100644 --- a/components/Modal/User/PrivacySettings.tsx +++ b/components/Modal/User/PrivacySettings.tsx @@ -50,7 +50,7 @@ export default function PrivacySettings({ return wordsArray; }; - const postJSONFile = async (e: any) => { + const postBookmarkFile = async (e: any) => { const file: File = e.target.files[0]; if (file) { @@ -156,9 +156,7 @@ export default function PrivacySettings({
-

- Import/Export Data -

+

Import Data

@@ -201,11 +199,12 @@ export default function PrivacySettings({ ) : null}
- + {/* Commented out for now. */} + {/*
Export Data
- + */}
diff --git a/hooks/useDetectPageBottom.tsx b/hooks/useDetectPageBottom.tsx index 518620e..c383f08 100644 --- a/hooks/useDetectPageBottom.tsx +++ b/hooks/useDetectPageBottom.tsx @@ -5,21 +5,22 @@ const useDetectPageBottom = () => { useEffect(() => { const handleScroll = () => { - const offsetHeight = document.documentElement.offsetHeight; - const innerHeight = window.innerHeight; - const scrollTop = document.documentElement.scrollTop; + const totalHeight = document.documentElement.scrollHeight; + const scrolledHeight = window.scrollY + window.innerHeight; - const hasReachedBottom = offsetHeight - (innerHeight + scrollTop) <= 100; - - setReachedBottom(hasReachedBottom); + if (scrolledHeight >= totalHeight) { + setReachedBottom(true); + } }; window.addEventListener("scroll", handleScroll); - return () => window.removeEventListener("scroll", handleScroll); + return () => { + window.removeEventListener("scroll", handleScroll); + }; }, []); - return reachedBottom; + return { reachedBottom, setReachedBottom }; }; export default useDetectPageBottom; diff --git a/hooks/useLinks.tsx b/hooks/useLinks.tsx index 1d4ebf6..25d65b2 100644 --- a/hooks/useLinks.tsx +++ b/hooks/useLinks.tsx @@ -17,7 +17,7 @@ export default function useLinks( const { links, setLinks, resetLinks } = useLinkStore(); const router = useRouter(); - const hasReachedBottom = useDetectPageBottom(); + const { reachedBottom, setReachedBottom } = useDetectPageBottom(); const getLinks = async (isInitialCall: boolean, cursor?: number) => { const requestBody: LinkRequestQuery = { @@ -48,6 +48,8 @@ export default function useLinks( }, [router, sort, searchFilter]); useEffect(() => { - if (hasReachedBottom) getLinks(false, links?.at(-1)?.id); - }, [hasReachedBottom]); + if (reachedBottom) getLinks(false, links?.at(-1)?.id); + + setReachedBottom(false); + }, [reachedBottom]); } diff --git a/lib/api/controllers/links/updateLink.ts b/lib/api/controllers/links/updateLink.ts index 87954e1..13a9c53 100644 --- a/lib/api/controllers/links/updateLink.ts +++ b/lib/api/controllers/links/updateLink.ts @@ -8,6 +8,7 @@ export default async function updateLink( link: LinkIncludingShortenedCollectionAndTags, userId: number ) { + console.log(link); if (!link || !link.collection.id) return { response: "Please choose a valid link and collection.", diff --git a/lib/api/controllers/migration/getData.ts b/lib/api/controllers/migration/exportData.ts similarity index 88% rename from lib/api/controllers/migration/getData.ts rename to lib/api/controllers/migration/exportData.ts index 6b9e2b0..4d711f7 100644 --- a/lib/api/controllers/migration/getData.ts +++ b/lib/api/controllers/migration/exportData.ts @@ -1,6 +1,6 @@ import { prisma } from "@/lib/api/db"; -export default async function getData(userId: number) { +export default async function exportData(userId: number) { const user = await prisma.user.findUnique({ where: { id: userId }, include: { diff --git a/lib/api/controllers/migration/importData.ts b/lib/api/controllers/migration/importData.ts new file mode 100644 index 0000000..c7bc33e --- /dev/null +++ b/lib/api/controllers/migration/importData.ts @@ -0,0 +1,97 @@ +import { prisma } from "@/lib/api/db"; +import { Backup } from "@/types/global"; +import createFolder from "@/lib/api/storage/createFolder"; +import { JSDOM } from "jsdom"; + +export default async function importData(userId: number, rawData: string) { + try { + const dom = new JSDOM(rawData); + const document = dom.window.document; + + const folders = document.querySelectorAll("H3"); + + // @ts-ignore + for (const folder of folders) { + const findCollection = await prisma.user.findUnique({ + where: { + id: userId, + }, + select: { + collections: { + where: { + name: folder.textContent.trim(), + }, + }, + }, + }); + + const checkIfCollectionExists = findCollection?.collections[0]; + + let collectionId = findCollection?.collections[0]?.id; + + if (!checkIfCollectionExists || !collectionId) { + const newCollection = await prisma.collection.create({ + data: { + name: folder.textContent.trim(), + description: "", + color: "#0ea5e9", + isPublic: false, + ownerId: userId, + }, + }); + + createFolder({ filePath: `archives/${newCollection.id}` }); + + collectionId = newCollection.id; + } + + createFolder({ filePath: `archives/${collectionId}` }); + + const bookmarks = folder.nextElementSibling.querySelectorAll("A"); + for (const bookmark of bookmarks) { + await prisma.link.create({ + data: { + name: bookmark.textContent.trim(), + url: bookmark.getAttribute("HREF"), + tags: bookmark.getAttribute("TAGS") + ? { + connectOrCreate: bookmark + .getAttribute("TAGS") + .split(",") + .map((tag: string) => + tag + ? { + where: { + name_ownerId: { + name: tag.trim(), + ownerId: userId, + }, + }, + create: { + name: tag.trim(), + owner: { + connect: { + id: userId, + }, + }, + }, + } + : undefined + ), + } + : undefined, + description: bookmark.getAttribute("DESCRIPTION") + ? bookmark.getAttribute("DESCRIPTION") + : "", + collectionId: collectionId, + createdAt: new Date(), + }, + }); + } + } + } catch (err) { + console.log(err); + } + + return { response: "Success.", status: 200 }; +} diff --git a/lib/api/controllers/migration/postData.ts b/lib/api/controllers/migration/postData.ts deleted file mode 100644 index 2082e4c..0000000 --- a/lib/api/controllers/migration/postData.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { prisma } from "@/lib/api/db"; -import { Backup } from "@/types/global"; -import createFolder from "@/lib/api/storage/createFolder"; - -export default async function getData(userId: number, rawData: any) { - const data: Backup = JSON.parse(rawData); - - // Import collections - try { - data.collections.forEach(async (e) => { - e.name = e.name.trim(); - - const findCollection = await prisma.user.findUnique({ - where: { - id: userId, - }, - select: { - collections: { - where: { - name: e.name, - }, - }, - }, - }); - - const checkIfCollectionExists = findCollection?.collections[0]; - - let collectionId = findCollection?.collections[0]?.id; - - if (!checkIfCollectionExists) { - const newCollection = await prisma.collection.create({ - data: { - owner: { - connect: { - id: userId, - }, - }, - name: e.name, - description: e.description, - color: e.color, - }, - }); - - createFolder({ filePath: `archives/${newCollection.id}` }); - - collectionId = newCollection.id; - } - - // Import Links - e.links.forEach(async (e) => { - const newLink = await prisma.link.create({ - data: { - url: e.url, - name: e.name, - description: e.description, - collection: { - connect: { - id: collectionId, - }, - }, - tags: { - connectOrCreate: e.tags.map((tag) => ({ - where: { - name_ownerId: { - name: tag.name.trim(), - ownerId: userId, - }, - }, - create: { - name: tag.name.trim(), - owner: { - connect: { - id: userId, - }, - }, - }, - })), - }, - }, - }); - }); - }); - } catch (err) { - console.log(err); - } - - return { response: "Success.", status: 200 }; -} diff --git a/package.json b/package.json index 252a02b..9256da6 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "devDependencies": { "@playwright/test": "^1.35.1", "@types/bcrypt": "^5.0.0", + "@types/jsdom": "^21.1.3", "autoprefixer": "^10.4.14", "postcss": "^8.4.26", "prisma": "^5.1.0", diff --git a/pages/api/migration/index.ts b/pages/api/migration/index.ts index 9c15b2b..c082c3d 100644 --- a/pages/api/migration/index.ts +++ b/pages/api/migration/index.ts @@ -1,8 +1,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/pages/api/auth/[...nextauth]"; -import getData from "@/lib/api/controllers/migration/getData"; -import postData from "@/lib/api/controllers/migration/postData"; +import exportData from "@/lib/api/controllers/migration/exportData"; +import importData from "@/lib/api/controllers/migration/importData"; export default async function users(req: NextApiRequest, res: NextApiResponse) { const session = await getServerSession(req, res, authOptions); @@ -16,7 +16,7 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) { }); if (req.method === "GET") { - const data = await getData(session.user.id); + const data = await exportData(session.user.id); if (data.status === 200) return res @@ -25,7 +25,7 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) { .status(data.status) .json(data.response); } else if (req.method === "POST") { - const data = await postData(session.user.id, req.body); + const data = await importData(session.user.id, req.body); return res.status(data.status).json({ response: data.response }); } } diff --git a/pages/collections/[id].tsx b/pages/collections/[id].tsx index cb8ac03..2dd31cb 100644 --- a/pages/collections/[id].tsx +++ b/pages/collections/[id].tsx @@ -222,7 +222,7 @@ export default function Index() { - {links[0] ? ( + {links.some((e) => e.collectionId === Number(router.query.id)) ? (
{links .filter((e) => e.collectionId === Number(router.query.id)) diff --git a/yarn.lock b/yarn.lock index 023809e..71a70f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1466,6 +1466,15 @@ resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d" integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA== +"@types/jsdom@^21.1.3": + version "21.1.3" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-21.1.3.tgz#a88c5dc65703e1b10b2a7839c12db49662b43ff0" + integrity sha512-1zzqSP+iHJYV4lB3lZhNBa012pubABkj9yG/GuXuf6LZH1cSPIJBqFDrm5JX65HHt6VOnNYdTui/0ySerRbMgA== + dependencies: + "@types/node" "*" + "@types/tough-cookie" "*" + parse5 "^7.0.0" + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -1521,6 +1530,11 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/tough-cookie@*": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.3.tgz#3d06b6769518450871fbc40770b7586334bdfd90" + integrity sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg== + "@typescript-eslint/parser@^5.42.0": version "5.49.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.49.0.tgz#d699734b2f20e16351e117417d34a2bc9d7c4b90" @@ -3946,7 +3960,7 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse5@^7.1.2: +parse5@^7.0.0, parse5@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==