From b613bbd66c46ea7399cf9c7d9eb7e00e2dd57905 Mon Sep 17 00:00:00 2001 From: Aslam H Date: Mon, 4 Nov 2024 16:45:29 +0700 Subject: [PATCH] chore: subscribe co value --- package.json | 6 +- .../sidebar/partials/journal-section.tsx | 2 +- web/app/components/sidebar/sidebar.tsx | 39 +++--------- web/app/lib/utils/index.ts | 1 + web/app/lib/utils/jazz.ts | 39 ++++++++++++ web/app/routeTree.gen.ts | 8 +-- .../_pages/_protected/pages/$pageId/index.tsx | 59 ++++++++++++++++--- web/package.json | 40 ++++++------- web/shared/editor/editor.tsx | 7 ++- 9 files changed, 132 insertions(+), 69 deletions(-) create mode 100644 web/app/lib/utils/jazz.ts diff --git a/package.json b/package.json index 8746faa7..28bf148c 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,12 @@ "web" ], "dependencies": { - "@tauri-apps/cli": "^2.0.3", - "@tauri-apps/plugin-fs": "^2.0.0", + "@tauri-apps/cli": "^2.0.4", + "@tauri-apps/plugin-fs": "^2.0.1", "jazz-nodejs": "0.8.0" }, "devDependencies": { - "bun-types": "^1.1.31" + "bun-types": "^1.1.34" }, "prettier": { "plugins": [ diff --git a/web/app/components/sidebar/partials/journal-section.tsx b/web/app/components/sidebar/partials/journal-section.tsx index 26120a65..d12ea171 100644 --- a/web/app/components/sidebar/partials/journal-section.tsx +++ b/web/app/components/sidebar/partials/journal-section.tsx @@ -24,7 +24,7 @@ export const JournalSection: React.FC = () => { if ( user?.emailAddresses.some((email) => - response?.emails.includes(email.emailAddress), + response?.emails?.includes(email.emailAddress), ) ) { setIsFeatureActive(true) diff --git a/web/app/components/sidebar/sidebar.tsx b/web/app/components/sidebar/sidebar.tsx index f0815b2d..2619dd8a 100644 --- a/web/app/components/sidebar/sidebar.tsx +++ b/web/app/components/sidebar/sidebar.tsx @@ -11,19 +11,16 @@ import { Link, useLocation } from "@tanstack/react-router" import { PageSection } from "./partials/page-section" import { ProfileSection } from "./partials/profile-section" import { JournalSection } from "./partials/journal-section" -// import { TaskSection } from "./partials/task-section" import { LinkCollection } from "./partials/link-collection" -interface SidebarContextType { - isCollapsed: boolean - setIsCollapsed: React.Dispatch> +interface SidebarItemProps { + label: string + url: string + icon?: React.ReactNode + onClick?: () => void + children?: React.ReactNode } -const SidebarContext = React.createContext({ - isCollapsed: false, - setIsCollapsed: () => {}, -}) - const useSidebarCollapse = ( isTablet: boolean, ): [boolean, React.Dispatch>] => { @@ -41,14 +38,6 @@ const useSidebarCollapse = ( return [isCollapsed, setIsCollapsed] } -interface SidebarItemProps { - label: string - url: string - icon?: React.ReactNode - onClick?: () => void - children?: React.ReactNode -} - const SidebarItem: React.FC = React.memo( ({ label, url, icon, onClick, children }) => { const { pathname } = useLocation() @@ -132,7 +121,6 @@ const SidebarContent: React.FC = React.memo(() => {
{me._type === "Account" && } {me._type === "Account" && } - {/* {me._type === "Account" && } */} {me._type === "Account" && }
@@ -157,11 +145,6 @@ const Sidebar: React.FC = () => { isCollapsed ? "-translate-x-full" : "translate-x-0", ) - const contextValue = React.useMemo( - () => ({ isCollapsed, setIsCollapsed }), - [isCollapsed, setIsCollapsed], - ) - if (isTablet) { return ( <> @@ -183,9 +166,7 @@ const Sidebar: React.FC = () => {
- - - +
@@ -195,9 +176,7 @@ const Sidebar: React.FC = () => { return (
- - - +
) @@ -205,4 +184,4 @@ const Sidebar: React.FC = () => { Sidebar.displayName = "Sidebar" -export { Sidebar, SidebarItem, SidebarContext } +export { Sidebar, SidebarItem } diff --git a/web/app/lib/utils/index.ts b/web/app/lib/utils/index.ts index 9963da74..b07c610d 100644 --- a/web/app/lib/utils/index.ts +++ b/web/app/lib/utils/index.ts @@ -74,3 +74,4 @@ export * from "./force-graph" export * from "./env" export * from "./slug" export * from "./url" +export * from "./jazz" diff --git a/web/app/lib/utils/jazz.ts b/web/app/lib/utils/jazz.ts new file mode 100644 index 00000000..e7b1a4b8 --- /dev/null +++ b/web/app/lib/utils/jazz.ts @@ -0,0 +1,39 @@ +import { + Account, + CoValue, + CoValueClass, + DepthsIn, + ID, + subscribeToCoValue, +} from "jazz-tools" + +export function waitForCoValue( + coMap: CoValueClass, + valueId: ID, + account: Account, + predicate: (value: T) => boolean, + depth: DepthsIn, +) { + return new Promise((resolve) => { + function subscribe() { + const unsubscribe = subscribeToCoValue( + coMap, + valueId, + account, + depth, + (value) => { + if (predicate(value)) { + resolve(value) + unsubscribe() + } + }, + () => { + unsubscribe() + setTimeout(subscribe, 100) + }, + ) + } + + subscribe() + }) +} diff --git a/web/app/routeTree.gen.ts b/web/app/routeTree.gen.ts index 77745500..196b8b83 100644 --- a/web/app/routeTree.gen.ts +++ b/web/app/routeTree.gen.ts @@ -1,12 +1,12 @@ -/* prettier-ignore-start */ - /* eslint-disable */ // @ts-nocheck // noinspection JSUnusedGlobalSymbols -// This file is auto-generated by TanStack Router +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { createFileRoute } from '@tanstack/react-router' @@ -540,8 +540,6 @@ export const routeTree = rootRoute ._addFileChildren(rootRouteChildren) ._addFileTypes() -/* prettier-ignore-end */ - /* ROUTE_MANIFEST_START { "routes": { diff --git a/web/app/routes/_layout/_pages/_protected/pages/$pageId/index.tsx b/web/app/routes/_layout/_pages/_protected/pages/$pageId/index.tsx index a7ee2a47..64e2108f 100644 --- a/web/app/routes/_layout/_pages/_protected/pages/$pageId/index.tsx +++ b/web/app/routes/_layout/_pages/_protected/pages/$pageId/index.tsx @@ -32,6 +32,7 @@ function PageDetailComponent() { const { me } = useAccount({ root: { personalLinks: [] } }) const isMobile = useMedia("(max-width: 770px)") const page = useCoState(PersonalPage, pageId as ID) + const navigate = useNavigate() const { deletePage } = usePageActions() const confirm = useConfirm() @@ -63,10 +64,15 @@ function PageDetailComponent() { handleDelete={handleDelete} isMobile={isMobile} /> - + + {!isMobile && ( - + )} @@ -129,11 +135,40 @@ const DetailPageForm = ({ }) => { const titleEditorRef = React.useRef(null) const contentEditorRef = React.useRef(null) + const [isInitialSync, setIsInitialSync] = React.useState(true) + + React.useEffect(() => { + if (!page) return + + const unsubscribe = page.subscribe({}, (updatedPage) => { + if ( + updatedPage && + contentEditorRef.current && + titleEditorRef.current && + !isInitialSync + ) { + const currentTItle = titleEditorRef.current.getText() + const newTitle = updatedPage.title + + if (currentTItle !== newTitle) { + titleEditorRef.current.commands.setContent(newTitle as Content) + } + + const currentContent = contentEditorRef.current.getJSON() + const newContent = updatedPage.content + + if (JSON.stringify(currentContent) !== JSON.stringify(newContent)) { + contentEditorRef.current.commands.setContent(newContent as Content) + } + } + }) + + return () => unsubscribe() + }, [page, isInitialSync]) const updatePageContent = React.useCallback( (content: Content) => { - page.content = content - page.updatedAt = new Date() + page.applyDiff({ content, updatedAt: new Date() }) }, [page], ) @@ -142,7 +177,7 @@ const DetailPageForm = ({ (editor: Editor) => { const newTitle = editor.getText() if (newTitle !== page.title) { - const slug = generateUniqueSlug(newTitle || "") + const slug = generateUniqueSlug(newTitle) page.title = newTitle page.slug = slug page.updatedAt = new Date() @@ -201,7 +236,8 @@ const DetailPageForm = ({ ) const titleEditor = useEditor({ - immediatelyRender: false, + immediatelyRender: true, + shouldRerenderOnTransaction: false, extensions: [ FocusClasses, Paragraph, @@ -231,7 +267,7 @@ const DetailPageForm = ({ }, onCreate: ({ editor }) => { if (page.title) { - editor.commands.setContent(`

${page.title}

`) + editor.commands.setContent(page.title) } titleEditorRef.current = editor @@ -245,10 +281,13 @@ const DetailPageForm = ({ const handleCreate = React.useCallback( ({ editor }: { editor: Editor }) => { + contentEditorRef.current = editor + if (page.content) { editor.commands.setContent(page.content as Content) } - contentEditorRef.current = editor + + setIsInitialSync(false) if (page.title) { editor.commands.focus() @@ -276,7 +315,9 @@ const DetailPageForm = ({ value={page.content as Content} placeholder="Add content..." output="json" - throttleDelay={1000} + throttleDelay={0} + immediatelyRender={true} + shouldRerenderOnTransaction={false} editorProps={{ handleKeyDown: handleContentKeyDown }} onCreate={handleCreate} onUpdate={updatePageContent} diff --git a/web/package.json b/web/package.json index 56ef268c..69c1c665 100644 --- a/web/package.json +++ b/web/package.json @@ -12,15 +12,15 @@ "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix" }, "dependencies": { - "@clerk/tanstack-start": "^0.4.17", - "@clerk/themes": "^2.1.39", + "@clerk/tanstack-start": "^0.4.21", + "@clerk/themes": "^2.1.40", "@dnd-kit/core": "^6.1.0", "@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/sortable": "^8.0.0", "@hookform/resolvers": "^3.9.1", "@nothing-but/force-graph": "^0.9.5", "@nothing-but/utils": "^0.17.0", - "@omit/react-confirm-dialog": "^1.1.5", + "@omit/react-confirm-dialog": "^1.1.7", "@omit/react-fancy-switch": "^1.0.2", "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-avatar": "^1.1.1", @@ -28,7 +28,7 @@ "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dismissable-layer": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-scroll-area": "^1.2.0", @@ -39,12 +39,12 @@ "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.3", - "@tanstack/react-query": "^5.59.16", - "@tanstack/react-router": "^1.77.8", - "@tanstack/react-router-with-query": "^1.77.8", + "@tanstack/react-query": "^5.59.18", + "@tanstack/react-router": "^1.78.3", + "@tanstack/react-router-with-query": "^1.78.3", "@tanstack/react-virtual": "^3.10.8", - "@tanstack/router-zod-adapter": "^1.77.8", - "@tanstack/start": "^1.77.8", + "@tanstack/router-zod-adapter": "^1.78.3", + "@tanstack/start": "^1.78.3", "@tiptap/core": "^2.9.1", "@tiptap/extension-code-block-lowlight": "^2.9.1", "@tiptap/extension-color": "^2.9.1", @@ -65,13 +65,13 @@ "cheerio": "^1.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "cmdk": "^1.0.0", + "cmdk": "1.0.2", "date-fns": "^3.6.0", - "framer-motion": "^11.11.10", - "jazz-browser-media-images": "^0.8.7", - "jazz-react": "^0.8.7", - "jazz-react-auth-clerk": "^0.8.7", - "jazz-tools": "^0.8.5", + "framer-motion": "^11.11.11", + "jazz-browser-media-images": "^0.8.12", + "jazz-react": "^0.8.12", + "jazz-react-auth-clerk": "^0.8.12", + "jazz-tools": "^0.8.12", "jotai": "^2.10.1", "lowlight": "^3.1.0", "lucide-react": "^0.446.0", @@ -86,7 +86,7 @@ "react-textarea-autosize": "^8.5.4", "ronin": "^4.4.1", "slugify": "^1.6.6", - "sonner": "^1.5.0", + "sonner": "^1.6.1", "streaming-markdown": "^0.0.14", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", @@ -95,11 +95,11 @@ "zod": "^3.23.8" }, "devDependencies": { - "@ronin/learn-anything": "^0.0.0-3460430517579", + "@ronin/learn-anything": "^0.0.0-3460605731228", "@tailwindcss/typography": "^0.5.15", - "@tanstack/react-query-devtools": "^5.59.16", - "@tanstack/router-devtools": "^1.77.8", - "@types/node": "^22.8.4", + "@tanstack/react-query-devtools": "^5.59.18", + "@tanstack/router-devtools": "^1.78.3", + "@types/node": "^22.8.7", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@types/uuid": "^10.0.0", diff --git a/web/shared/editor/editor.tsx b/web/shared/editor/editor.tsx index 9b0946eb..d4fa7187 100644 --- a/web/shared/editor/editor.tsx +++ b/web/shared/editor/editor.tsx @@ -17,7 +17,12 @@ export interface LaEditorProps extends UseLaEditorProps { export const LaEditor = React.forwardRef( ({ className, editorContentClassName, me, personalPage, ...props }, ref) => { - const editor = useLaEditor({ ...props, me, personalPage }) + const editor = useLaEditor({ + ...props, + me, + personalPage, + shouldRerenderOnTransaction: true, + }) if (!editor) { return null