fix: topic selector (#129)

* feat: add jazz globa group cons

* chore: remove topic selector atom

* chore: use jazz from constant

* chore: remove delete model and add new topic selector

* chore: use jazz group id form constant in search component

* chore: use jazz group id form constant in public home route

* fix: topic selector in link

* fix: topic section in detail topic

* chore: update la editor

* chore: content header tweak class

* chore: add btn variant to topic selector

* refactor: tweak border for link header

* chore: page header

* fix: page detail route
This commit is contained in:
Aslam
2024-09-04 05:32:37 +07:00
committed by GitHub
parent e383381ffc
commit a3913baff9
21 changed files with 573 additions and 437 deletions

View File

@@ -1,10 +1,9 @@
"use client"
import * as React from "react"
import { useAtom } from "jotai"
import { ID } from "jazz-tools"
import { PersonalPage, Topic } from "@/lib/schema"
import { useCallback, useRef, useEffect, useState } from "react"
import { PersonalPage } from "@/lib/schema"
import { useCallback, useRef, useEffect } from "react"
import { LAEditor, LAEditorRef } from "@/components/la-editor"
import { Content, EditorContent, useEditor } from "@tiptap/react"
import { StarterKit } from "@/components/la-editor/extensions/starter-kit"
@@ -13,26 +12,46 @@ import { useAccount, useCoState } from "@/lib/providers/jazz-provider"
import { EditorView } from "@tiptap/pm/view"
import { Editor } from "@tiptap/core"
import { generateUniqueSlug } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { LaIcon } from "@/components/custom/la-icon"
import { pageTopicSelectorAtom } from "@/store/page"
import { TopicSelector } from "@/components/routes/link/partials/form/topic-selector"
import { FocusClasses } from "@tiptap/extension-focus"
import DeletePageModal from "@/components/custom/delete-modal"
import { DetailPageHeader } from "./header"
import { useMedia } from "react-use"
import TopicSelector from "@/components/custom/topic-selector"
const TITLE_PLACEHOLDER = "Untitled"
export function PageDetailRoute({ pageId }: { pageId: string }) {
const isMobile = useMedia("(max-width: 770px)")
const page = useCoState(PersonalPage, pageId as ID<PersonalPage>)
if (!page) return <div>Loading...</div>
if (!page) return null
return (
<div className="flex flex-row">
<div className="absolute inset-0 flex flex-row overflow-hidden">
<div className="flex h-full w-full">
<div className="relative flex min-w-0 grow basis-[760px] flex-col">
<DetailPageHeader page={page} />
<DetailPageForm page={page} />
</div>
{!isMobile && (
<div className="relative min-w-56 max-w-72 border-l">
<div className="flex">
<div className="flex h-10 flex-auto flex-row items-center justify-between px-4">
<span className="text-left text-sm font-medium">Page actions</span>
</div>
<div className="absolute bottom-0 left-0 right-0 top-10 overflow-y-auto px-4 py-1.5">
<TopicSelector
value={page.topic?.name}
onTopicChange={topic => {
page.topic = topic
page.updatedAt = new Date()
}}
/>
</div>
</div>
</div>
)}
</div>
</div>
)
@@ -42,9 +61,6 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
const { me } = useAccount()
const titleEditorRef = useRef<Editor | null>(null)
const contentEditorRef = useRef<LAEditorRef>(null)
const [, setTopicSelectorOpen] = useAtom(pageTopicSelectorAtom)
const [, setSelectedPageTopic] = useState<Topic | null>(page.topic || null)
const [deleteModalOpen, setDeleteModalOpen] = useState(false)
const isTitleInitialMount = useRef(true)
const isContentInitialMount = useRef(true)
@@ -55,7 +71,6 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
return
}
console.log("Updating page content")
model.content = content
model.updatedAt = new Date()
}
@@ -66,22 +81,6 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
return
}
/*
* The logic changed, but we keep this commented code for reference
*/
// const newTitle = editor.getText().trim()
// if (!newTitle) {
// toast.error("Update failed", {
// description: "Title must be longer than or equal to 1 character"
// })
// editor.commands.setContent(page.title || "")
// return
// }
// if (newTitle === page.title) return
console.log("Updating page title")
const personalPages = me?.root?.personalPages?.toJSON() || []
const slug = generateUniqueSlug(personalPages, page.slug || "")
@@ -101,29 +100,44 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
const { selection } = state
const { $anchor } = selection
if ((event.key === "ArrowLeft" || event.key === "ArrowUp") && $anchor.pos - 1 === 0) {
event.preventDefault()
titleEditorRef.current?.commands.focus("end")
return true
switch (event.key) {
case "ArrowRight":
case "ArrowDown":
if ($anchor.pos === state.doc.content.size - 1) {
event.preventDefault()
contentEditorRef.current?.editor?.commands.focus("start")
return true
}
break
case "Enter":
if (!event.shiftKey) {
event.preventDefault()
contentEditorRef.current?.editor?.commands.focus("start")
return true
}
break
}
return false
}, [])
const handleContentKeyDown = useCallback((view: EditorView, event: KeyboardEvent) => {
const editor = contentEditorRef.current?.editor
if (!editor) return false
const { state } = editor
const { selection } = state
const { $anchor } = selection
if ((event.key === "ArrowLeft" || event.key === "ArrowUp") && $anchor.pos - 1 === 0) {
event.preventDefault()
titleEditorRef.current?.commands.focus("end")
return true
}
return false
}, [])
const confirmDelete = (page: PersonalPage) => {
console.log("Deleting page:", page.id)
setDeleteModalOpen(false)
//TODO: add delete logic
}
const titleEditor = useEditor({
immediatelyRender: false,
autofocus: true,
@@ -139,7 +153,6 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
strike: false,
focus: false,
gapcursor: false,
history: false,
placeholder: {
placeholder: TITLE_PLACEHOLDER
}
@@ -152,7 +165,8 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
"aria-readonly": "false",
"aria-multiline": "false",
"aria-label": TITLE_PLACEHOLDER,
translate: "no"
translate: "no",
class: "focus:outline-none"
},
handleKeyDown: handleTitleKeyDown
},
@@ -160,9 +174,7 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
if (page.title) editor.commands.setContent(`<p>${page.title}</p>`)
},
onBlur: ({ editor }) => handleUpdateTitle(editor),
onUpdate: ({ editor }) => {
handleUpdateTitle(editor)
}
onUpdate: ({ editor }) => handleUpdateTitle(editor)
})
useEffect(() => {
@@ -177,37 +189,20 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
}, [])
return (
<div tabIndex={0} className="relative flex grow flex-col overflow-y-auto">
<div className="relative mx-auto flex h-full w-[calc(100%-40px)] shrink-0 grow flex-col sm:w-[calc(100%-80px)]">
<div className="relative flex grow flex-col overflow-y-auto [scrollbar-gutter:stable]">
<div className="relative mx-auto flex h-full w-[calc(100%-80px)] shrink-0 grow flex-col max-lg:w-[calc(100%-40px)] max-lg:max-w-[unset]">
<form className="flex shrink-0 flex-col">
<div className="mb-2 mt-8 flex flex-row justify-between py-1.5">
<div className="mb-2 mt-8 py-1.5">
<EditorContent
editor={titleEditor}
className="la-editor no-command grow cursor-text select-text text-2xl font-semibold leading-[calc(1.33333)] tracking-[-0.00625rem]"
/>
<div className="items-center space-x-4">
<TopicSelector
onSelect={topic => {
page.topic = topic
setSelectedPageTopic(topic)
setTopicSelectorOpen(false)
}}
/>
<Button
type="button"
variant="secondary"
className="text-foreground bg-truncat"
onClick={() => setDeleteModalOpen(true)}
>
<LaIcon name="Trash" className="h-4 w-4" />
</Button>
</div>
</div>
<div className="flex flex-auto flex-col">
<div className="relative flex h-full max-w-full grow flex-col items-stretch p-0">
<LAEditor
ref={contentEditorRef}
editorClassName="-mx-3.5 px-3.5 py-2.5 flex-auto"
editorClassName="-mx-3.5 px-3.5 py-2.5 flex-auto focus:outline-none"
value={page.content}
placeholder="Add content..."
output="json"
@@ -221,15 +216,6 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
</div>
</form>
</div>
<DeletePageModal
isOpen={deleteModalOpen}
onClose={() => setDeleteModalOpen(false)}
onConfirm={() => {
confirmDelete(page)
}}
title={page.title || ""}
/>
</div>
)
}

View File

@@ -2,24 +2,33 @@
import * as React from "react"
import { ContentHeader, SidebarToggleButton } from "@/components/custom/content-header"
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from "@/components/ui/breadcrumb"
import { PersonalPage } from "@/lib/schema/personal-page"
import { ID } from "jazz-tools"
import { useMedia } from "react-use"
import { TopicSelector } from "@/components/custom/topic-selector"
export const DetailPageHeader = ({ page }: { page: PersonalPage }) => {
const isMobile = useMedia("(max-width: 770px)")
export const DetailPageHeader = ({ pageId }: { pageId: ID<PersonalPage> }) => {
return (
<ContentHeader>
<div className="flex min-w-0 gap-2">
<SidebarToggleButton />
isMobile && (
<>
<ContentHeader className="lg:min-h-0">
<div className="flex min-w-0 gap-2">
<SidebarToggleButton />
</div>
</ContentHeader>
<Breadcrumb className="flex flex-row items-center">
<BreadcrumbList className="sm:gap-2">
<BreadcrumbItem>
<BreadcrumbPage className="text-foreground font-medium">Pages</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
</ContentHeader>
<div className="flex flex-row items-start justify-between border-b px-6 py-2 max-lg:pl-4">
<TopicSelector
value={page.topic?.name}
onTopicChange={topic => {
page.topic = topic
page.updatedAt = new Date()
}}
align="start"
/>
</div>
</>
)
)
}