mirror of
https://github.com/linsa-io/linsa.git
synced 2026-04-23 08:48:37 +02:00
fix: detail topic (#117)
* feat: keyboard nav * fix: link update * feat: reusable learning state * chore: use new learning state * feat: add to my profile * . * . * feat: on enter open the link * fix: lint * fix: use eslint v8 instead of v9 * fix: add to my profile * chore: update personal link schema * chore: update personal page schema * fix: update detail wrapper * fix: update page section * removing option for learning status * removing option for learning status for topic * feat: add createdAt and updatedAt for personal Page * chore: update page section component * chore: remove chevron from sub menu * fix: sidebar * chore: add focus and disable toast * feat: la editor add execption for no command class * fix: la editor style and fix page detail * fix: title * fix: topic learning state * chore: add showSearch for learning state * fix: bunch stuff * chore: link list and item handle learning state * chore: set expand to false * feat: personal link for topic detail * chore: hook use topic data * chore: go to list * fix: link and topic * feat(utils): new keyboard utils * feat(store): add linkOpenPopoverForIdAtom for link * chore: using memo for use topic data * fix: remove duplicate component * chore: performance for topic detail lint item * refactor: remove LinkOptions component * chore: improve performance for list * feat: added LinkRoute copmonent * chore: link manage * feat: bottom bar * fix: link * fix: page wrapper * fix: import thing * chore: added a displayname * refactor: page detail * refactor: page detail * fix: add topic to personal link form link * fix: only show page count if more than zero * fix: sidebar topic section --------- Co-authored-by: Nikita <github@nikiv.dev> Co-authored-by: marshennikovaolga <marshennikova@gmail.com>
This commit is contained in:
@@ -10,21 +10,19 @@ import { Content, EditorContent, useEditor } from "@tiptap/react"
|
||||
import { StarterKit } from "@/components/la-editor/extensions/starter-kit"
|
||||
import { Paragraph } from "@/components/la-editor/extensions/paragraph"
|
||||
import { useAccount, useCoState } from "@/lib/providers/jazz-provider"
|
||||
import { toast } from "sonner"
|
||||
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/form/partial/topic-selector"
|
||||
import { TopicSelector } from "@/components/routes/link/partials/form/topic-selector"
|
||||
import { FocusClasses } from "@tiptap/extension-focus"
|
||||
import DeletePageModal from "@/components/custom/delete-modal"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||
import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"
|
||||
|
||||
const TITLE_PLACEHOLDER = "Page title"
|
||||
const TITLE_PLACEHOLDER = "Untitled"
|
||||
|
||||
export function DetailPageWrapper({ pageId }: { pageId: string }) {
|
||||
export function PageDetailRoute({ pageId }: { pageId: string }) {
|
||||
const page = useCoState(PersonalPage, pageId as ID<PersonalPage>)
|
||||
|
||||
if (!page) return <div>Loading...</div>
|
||||
@@ -45,32 +43,52 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
|
||||
const titleEditorRef = useRef<Editor | null>(null)
|
||||
const contentEditorRef = useRef<LAEditorRef>(null)
|
||||
const [, setTopicSelectorOpen] = useAtom(pageTopicSelectorAtom)
|
||||
const [selectedPageTopic, setSelectedPageTopic] = useState<Topic | null>(page.topic || null)
|
||||
const [, setSelectedPageTopic] = useState<Topic | null>(page.topic || null)
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false)
|
||||
|
||||
const isTitleInitialMount = useRef(true)
|
||||
const isContentInitialMount = useRef(true)
|
||||
|
||||
const updatePageContent = (content: Content, model: PersonalPage) => {
|
||||
model.content = content
|
||||
}
|
||||
|
||||
const handleTitleBlur = (editor: Editor) => {
|
||||
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)
|
||||
if (isContentInitialMount.current) {
|
||||
isContentInitialMount.current = false
|
||||
return
|
||||
}
|
||||
|
||||
if (newTitle === page.title) return
|
||||
console.log("Updating page content")
|
||||
model.content = content
|
||||
model.updatedAt = new Date()
|
||||
}
|
||||
|
||||
const handleUpdateTitle = (editor: Editor) => {
|
||||
if (isTitleInitialMount.current) {
|
||||
isTitleInitialMount.current = false
|
||||
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)
|
||||
const slug = generateUniqueSlug(personalPages, page.slug || "")
|
||||
|
||||
const trimmedTitle = editor.getText().trim()
|
||||
page.title = trimmedTitle
|
||||
page.slug = slug
|
||||
page.updatedAt = new Date()
|
||||
|
||||
editor.commands.setContent(trimmedTitle)
|
||||
}
|
||||
@@ -108,7 +126,9 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
|
||||
|
||||
const titleEditor = useEditor({
|
||||
immediatelyRender: false,
|
||||
autofocus: true,
|
||||
extensions: [
|
||||
FocusClasses,
|
||||
Paragraph,
|
||||
StarterKit.configure({
|
||||
bold: false,
|
||||
@@ -137,10 +157,12 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
|
||||
handleKeyDown: handleTitleKeyDown
|
||||
},
|
||||
onCreate: ({ editor }) => {
|
||||
const capitalizedTitle = page.title.charAt(0).toUpperCase() + page.title.slice(1)
|
||||
editor.commands.setContent(`<p>${capitalizedTitle}</p>`)
|
||||
if (page.title) editor.commands.setContent(`<p>${page.title}</p>`)
|
||||
},
|
||||
onBlur: ({ editor }) => handleTitleBlur(editor)
|
||||
onBlur: ({ editor }) => handleUpdateTitle(editor),
|
||||
onUpdate: ({ editor }) => {
|
||||
handleUpdateTitle(editor)
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
@@ -149,6 +171,11 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
|
||||
}
|
||||
}, [titleEditor])
|
||||
|
||||
useEffect(() => {
|
||||
isTitleInitialMount.current = true
|
||||
isContentInitialMount.current = true
|
||||
}, [])
|
||||
|
||||
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)]">
|
||||
@@ -156,7 +183,7 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
|
||||
<div className="mb-2 mt-8 flex flex-row justify-between py-1.5">
|
||||
<EditorContent
|
||||
editor={titleEditor}
|
||||
className="la-editor cursor-text select-text text-2xl font-semibold leading-[calc(1.33333)] tracking-[-0.00625rem]"
|
||||
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
|
||||
@@ -201,7 +228,7 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
|
||||
onConfirm={() => {
|
||||
confirmDelete(page)
|
||||
}}
|
||||
title={page.title.charAt(0).toUpperCase() + page.title.slice(1)}
|
||||
title={page.title || ""}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -2,20 +2,11 @@
|
||||
|
||||
import * as React from "react"
|
||||
import { ContentHeader, SidebarToggleButton } from "@/components/custom/content-header"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator
|
||||
} from "@/components/ui/breadcrumb"
|
||||
import { useCoState } from "@/lib/providers/jazz-provider"
|
||||
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from "@/components/ui/breadcrumb"
|
||||
import { PersonalPage } from "@/lib/schema/personal-page"
|
||||
import { ID } from "jazz-tools"
|
||||
|
||||
export const DetailPageHeader = ({ pageId }: { pageId: ID<PersonalPage> }) => {
|
||||
const page = useCoState(PersonalPage, pageId)
|
||||
|
||||
return (
|
||||
<ContentHeader>
|
||||
<div className="flex min-w-0 gap-2">
|
||||
|
||||
Reference in New Issue
Block a user