diff --git a/bun.lockb b/bun.lockb index 1491662a..eb721f50 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/cli/run.ts b/cli/run.ts index efc864bc..c61727b3 100644 --- a/cli/run.ts +++ b/cli/run.ts @@ -32,12 +32,12 @@ async function readJazz() { ] } } - ], - forceGraphs: [ - { - connections: [{}] - } ] + // forceGraphs: [ + // { + // connections: [{}] + // } + // ] } }) @@ -49,18 +49,18 @@ async function readJazz() { /* * Log forceGraphs */ - const asJsonForceGraphs = globalGroup.root.forceGraphs.map(node => { - console.log({ node }, "node") - return { - name: node.name, - prettyName: node.prettyName, - connections: node.connections?.map(connection => { - return { - name: connection?.name - } - }) - } - }) + // const asJsonForceGraphs = globalGroup.root.forceGraphs.map(node => { + // console.log({ node }, "node") + // return { + // name: node.name, + // prettyName: node.prettyName, + // connections: node.connections?.map(connection => { + // return { + // name: connection?.name + // } + // }) + // } + // }) const asJson = globalGroup.root.topics?.map(node => { return { @@ -82,7 +82,7 @@ async function readJazz() { } }) - console.log({ asJsonForceGraphs }, "asJsonForceGraphs") + // console.log({ asJsonForceGraphs }, "asJsonForceGraphs") console.log({ asJson }, "asJson") } diff --git a/cli/seed.ts b/cli/seed.ts index 0145fa48..5b33a5c2 100644 --- a/cli/seed.ts +++ b/cli/seed.ts @@ -158,7 +158,7 @@ async function processJsonFiles(): Promise<[LinkManager, TopicJson[]]> { let files = await fs.readdir(directory) files.sort((a, b) => a.localeCompare(b)) // sort files alphabetically - files = files.slice(0, 1) // get only 1 file for testing + // files = files.slice(0, 1) // get only 1 file for testing for (const file of files) { if (path.extname(file) === ".json") { @@ -253,9 +253,11 @@ async function saveProcessedData(linkLists: Link[], topics: TopicJson[], chunkSi { owner: globalGroup } ) }, - { owner: globalGroup } + { owner: globalGroup, unique: { topicName: topic.name } } ) + // console.log("Created topic", topic.name, topicModel.id, "in group", globalGroup.id, topicModel._raw.core.header) + if (!topic.latestGlobalGuide) { console.error("No sections found in", topic.name) return @@ -391,7 +393,7 @@ async function saveForceGraph(connectionLists: Connection[], forceGraphs: ForceG prettyName: forceGraph.prettyName, connections: ListOfConnections.create([], { owner: globalGroup }) }, - { owner: globalGroup } + { owner: globalGroup, unique: { forceGraphName: forceGraph.name } } ) forceGraph.connections.map(connection => { diff --git a/package.json b/package.json index 70c25964..ce4c3e68 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "web" ], "dependencies": { - "jazz-nodejs": "^0.7.34", + "jazz-nodejs": "0.7.35-unique.2", "react-icons": "^5.3.0" }, "devDependencies": { diff --git a/web/components/routes/topics/detail/TopicDetailRoute.tsx b/web/components/routes/topics/detail/TopicDetailRoute.tsx index 6843b7c5..8b7b4ee4 100644 --- a/web/components/routes/topics/detail/TopicDetailRoute.tsx +++ b/web/components/routes/topics/detail/TopicDetailRoute.tsx @@ -1,23 +1,53 @@ "use client" -import React from "react" +import React, { useMemo } from "react" import { TopicDetailHeader } from "./Header" import { TopicSections } from "./partials/topic-sections" import { useLinkNavigation } from "./use-link-navigation" import { useTopicData } from "@/hooks/use-topic-data" import { atom } from "jotai" -import { useAccount } from "@/lib/providers/jazz-provider" +import { useAccount, useCoState } from "@/lib/providers/jazz-provider" +import { Link, Topic } from "@/lib/schema" +import { PublicGlobalGroup } from "@/lib/schema/master/public-group" +import { ID } from "jazz-tools" +import { LinkItem } from "./partials/link-item" interface TopicDetailRouteProps { topicName: string } export const openPopoverForIdAtom = atom(null) +const GLOBAL_GROUP_ID = process.env.NEXT_PUBLIC_JAZZ_GLOBAL_GROUP as ID export function TopicDetailRoute({ topicName }: TopicDetailRouteProps) { const { me } = useAccount({ root: { personalLinks: [] } }) - const { topic, allLinks } = useTopicData(topicName) - const { activeIndex, setActiveIndex, containerRef, linkRefs } = useLinkNavigation(allLinks) + + // const { topic, allLinks, transformedData } = useTopicData(topicName, me) + + // const findTopic = useMemo(() => me && Topic.findUnique({ topicName }, GLOBAL_GROUP_ID, me), [me]) + // const topic = useCoState(Topic, findTopic, { latestGlobalGuide: { sections: [{ links: [] }] } }) + const topic = useCoState(Topic, "co_z729EvE8fQEgMGNGaR4HKa1Jfir" as ID, {}) + + const transformedData = useMemo(() => { + if (!topic?.latestGlobalGuide?.sections) return [] + + return topic.latestGlobalGuide.sections.flatMap(section => [ + { type: "section", data: section }, + ...(section?.links?.filter(link => !!link?.url).map(link => ({ type: "link", data: link })) || []) + ]) + }, [topic?.latestGlobalGuide?.sections]) + + // console.log({ transformedData}, "transformedData") + + // const allLinks = useMemo(() => { + // if (!topic?.latestGlobalGuide?.sections) return [] + + // return topic.latestGlobalGuide.sections.flatMap( + // section => section?.links?.filter((link): link is Link => !!link?.url) ?? [] + // ) + // }, [topic?.latestGlobalGuide?.sections]) + + // const { activeIndex, setActiveIndex, containerRef, linkRefs } = useLinkNavigation(allLinks) if (!topic || !me) { return null @@ -26,7 +56,7 @@ export function TopicDetailRoute({ topicName }: TopicDetailRouteProps) { return (
- + /> */} + +
+
+
+ {transformedData?.map((data, index) => + data.type === "section" ? ( +
acc + (s?.links?.length || 0), 0)} + // linkRefs={linkRefs} + // me={me} + // personalLinks={personalLinks} + /> + ) : ( + { + linkRefs.current[startIndex + index] = el + }} + me={me} + personalLinks={personalLinks} + /> + ) + )} +
+
+
) } diff --git a/web/components/routes/topics/detail/partials/link-item.tsx b/web/components/routes/topics/detail/partials/link-item.tsx index 1e119bd3..0cd1a6c1 100644 --- a/web/components/routes/topics/detail/partials/link-item.tsx +++ b/web/components/routes/topics/detail/partials/link-item.tsx @@ -72,6 +72,7 @@ export const LinkItem = React.memo( toast.success("Link learning state updated", defaultToast) } } else { + console.log(personalLinks.toJSON(), link.title) const slug = generateUniqueSlug(personalLinks.toJSON(), link.title) const newPersonalLink = PersonalLink.create( { diff --git a/web/components/routes/topics/detail/use-link-navigation.ts b/web/components/routes/topics/detail/use-link-navigation.ts index 5726626a..58f25eb1 100644 --- a/web/components/routes/topics/detail/use-link-navigation.ts +++ b/web/components/routes/topics/detail/use-link-navigation.ts @@ -1,3 +1,83 @@ +// import { useState, useRef, useCallback, useEffect } from "react" +// import { Link as LinkSchema, Section as SectionSchema } from "@/lib/schema" +// import { ensureUrlProtocol } from "@/lib/utils" + +// interface TransformedDataItem { +// type: string +// data: T | null +// } + +// type TransformedData = Array | TransformedDataItem> + +// interface UseLinkNavigationProps { +// transformedData: TransformedData +// } + +// export function useLinkNavigation({ transformedData }: UseLinkNavigationProps) { +// const [activeIndex, setActiveIndex] = useState(-1) +// const containerRef = useRef(null) +// const linkRefs = useRef<(HTMLLIElement | null)[]>([]) + +// const allLinks = transformedData.filter( +// (item): item is TransformedDataItem => item.type === "link" && item.data !== null +// ) + +// const scrollToLink = useCallback((index: number) => { +// if (linkRefs.current[index] && containerRef.current) { +// const linkElement = linkRefs.current[index] +// const container = containerRef.current + +// const linkRect = linkElement?.getBoundingClientRect() +// const containerRect = container.getBoundingClientRect() + +// if (linkRect && containerRect) { +// if (linkRect.bottom > containerRect.bottom) { +// container.scrollTop += linkRect.bottom - containerRect.bottom +// } else if (linkRect.top < containerRect.top) { +// container.scrollTop -= containerRect.top - linkRect.top +// } +// } +// } +// }, []) + +// const handleKeyDown = useCallback( +// (e: KeyboardEvent) => { +// if (e.key === "ArrowDown") { +// e.preventDefault() +// setActiveIndex(prevIndex => { +// const newIndex = (prevIndex + 1) % allLinks.length +// scrollToLink(newIndex) +// return newIndex +// }) +// } else if (e.key === "ArrowUp") { +// e.preventDefault() +// setActiveIndex(prevIndex => { +// const newIndex = (prevIndex - 1 + allLinks.length) % allLinks.length +// scrollToLink(newIndex) +// return newIndex +// }) +// } else if (e.key === "Enter" && activeIndex !== -1) { +// const linkItem = allLinks[activeIndex] +// if (linkItem && linkItem.data) { +// window.open(ensureUrlProtocol(linkItem.data.url), "_blank") +// } +// } +// }, +// [activeIndex, allLinks, scrollToLink] +// ) + +// useEffect(() => { +// window.addEventListener("keydown", handleKeyDown) +// return () => window.removeEventListener("keydown", handleKeyDown) +// }, [handleKeyDown]) + +// useEffect(() => { +// linkRefs.current = linkRefs.current.slice(0, allLinks.length) +// }, [allLinks]) + +// return { activeIndex, setActiveIndex, containerRef, linkRefs } +// } + import { useState, useRef, useCallback, useEffect } from "react" import { Link as LinkSchema } from "@/lib/schema" import { ensureUrlProtocol } from "@/lib/utils" diff --git a/web/hooks/use-topic-data.ts b/web/hooks/use-topic-data.ts index 80b8efd9..7af01116 100644 --- a/web/hooks/use-topic-data.ts +++ b/web/hooks/use-topic-data.ts @@ -2,20 +2,22 @@ import { useMemo } from "react" import { useCoState } from "@/lib/providers/jazz-provider" import { PublicGlobalGroup } from "@/lib/schema/master/public-group" import { ID } from "jazz-tools" -import { Link } from "@/lib/schema" +import { LaAccount, Link, Topic } from "@/lib/schema" const GLOBAL_GROUP_ID = process.env.NEXT_PUBLIC_JAZZ_GLOBAL_GROUP as ID -export function useTopicData(topicName: string) { - const group = useCoState(PublicGlobalGroup, GLOBAL_GROUP_ID, { - root: { topics: [] } - }) +export function useTopicData(topicName: string, me?: LaAccount) { + const findTopic = useMemo(() => me && Topic.findUnique({ topicName }, GLOBAL_GROUP_ID, me), [me]) + const topic = useCoState(Topic, findTopic, { latestGlobalGuide: { sections: [{ links: [{}] }] } }) - // const topic = useCoState(Topic, "co_zS3TH4Lkj5MK9GEehinxhjjNTxB" as ID, {}) - const topic = useMemo( - () => group?.root.topics.find(topic => topic?.name === topicName), - [group?.root.topics, topicName] - ) + const transformedData = useMemo(() => { + if (!topic?.latestGlobalGuide?.sections) return [] + + return topic.latestGlobalGuide.sections.flatMap(section => [ + { type: "section", data: section }, + ...(section?.links?.filter(link => !!link?.url).map(link => ({ type: "link", data: link })) || []) + ]) + }, [topic?.latestGlobalGuide?.sections]) const allLinks = useMemo(() => { if (!topic?.latestGlobalGuide?.sections) return [] @@ -25,5 +27,5 @@ export function useTopicData(topicName: string) { ) }, [topic?.latestGlobalGuide?.sections]) - return { topic, allLinks } + return { topic, allLinks, transformedData } } diff --git a/web/lib/providers/jazz-provider.tsx b/web/lib/providers/jazz-provider.tsx index e5b8fb80..b19a5a18 100644 --- a/web/lib/providers/jazz-provider.tsx +++ b/web/lib/providers/jazz-provider.tsx @@ -5,6 +5,7 @@ import { LaAccount } from "@/lib/schema" import { useClerk } from "@clerk/nextjs" import { createContext, useMemo, useState } from "react" import { AuthMethodCtx } from "jazz-react" +import { BrowserClerkAuth as X } from "jazz-react-auth-clerk" const Jazz = createJazzReactApp({ AccountSchema: LaAccount diff --git a/web/package.json b/web/package.json index 1a786c21..78a25065 100644 --- a/web/package.json +++ b/web/package.json @@ -1,111 +1,111 @@ { - "name": "web", - "version": "0.1.0", - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint", - "test": "jest" - }, - "dependencies": { - "@clerk/nextjs": "^5.3.7", - "@dnd-kit/core": "^6.1.0", - "@dnd-kit/sortable": "^8.0.0", - "@hookform/resolvers": "^3.9.0", - "@nothing-but/force-graph": "^0.7.3", - "@omit/react-confirm-dialog": "^1.1.5", - "@omit/react-fancy-switch": "^0.1.1", - "@radix-ui/react-avatar": "^1.1.0", - "@radix-ui/react-checkbox": "^1.1.1", - "@radix-ui/react-context-menu": "^2.2.1", - "@radix-ui/react-dialog": "^1.1.1", - "@radix-ui/react-dismissable-layer": "^1.1.0", - "@radix-ui/react-dropdown-menu": "^2.1.1", - "@radix-ui/react-focus-scope": "^1.1.0", - "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-popover": "^1.1.1", - "@radix-ui/react-scroll-area": "^1.1.0", - "@radix-ui/react-select": "^2.1.1", - "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-toggle": "^1.1.0", - "@radix-ui/react-tooltip": "^1.1.2", - "@tiptap/core": "^2.6.6", - "@tiptap/extension-blockquote": "^2.6.6", - "@tiptap/extension-bold": "^2.6.6", - "@tiptap/extension-bullet-list": "^2.6.6", - "@tiptap/extension-code": "^2.6.6", - "@tiptap/extension-code-block-lowlight": "^2.6.6", - "@tiptap/extension-document": "^2.6.6", - "@tiptap/extension-dropcursor": "^2.6.6", - "@tiptap/extension-focus": "^2.6.6", - "@tiptap/extension-gapcursor": "^2.6.6", - "@tiptap/extension-hard-break": "^2.6.6", - "@tiptap/extension-heading": "^2.6.6", - "@tiptap/extension-history": "^2.6.6", - "@tiptap/extension-horizontal-rule": "^2.6.6", - "@tiptap/extension-italic": "^2.6.6", - "@tiptap/extension-link": "^2.6.6", - "@tiptap/extension-list-item": "^2.6.6", - "@tiptap/extension-ordered-list": "^2.6.6", - "@tiptap/extension-paragraph": "^2.6.6", - "@tiptap/extension-placeholder": "^2.6.6", - "@tiptap/extension-strike": "^2.6.6", - "@tiptap/extension-task-item": "^2.6.6", - "@tiptap/extension-task-list": "^2.6.6", - "@tiptap/extension-text": "^2.6.6", - "@tiptap/extension-typography": "^2.6.6", - "@tiptap/pm": "^2.6.6", - "@tiptap/react": "^2.6.6", - "@tiptap/suggestion": "^2.6.6", - "axios": "^1.7.5", - "cheerio": "1.0.0", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.1", - "cmdk": "^1.0.0", - "date-fns": "^3.6.0", - "framer-motion": "^11.3.31", - "jazz-react": "0.7.35-new-auth.1", - "jazz-react-auth-clerk": "0.7.33-new-auth.1", - "jazz-tools": "0.7.35-new-auth.0", - "jotai": "^2.9.3", - "lowlight": "^3.1.0", - "lucide-react": "^0.429.0", - "next": "14.2.5", - "next-themes": "^0.3.0", - "nuqs": "^1.17.8", - "react": "^18.3.1", - "react-day-picker": "^9.0.8", - "react-dom": "^18.3.1", - "react-hook-form": "^7.53.0", - "react-textarea-autosize": "^8.5.3", - "react-use": "^17.5.1", - "slugify": "^1.6.6", - "sonner": "^1.5.0", - "streaming-markdown": "^0.0.14", - "tailwind-merge": "^2.5.2", - "tailwindcss-animate": "^1.0.7", - "ts-node": "^10.9.2", - "zod": "^3.23.8", - "zsa": "^0.6.0" - }, - "devDependencies": { - "@testing-library/jest-dom": "^6.5.0", - "@testing-library/react": "^16.0.1", - "@types/jest": "^29.5.12", - "@types/node": "^22.5.1", - "@types/react": "^18.3.5", - "@types/react-dom": "^18.3.0", - "eslint": "^8.57.0", - "eslint-config-next": "14.2.5", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", - "postcss": "^8.4.41", - "prettier-plugin-tailwindcss": "^0.6.6", - "tailwindcss": "^3.4.10", - "ts-jest": "^29.2.5", - "typescript": "^5.5.4" - } + "name": "web", + "version": "0.1.0", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "test": "jest" + }, + "dependencies": { + "@clerk/nextjs": "^5.3.7", + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/sortable": "^8.0.0", + "@hookform/resolvers": "^3.9.0", + "@nothing-but/force-graph": "^0.7.3", + "@omit/react-confirm-dialog": "^1.1.5", + "@omit/react-fancy-switch": "^0.1.1", + "@radix-ui/react-avatar": "^1.1.0", + "@radix-ui/react-checkbox": "^1.1.1", + "@radix-ui/react-context-menu": "^2.2.1", + "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dismissable-layer": "^1.1.0", + "@radix-ui/react-dropdown-menu": "^2.1.1", + "@radix-ui/react-focus-scope": "^1.1.0", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-popover": "^1.1.1", + "@radix-ui/react-scroll-area": "^1.1.0", + "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-toggle": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.2", + "@tiptap/core": "^2.6.6", + "@tiptap/extension-blockquote": "^2.6.6", + "@tiptap/extension-bold": "^2.6.6", + "@tiptap/extension-bullet-list": "^2.6.6", + "@tiptap/extension-code": "^2.6.6", + "@tiptap/extension-code-block-lowlight": "^2.6.6", + "@tiptap/extension-document": "^2.6.6", + "@tiptap/extension-dropcursor": "^2.6.6", + "@tiptap/extension-focus": "^2.6.6", + "@tiptap/extension-gapcursor": "^2.6.6", + "@tiptap/extension-hard-break": "^2.6.6", + "@tiptap/extension-heading": "^2.6.6", + "@tiptap/extension-history": "^2.6.6", + "@tiptap/extension-horizontal-rule": "^2.6.6", + "@tiptap/extension-italic": "^2.6.6", + "@tiptap/extension-link": "^2.6.6", + "@tiptap/extension-list-item": "^2.6.6", + "@tiptap/extension-ordered-list": "^2.6.6", + "@tiptap/extension-paragraph": "^2.6.6", + "@tiptap/extension-placeholder": "^2.6.6", + "@tiptap/extension-strike": "^2.6.6", + "@tiptap/extension-task-item": "^2.6.6", + "@tiptap/extension-task-list": "^2.6.6", + "@tiptap/extension-text": "^2.6.6", + "@tiptap/extension-typography": "^2.6.6", + "@tiptap/pm": "^2.6.6", + "@tiptap/react": "^2.6.6", + "@tiptap/suggestion": "^2.6.6", + "axios": "^1.7.7", + "cheerio": "1.0.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "cmdk": "^1.0.0", + "date-fns": "^3.6.0", + "framer-motion": "^11.3.31", + "jazz-react": "0.7.35-unique.2", + "jazz-react-auth-clerk": "0.7.33-new-auth.1", + "jazz-tools": "0.7.35-unique.2", + "jotai": "^2.9.3", + "lowlight": "^3.1.0", + "lucide-react": "^0.429.0", + "next": "14.2.5", + "next-themes": "^0.3.0", + "nuqs": "^1.18.0", + "react": "^18.3.1", + "react-day-picker": "^9.0.8", + "react-dom": "^18.3.1", + "react-hook-form": "^7.53.0", + "react-textarea-autosize": "^8.5.3", + "react-use": "^17.5.1", + "slugify": "^1.6.6", + "sonner": "^1.5.0", + "streaming-markdown": "^0.0.14", + "tailwind-merge": "^2.5.2", + "tailwindcss-animate": "^1.0.7", + "ts-node": "^10.9.2", + "zod": "^3.23.8", + "zsa": "^0.6.0" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.5.0", + "@testing-library/react": "^16.0.1", + "@types/jest": "^29.5.12", + "@types/node": "^22.5.2", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "eslint": "^8.57.0", + "eslint-config-next": "14.2.5", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "postcss": "^8.4.44", + "prettier-plugin-tailwindcss": "^0.6.6", + "tailwindcss": "^3.4.10", + "ts-jest": "^29.2.5", + "typescript": "^5.5.4" + } }