Files
archived-linsa/web/components/la-editor/la-editor.tsx
Aslam 36e0e19212 Setup (#112)
* wip

* wip

* wip3

* chore: utils

* feat: add command

* wip

* fix: key duplicate

* fix: move and check

* fix: use react-use instead

* fix: sidebar

* chore: make dynamic

* chore: tablet mode

* chore: fix padding

* chore: link instead of inbox

* fix: use dnd kit

* feat: add select component

* chore: use atom

* refactor: remove dnd provider

* feat: disabled drag when sort is not manual

* search route

* .

* feat: accessibility

* fix: search

* .

* .

* .

* fix: sidebar collapsed

* ai search layout

* .

* .

* .

* .

* ai responsible content

* .

* .

* .

* .

* .

* global topic route

* global topic correct route

* topic buttons

* sidebar search navigation

* ai

* Update jazz

* .

* .

* .

* .

* .

* learning status

* .

* .

* chore: content header

* fix: pointer none when dragging. prevent auto click after drag end

* fix: confirm

* fix: prevent drag when editing

* chore: remove unused fn

* fix: check propagation

* chore: list

* chore: tweak sonner

* chore: update stuff

* feat: add badge

* chore: close edit when create

* chore: escape on manage form

* refactor: remove learn path

* css: responsive item

* chore: separate pages and topic

* reafactor: remove new-schema

* feat(types): extend jazz type so it can be nullable

* chore: use new types

* fix: missing deps

* fix: link

* fix: sidebar in layout

* fix: quotes

* css: use medium instead semi

* Actual streaming and rendering markdown response

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* chore: metadata

* feat: la-editor

* .

* fix: editor and page

* .

* .

* .

* .

* .

* .

* fix: remove link

* chore: page sidebar

* fix: remove 'replace with learning status'

* fix: link

* fix: link

* chore: update schema

* chore: use new schema

* fix: instead of showing error, just do unique slug

* feat: create slug

* refactor apply

* update package json

* fix: schema personal page

* chore: editor

* feat: pages

* fix: metadata

* fix: jazz provider

* feat: handling data

* feat: page detail

* chore: server page to id

* chore: use id instead of slug

* chore: update content header

* chore: update link header implementation

* refactor: global.css

* fix: la editor use animation frame

* fix: editor export ref

* refactor: page detail

* chore: tidy up schema

* chore: adapt to new schema

* fix: wrap using settimeout

* fix: wrap using settimeout

* .

* .

---------

Co-authored-by: marshennikovaolga <marshennikova@gmail.com>
Co-authored-by: Nikita <github@nikiv.dev>
Co-authored-by: Anselm <anselm.eickhoff@gmail.com>
Co-authored-by: Damian Tarnawski <gthetarnav@gmail.com>
2024-08-07 20:57:22 +03:00

147 lines
3.5 KiB
TypeScript

"use client"
import * as React from "react"
import { EditorContent, useEditor } from "@tiptap/react"
import { Editor, Content } from "@tiptap/core"
import { useThrottleFn } from "react-use"
import { BubbleMenu } from "./components/bubble-menu"
import { createExtensions } from "./extensions"
import "./styles/index.css"
import { cn } from "@/lib/utils"
import { getOutput } from "./lib/utils"
export interface LAEditorProps extends Omit<React.HTMLProps<HTMLDivElement>, "value"> {
initialContent?: any
output?: "html" | "json" | "text"
placeholder?: string
editorClassName?: string
onUpdate?: (content: Content) => void
onBlur?: (content: Content) => void
onNewBlock?: (content: Content) => void
value?: Content
throttleDelay?: number
}
export interface LAEditorRef {
focus: () => void
}
interface CustomEditor extends Editor {
previousBlockCount?: number
}
export const LAEditor = React.forwardRef<LAEditorRef, LAEditorProps>(
(
{
initialContent,
value,
placeholder,
output = "html",
editorClassName,
className,
onUpdate,
onBlur,
onNewBlock,
throttleDelay = 1000,
...props
},
ref
) => {
const [content, setContent] = React.useState<Content | undefined>(value)
const throttledContent = useThrottleFn(defaultContent => defaultContent, throttleDelay, [content])
const [lastThrottledContent, setLastThrottledContent] = React.useState(throttledContent)
const handleUpdate = React.useCallback(
(editor: Editor) => {
const newContent = getOutput(editor, output)
setContent(newContent)
const customEditor = editor as CustomEditor
const json = customEditor.getJSON()
if (json.content && Array.isArray(json.content)) {
const currentBlockCount = json.content.length
if (
typeof customEditor.previousBlockCount === "number" &&
currentBlockCount > customEditor.previousBlockCount
) {
requestAnimationFrame(() => {
onNewBlock?.(newContent)
})
}
customEditor.previousBlockCount = currentBlockCount
}
},
[output, onNewBlock]
)
const editor = useEditor({
autofocus: false,
extensions: createExtensions({ placeholder }),
editorProps: {
attributes: {
autocomplete: "off",
autocorrect: "off",
autocapitalize: "off",
class: editorClassName || ""
}
},
onCreate: ({ editor }) => {
if (editor.isEmpty && value) {
editor.commands.setContent(value)
}
},
onUpdate: ({ editor }) => handleUpdate(editor),
onBlur: ({ editor }) => {
requestAnimationFrame(() => {
onBlur?.(getOutput(editor, output))
})
}
})
React.useEffect(() => {
if (editor && initialContent) {
// https://github.com/ueberdosis/tiptap/issues/3764
setTimeout(() => {
editor.commands.setContent(initialContent)
})
}
}, [editor, initialContent])
React.useEffect(() => {
if (lastThrottledContent !== throttledContent) {
setLastThrottledContent(throttledContent)
requestAnimationFrame(() => {
onUpdate?.(throttledContent!)
})
}
}, [throttledContent, lastThrottledContent, onUpdate])
React.useImperativeHandle(
ref,
() => ({
focus: () => editor?.commands.focus()
}),
[editor]
)
if (!editor) {
return null
}
return (
<div className={cn("la-editor relative flex h-full w-full grow flex-col", className)} {...props}>
<EditorContent editor={editor} />
<BubbleMenu editor={editor} />
</div>
)
}
)
LAEditor.displayName = "LAEditor"
export default LAEditor