mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-12 12:20:23 +01:00
chore: subscribe co value
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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<React.SetStateAction<boolean>>
|
||||
interface SidebarItemProps {
|
||||
label: string
|
||||
url: string
|
||||
icon?: React.ReactNode
|
||||
onClick?: () => void
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const SidebarContext = React.createContext<SidebarContextType>({
|
||||
isCollapsed: false,
|
||||
setIsCollapsed: () => {},
|
||||
})
|
||||
|
||||
const useSidebarCollapse = (
|
||||
isTablet: boolean,
|
||||
): [boolean, React.Dispatch<React.SetStateAction<boolean>>] => {
|
||||
@@ -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<SidebarItemProps> = React.memo(
|
||||
({ label, url, icon, onClick, children }) => {
|
||||
const { pathname } = useLocation()
|
||||
@@ -132,7 +121,6 @@ const SidebarContent: React.FC = React.memo(() => {
|
||||
<div className="h-2 shrink-0" />
|
||||
{me._type === "Account" && <LinkCollection />}
|
||||
{me._type === "Account" && <JournalSection />}
|
||||
{/* {me._type === "Account" && <TaskSection />} */}
|
||||
{me._type === "Account" && <PageSection />}
|
||||
</div>
|
||||
|
||||
@@ -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 = () => {
|
||||
<div
|
||||
className={cn(sidebarInnerClasses, "border-r border-r-primary/5")}
|
||||
>
|
||||
<SidebarContext.Provider value={contextValue}>
|
||||
<SidebarContent />
|
||||
</SidebarContext.Provider>
|
||||
<SidebarContent />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -195,9 +176,7 @@ const Sidebar: React.FC = () => {
|
||||
return (
|
||||
<div className={sidebarClasses}>
|
||||
<div className={sidebarInnerClasses}>
|
||||
<SidebarContext.Provider value={contextValue}>
|
||||
<SidebarContent />
|
||||
</SidebarContext.Provider>
|
||||
<SidebarContent />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -205,4 +184,4 @@ const Sidebar: React.FC = () => {
|
||||
|
||||
Sidebar.displayName = "Sidebar"
|
||||
|
||||
export { Sidebar, SidebarItem, SidebarContext }
|
||||
export { Sidebar, SidebarItem }
|
||||
|
||||
@@ -74,3 +74,4 @@ export * from "./force-graph"
|
||||
export * from "./env"
|
||||
export * from "./slug"
|
||||
export * from "./url"
|
||||
export * from "./jazz"
|
||||
|
||||
39
web/app/lib/utils/jazz.ts
Normal file
39
web/app/lib/utils/jazz.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
Account,
|
||||
CoValue,
|
||||
CoValueClass,
|
||||
DepthsIn,
|
||||
ID,
|
||||
subscribeToCoValue,
|
||||
} from "jazz-tools"
|
||||
|
||||
export function waitForCoValue<T extends CoValue>(
|
||||
coMap: CoValueClass<T>,
|
||||
valueId: ID<T>,
|
||||
account: Account,
|
||||
predicate: (value: T) => boolean,
|
||||
depth: DepthsIn<T>,
|
||||
) {
|
||||
return new Promise<T>((resolve) => {
|
||||
function subscribe() {
|
||||
const unsubscribe = subscribeToCoValue(
|
||||
coMap,
|
||||
valueId,
|
||||
account,
|
||||
depth,
|
||||
(value) => {
|
||||
if (predicate(value)) {
|
||||
resolve(value)
|
||||
unsubscribe()
|
||||
}
|
||||
},
|
||||
() => {
|
||||
unsubscribe()
|
||||
setTimeout(subscribe, 100)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
subscribe()
|
||||
})
|
||||
}
|
||||
@@ -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<FileRouteTypes>()
|
||||
|
||||
/* prettier-ignore-end */
|
||||
|
||||
/* ROUTE_MANIFEST_START
|
||||
{
|
||||
"routes": {
|
||||
|
||||
@@ -32,6 +32,7 @@ function PageDetailComponent() {
|
||||
const { me } = useAccount({ root: { personalLinks: [] } })
|
||||
const isMobile = useMedia("(max-width: 770px)")
|
||||
const page = useCoState(PersonalPage, pageId as ID<PersonalPage>)
|
||||
|
||||
const navigate = useNavigate()
|
||||
const { deletePage } = usePageActions()
|
||||
const confirm = useConfirm()
|
||||
@@ -63,10 +64,15 @@ function PageDetailComponent() {
|
||||
handleDelete={handleDelete}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
<DetailPageForm key={pageId} page={page} me={me} />
|
||||
<DetailPageForm page={page} me={me} />
|
||||
</div>
|
||||
|
||||
{!isMobile && (
|
||||
<SidebarActions page={page} handleDelete={handleDelete} />
|
||||
<SidebarActions
|
||||
key={pageId}
|
||||
page={page}
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,11 +135,40 @@ const DetailPageForm = ({
|
||||
}) => {
|
||||
const titleEditorRef = React.useRef<Editor | null>(null)
|
||||
const contentEditorRef = React.useRef<Editor | null>(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(`<p>${page.title}</p>`)
|
||||
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}
|
||||
|
||||
Reference in New Issue
Block a user