This commit is contained in:
Aslam H
2024-09-03 20:58:23 +07:00
parent c4d4afd4df
commit c4c565f7ac
10 changed files with 302 additions and 148 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -32,12 +32,12 @@ async function readJazz() {
] ]
} }
} }
],
forceGraphs: [
{
connections: [{}]
}
] ]
// forceGraphs: [
// {
// connections: [{}]
// }
// ]
} }
}) })
@@ -49,18 +49,18 @@ async function readJazz() {
/* /*
* Log forceGraphs * Log forceGraphs
*/ */
const asJsonForceGraphs = globalGroup.root.forceGraphs.map(node => { // const asJsonForceGraphs = globalGroup.root.forceGraphs.map(node => {
console.log({ node }, "node") // console.log({ node }, "node")
return { // return {
name: node.name, // name: node.name,
prettyName: node.prettyName, // prettyName: node.prettyName,
connections: node.connections?.map(connection => { // connections: node.connections?.map(connection => {
return { // return {
name: connection?.name // name: connection?.name
} // }
}) // })
} // }
}) // })
const asJson = globalGroup.root.topics?.map(node => { const asJson = globalGroup.root.topics?.map(node => {
return { return {
@@ -82,7 +82,7 @@ async function readJazz() {
} }
}) })
console.log({ asJsonForceGraphs }, "asJsonForceGraphs") // console.log({ asJsonForceGraphs }, "asJsonForceGraphs")
console.log({ asJson }, "asJson") console.log({ asJson }, "asJson")
} }

View File

@@ -158,7 +158,7 @@ async function processJsonFiles(): Promise<[LinkManager, TopicJson[]]> {
let files = await fs.readdir(directory) let files = await fs.readdir(directory)
files.sort((a, b) => a.localeCompare(b)) // sort files alphabetically 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) { for (const file of files) {
if (path.extname(file) === ".json") { if (path.extname(file) === ".json") {
@@ -253,9 +253,11 @@ async function saveProcessedData(linkLists: Link[], topics: TopicJson[], chunkSi
{ owner: globalGroup } { 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) { if (!topic.latestGlobalGuide) {
console.error("No sections found in", topic.name) console.error("No sections found in", topic.name)
return return
@@ -391,7 +393,7 @@ async function saveForceGraph(connectionLists: Connection[], forceGraphs: ForceG
prettyName: forceGraph.prettyName, prettyName: forceGraph.prettyName,
connections: ListOfConnections.create([], { owner: globalGroup }) connections: ListOfConnections.create([], { owner: globalGroup })
}, },
{ owner: globalGroup } { owner: globalGroup, unique: { forceGraphName: forceGraph.name } }
) )
forceGraph.connections.map(connection => { forceGraph.connections.map(connection => {

View File

@@ -11,7 +11,7 @@
"web" "web"
], ],
"dependencies": { "dependencies": {
"jazz-nodejs": "^0.7.34", "jazz-nodejs": "0.7.35-unique.2",
"react-icons": "^5.3.0" "react-icons": "^5.3.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,23 +1,53 @@
"use client" "use client"
import React from "react" import React, { useMemo } from "react"
import { TopicDetailHeader } from "./Header" import { TopicDetailHeader } from "./Header"
import { TopicSections } from "./partials/topic-sections" import { TopicSections } from "./partials/topic-sections"
import { useLinkNavigation } from "./use-link-navigation" import { useLinkNavigation } from "./use-link-navigation"
import { useTopicData } from "@/hooks/use-topic-data" import { useTopicData } from "@/hooks/use-topic-data"
import { atom } from "jotai" 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 { interface TopicDetailRouteProps {
topicName: string topicName: string
} }
export const openPopoverForIdAtom = atom<string | null>(null) export const openPopoverForIdAtom = atom<string | null>(null)
const GLOBAL_GROUP_ID = process.env.NEXT_PUBLIC_JAZZ_GLOBAL_GROUP as ID<PublicGlobalGroup>
export function TopicDetailRoute({ topicName }: TopicDetailRouteProps) { export function TopicDetailRoute({ topicName }: TopicDetailRouteProps) {
const { me } = useAccount({ root: { personalLinks: [] } }) 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<Topic>, {})
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) { if (!topic || !me) {
return null return null
@@ -26,7 +56,7 @@ export function TopicDetailRoute({ topicName }: TopicDetailRouteProps) {
return ( return (
<div className="flex h-full flex-auto flex-col"> <div className="flex h-full flex-auto flex-col">
<TopicDetailHeader topic={topic} /> <TopicDetailHeader topic={topic} />
<TopicSections {/* <TopicSections
topic={topic} topic={topic}
sections={topic.latestGlobalGuide?.sections} sections={topic.latestGlobalGuide?.sections}
activeIndex={activeIndex} activeIndex={activeIndex}
@@ -35,7 +65,45 @@ export function TopicDetailRoute({ topicName }: TopicDetailRouteProps) {
containerRef={containerRef} containerRef={containerRef}
me={me} me={me}
personalLinks={me.root.personalLinks} personalLinks={me.root.personalLinks}
/> /> */}
<div className="flex w-full flex-1 flex-col overflow-y-auto [scrollbar-gutter:stable]">
<div tabIndex={-1} className="outline-none">
<div className="flex flex-1 flex-col gap-4" role="listbox" aria-label="Topic sections">
{transformedData?.map((data, index) =>
data.type === "section" ? (
<Section
key={index}
section={data.data}
// key={sectionIndex}
// topic={topic}
// section={section}
// activeIndex={activeIndex}
// setActiveIndex={setActiveIndex}
// startIndex={sections.slice(0, sectionIndex).reduce((acc, s) => acc + (s?.links?.length || 0), 0)}
// linkRefs={linkRefs}
// me={me}
// personalLinks={personalLinks}
/>
) : (
<LinkItem
key={index}
link={link}
topic={topic}
isActive={activeIndex === startIndex + index}
index={startIndex + index}
setActiveIndex={setActiveIndex}
ref={el => {
linkRefs.current[startIndex + index] = el
}}
me={me}
personalLinks={personalLinks}
/>
)
)}
</div>
</div>
</div>
</div> </div>
) )
} }

View File

@@ -72,6 +72,7 @@ export const LinkItem = React.memo(
toast.success("Link learning state updated", defaultToast) toast.success("Link learning state updated", defaultToast)
} }
} else { } else {
console.log(personalLinks.toJSON(), link.title)
const slug = generateUniqueSlug(personalLinks.toJSON(), link.title) const slug = generateUniqueSlug(personalLinks.toJSON(), link.title)
const newPersonalLink = PersonalLink.create( const newPersonalLink = PersonalLink.create(
{ {

View File

@@ -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<T> {
// type: string
// data: T | null
// }
// type TransformedData<S, L> = Array<TransformedDataItem<S> | TransformedDataItem<L>>
// interface UseLinkNavigationProps<S, L> {
// transformedData: TransformedData<S, L>
// }
// export function useLinkNavigation({ transformedData }: UseLinkNavigationProps<SectionSchema, LinkSchema>) {
// const [activeIndex, setActiveIndex] = useState(-1)
// const containerRef = useRef<HTMLDivElement>(null)
// const linkRefs = useRef<(HTMLLIElement | null)[]>([])
// const allLinks = transformedData.filter(
// (item): item is TransformedDataItem<LinkSchema> => 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 { useState, useRef, useCallback, useEffect } from "react"
import { Link as LinkSchema } from "@/lib/schema" import { Link as LinkSchema } from "@/lib/schema"
import { ensureUrlProtocol } from "@/lib/utils" import { ensureUrlProtocol } from "@/lib/utils"

View File

@@ -2,20 +2,22 @@ import { useMemo } from "react"
import { useCoState } from "@/lib/providers/jazz-provider" import { useCoState } from "@/lib/providers/jazz-provider"
import { PublicGlobalGroup } from "@/lib/schema/master/public-group" import { PublicGlobalGroup } from "@/lib/schema/master/public-group"
import { ID } from "jazz-tools" 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<PublicGlobalGroup> const GLOBAL_GROUP_ID = process.env.NEXT_PUBLIC_JAZZ_GLOBAL_GROUP as ID<PublicGlobalGroup>
export function useTopicData(topicName: string) { export function useTopicData(topicName: string, me?: LaAccount) {
const group = useCoState(PublicGlobalGroup, GLOBAL_GROUP_ID, { const findTopic = useMemo(() => me && Topic.findUnique({ topicName }, GLOBAL_GROUP_ID, me), [me])
root: { topics: [] } const topic = useCoState(Topic, findTopic, { latestGlobalGuide: { sections: [{ links: [{}] }] } })
})
// const topic = useCoState(Topic, "co_zS3TH4Lkj5MK9GEehinxhjjNTxB" as ID<Topic>, {}) const transformedData = useMemo(() => {
const topic = useMemo( if (!topic?.latestGlobalGuide?.sections) return []
() => group?.root.topics.find(topic => topic?.name === topicName),
[group?.root.topics, topicName] 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(() => { const allLinks = useMemo(() => {
if (!topic?.latestGlobalGuide?.sections) return [] if (!topic?.latestGlobalGuide?.sections) return []
@@ -25,5 +27,5 @@ export function useTopicData(topicName: string) {
) )
}, [topic?.latestGlobalGuide?.sections]) }, [topic?.latestGlobalGuide?.sections])
return { topic, allLinks } return { topic, allLinks, transformedData }
} }

View File

@@ -5,6 +5,7 @@ import { LaAccount } from "@/lib/schema"
import { useClerk } from "@clerk/nextjs" import { useClerk } from "@clerk/nextjs"
import { createContext, useMemo, useState } from "react" import { createContext, useMemo, useState } from "react"
import { AuthMethodCtx } from "jazz-react" import { AuthMethodCtx } from "jazz-react"
import { BrowserClerkAuth as X } from "jazz-react-auth-clerk"
const Jazz = createJazzReactApp({ const Jazz = createJazzReactApp({
AccountSchema: LaAccount AccountSchema: LaAccount

View File

@@ -1,111 +1,111 @@
{ {
"name": "web", "name": "web",
"version": "0.1.0", "version": "0.1.0",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"test": "jest" "test": "jest"
}, },
"dependencies": { "dependencies": {
"@clerk/nextjs": "^5.3.7", "@clerk/nextjs": "^5.3.7",
"@dnd-kit/core": "^6.1.0", "@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@hookform/resolvers": "^3.9.0", "@hookform/resolvers": "^3.9.0",
"@nothing-but/force-graph": "^0.7.3", "@nothing-but/force-graph": "^0.7.3",
"@omit/react-confirm-dialog": "^1.1.5", "@omit/react-confirm-dialog": "^1.1.5",
"@omit/react-fancy-switch": "^0.1.1", "@omit/react-fancy-switch": "^0.1.1",
"@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-context-menu": "^2.2.1", "@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dismissable-layer": "^1.1.0", "@radix-ui/react-dismissable-layer": "^1.1.0",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-focus-scope": "^1.1.0", "@radix-ui/react-focus-scope": "^1.1.0",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0", "@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1", "@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.2",
"@tiptap/core": "^2.6.6", "@tiptap/core": "^2.6.6",
"@tiptap/extension-blockquote": "^2.6.6", "@tiptap/extension-blockquote": "^2.6.6",
"@tiptap/extension-bold": "^2.6.6", "@tiptap/extension-bold": "^2.6.6",
"@tiptap/extension-bullet-list": "^2.6.6", "@tiptap/extension-bullet-list": "^2.6.6",
"@tiptap/extension-code": "^2.6.6", "@tiptap/extension-code": "^2.6.6",
"@tiptap/extension-code-block-lowlight": "^2.6.6", "@tiptap/extension-code-block-lowlight": "^2.6.6",
"@tiptap/extension-document": "^2.6.6", "@tiptap/extension-document": "^2.6.6",
"@tiptap/extension-dropcursor": "^2.6.6", "@tiptap/extension-dropcursor": "^2.6.6",
"@tiptap/extension-focus": "^2.6.6", "@tiptap/extension-focus": "^2.6.6",
"@tiptap/extension-gapcursor": "^2.6.6", "@tiptap/extension-gapcursor": "^2.6.6",
"@tiptap/extension-hard-break": "^2.6.6", "@tiptap/extension-hard-break": "^2.6.6",
"@tiptap/extension-heading": "^2.6.6", "@tiptap/extension-heading": "^2.6.6",
"@tiptap/extension-history": "^2.6.6", "@tiptap/extension-history": "^2.6.6",
"@tiptap/extension-horizontal-rule": "^2.6.6", "@tiptap/extension-horizontal-rule": "^2.6.6",
"@tiptap/extension-italic": "^2.6.6", "@tiptap/extension-italic": "^2.6.6",
"@tiptap/extension-link": "^2.6.6", "@tiptap/extension-link": "^2.6.6",
"@tiptap/extension-list-item": "^2.6.6", "@tiptap/extension-list-item": "^2.6.6",
"@tiptap/extension-ordered-list": "^2.6.6", "@tiptap/extension-ordered-list": "^2.6.6",
"@tiptap/extension-paragraph": "^2.6.6", "@tiptap/extension-paragraph": "^2.6.6",
"@tiptap/extension-placeholder": "^2.6.6", "@tiptap/extension-placeholder": "^2.6.6",
"@tiptap/extension-strike": "^2.6.6", "@tiptap/extension-strike": "^2.6.6",
"@tiptap/extension-task-item": "^2.6.6", "@tiptap/extension-task-item": "^2.6.6",
"@tiptap/extension-task-list": "^2.6.6", "@tiptap/extension-task-list": "^2.6.6",
"@tiptap/extension-text": "^2.6.6", "@tiptap/extension-text": "^2.6.6",
"@tiptap/extension-typography": "^2.6.6", "@tiptap/extension-typography": "^2.6.6",
"@tiptap/pm": "^2.6.6", "@tiptap/pm": "^2.6.6",
"@tiptap/react": "^2.6.6", "@tiptap/react": "^2.6.6",
"@tiptap/suggestion": "^2.6.6", "@tiptap/suggestion": "^2.6.6",
"axios": "^1.7.5", "axios": "^1.7.7",
"cheerio": "1.0.0", "cheerio": "1.0.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"framer-motion": "^11.3.31", "framer-motion": "^11.3.31",
"jazz-react": "0.7.35-new-auth.1", "jazz-react": "0.7.35-unique.2",
"jazz-react-auth-clerk": "0.7.33-new-auth.1", "jazz-react-auth-clerk": "0.7.33-new-auth.1",
"jazz-tools": "0.7.35-new-auth.0", "jazz-tools": "0.7.35-unique.2",
"jotai": "^2.9.3", "jotai": "^2.9.3",
"lowlight": "^3.1.0", "lowlight": "^3.1.0",
"lucide-react": "^0.429.0", "lucide-react": "^0.429.0",
"next": "14.2.5", "next": "14.2.5",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"nuqs": "^1.17.8", "nuqs": "^1.18.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-day-picker": "^9.0.8", "react-day-picker": "^9.0.8",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.53.0", "react-hook-form": "^7.53.0",
"react-textarea-autosize": "^8.5.3", "react-textarea-autosize": "^8.5.3",
"react-use": "^17.5.1", "react-use": "^17.5.1",
"slugify": "^1.6.6", "slugify": "^1.6.6",
"sonner": "^1.5.0", "sonner": "^1.5.0",
"streaming-markdown": "^0.0.14", "streaming-markdown": "^0.0.14",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"zod": "^3.23.8", "zod": "^3.23.8",
"zsa": "^0.6.0" "zsa": "^0.6.0"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^6.5.0", "@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1", "@testing-library/react": "^16.0.1",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^22.5.1", "@types/node": "^22.5.2",
"@types/react": "^18.3.5", "@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-next": "14.2.5", "eslint-config-next": "14.2.5",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0", "jest-environment-jsdom": "^29.7.0",
"postcss": "^8.4.41", "postcss": "^8.4.44",
"prettier-plugin-tailwindcss": "^0.6.6", "prettier-plugin-tailwindcss": "^0.6.6",
"tailwindcss": "^3.4.10", "tailwindcss": "^3.4.10",
"ts-jest": "^29.2.5", "ts-jest": "^29.2.5",
"typescript": "^5.5.4" "typescript": "^5.5.4"
} }
} }