feat: feedback (#156)

* minimal tiptap

* wip

* img edit block

* wip

* fix
This commit is contained in:
Aslam
2024-09-10 17:58:58 +07:00
committed by GitHub
parent 4ea3a179e0
commit 711fe35e1a
62 changed files with 2689 additions and 6 deletions

View File

@@ -0,0 +1,15 @@
import * as React from 'react'
export const useImageLoad = (src: string) => {
const [imgSize, setImgSize] = React.useState({ width: 0, height: 0 })
React.useEffect(() => {
const img = new Image()
img.src = src
img.onload = () => {
setImgSize({ width: img.width, height: img.height })
}
}, [src])
return imgSize
}

View File

@@ -0,0 +1,107 @@
import * as React from "react"
import { StarterKit } from "@tiptap/starter-kit"
import type { Content, UseEditorOptions } from "@tiptap/react"
import { useEditor } from "@tiptap/react"
import type { Editor } from "@tiptap/core"
import { Typography } from "@tiptap/extension-typography"
import { Placeholder } from "@tiptap/extension-placeholder"
import { TextStyle } from "@tiptap/extension-text-style"
import {
Link,
Image,
HorizontalRule,
CodeBlockLowlight,
Selection,
Color,
UnsetAllMarks,
ResetMarksOnEnter
} from "../extensions"
import { cn } from "@/lib/utils"
import { getOutput } from "../utils"
import { useThrottle } from "../hooks/use-throttle"
export interface UseMinimalTiptapEditorProps extends UseEditorOptions {
value?: Content
output?: "html" | "json" | "text"
placeholder?: string
editorClassName?: string
throttleDelay?: number
onUpdate?: (content: Content) => void
onBlur?: (content: Content) => void
}
const createExtensions = (placeholder: string) => [
StarterKit.configure({
horizontalRule: false,
codeBlock: false,
paragraph: { HTMLAttributes: { class: "text-node" } },
heading: { HTMLAttributes: { class: "heading-node" } },
blockquote: { HTMLAttributes: { class: "block-node" } },
bulletList: { HTMLAttributes: { class: "list-node" } },
orderedList: { HTMLAttributes: { class: "list-node" } },
code: { HTMLAttributes: { class: "inline", spellcheck: "false" } },
dropcursor: { width: 2, class: "ProseMirror-dropcursor border" }
}),
Link,
Image,
Color,
TextStyle,
Selection,
Typography,
UnsetAllMarks,
HorizontalRule,
ResetMarksOnEnter,
CodeBlockLowlight,
Placeholder.configure({ placeholder: () => placeholder })
]
export const useMinimalTiptapEditor = ({
value,
output = "html",
placeholder = "",
editorClassName,
throttleDelay = 1000,
onUpdate,
onBlur,
...props
}: UseMinimalTiptapEditorProps) => {
const throttledSetValue = useThrottle((value: Content) => onUpdate?.(value), throttleDelay)
const handleUpdate = React.useCallback(
(editor: Editor) => {
throttledSetValue(getOutput(editor, output))
},
[output, throttledSetValue]
)
const handleCreate = React.useCallback(
(editor: Editor) => {
if (value && editor.isEmpty) {
editor.commands.setContent(value)
}
},
[value]
)
const handleBlur = React.useCallback((editor: Editor) => onBlur?.(getOutput(editor, output)), [output, onBlur])
const editor = useEditor({
extensions: createExtensions(placeholder!),
editorProps: {
attributes: {
autocomplete: "off",
autocorrect: "off",
autocapitalize: "off",
class: cn("focus:outline-none", editorClassName)
}
},
onUpdate: ({ editor }) => handleUpdate(editor),
onCreate: ({ editor }) => handleCreate(editor),
onBlur: ({ editor }) => handleBlur(editor),
...props
})
return editor
}
export default useMinimalTiptapEditor

View File

@@ -0,0 +1,25 @@
import * as React from 'react'
export const useTheme = () => {
const [isDarkMode, setIsDarkMode] = React.useState(false)
React.useEffect(() => {
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
setIsDarkMode(darkModeMediaQuery.matches)
const handleChange = (e: MediaQueryListEvent) => {
const newDarkMode = e.matches
setIsDarkMode(newDarkMode)
}
darkModeMediaQuery.addEventListener('change', handleChange)
return () => {
darkModeMediaQuery.removeEventListener('change', handleChange)
}
}, [])
return isDarkMode
}
export default useTheme

View File

@@ -0,0 +1,34 @@
import { useRef, useCallback } from 'react'
export function useThrottle<T extends (...args: any[]) => void>(
callback: T,
delay: number
): (...args: Parameters<T>) => void {
const lastRan = useRef(Date.now())
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
return useCallback(
(...args: Parameters<T>) => {
const handler = () => {
if (Date.now() - lastRan.current >= delay) {
callback(...args)
lastRan.current = Date.now()
} else {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
timeoutRef.current = setTimeout(
() => {
callback(...args)
lastRan.current = Date.now()
},
delay - (Date.now() - lastRan.current)
)
}
}
handler()
},
[callback, delay]
)
}