chore: Enhancement + New Feature (#185)

* wip

* wip page

* chore: style

* wip pages

* wip pages

* chore: toggle

* chore: link

* feat: topic search

* chore: page section

* refactor: apply tailwind class ordering

* fix: handle loggedIn user for guest route

* feat: folder & image schema

* chore: move utils to shared

* refactor: tailwind class ordering

* feat: img ext for editor

* refactor: remove qa

* fix: tanstack start

* fix: wrong import

* chore: use toast

* chore: schema
This commit is contained in:
Aslam
2024-10-18 21:18:20 +07:00
committed by GitHub
parent c93c634a77
commit a440828f8c
158 changed files with 2808 additions and 1064 deletions

View File

@@ -0,0 +1,171 @@
import { useState, useCallback, useEffect } from "react"
type ResizeDirection = "left" | "right"
export type ElementDimensions = { width: number; height: number }
type HookParams = {
initialWidth?: number
initialHeight?: number
contentWidth?: number
contentHeight?: number
gridInterval: number
minWidth: number
minHeight: number
maxWidth: number
onDimensionsChange?: (dimensions: ElementDimensions) => void
}
export function useDragResize({
initialWidth,
initialHeight,
contentWidth,
contentHeight,
gridInterval,
minWidth,
minHeight,
maxWidth,
onDimensionsChange,
}: HookParams) {
const [dimensions, updateDimensions] = useState<ElementDimensions>({
width: Math.max(initialWidth ?? minWidth, minWidth),
height: Math.max(initialHeight ?? minHeight, minHeight),
})
const [boundaryWidth, setBoundaryWidth] = useState(Infinity)
const [resizeOrigin, setResizeOrigin] = useState(0)
const [initialDimensions, setInitialDimensions] = useState(dimensions)
const [resizeDirection, setResizeDirection] = useState<
ResizeDirection | undefined
>()
const widthConstraint = useCallback(
(proposedWidth: number, maxAllowedWidth: number) => {
const effectiveMinWidth = Math.max(
minWidth,
Math.min(
contentWidth ?? minWidth,
(gridInterval / 100) * maxAllowedWidth,
),
)
return Math.min(
maxAllowedWidth,
Math.max(proposedWidth, effectiveMinWidth),
)
},
[gridInterval, contentWidth, minWidth],
)
const handlePointerMove = useCallback(
(event: PointerEvent) => {
event.preventDefault()
const movementDelta =
(resizeDirection === "left"
? resizeOrigin - event.pageX
: event.pageX - resizeOrigin) * 2
const gridUnitWidth = (gridInterval / 100) * boundaryWidth
const proposedWidth = initialDimensions.width + movementDelta
const alignedWidth =
Math.round(proposedWidth / gridUnitWidth) * gridUnitWidth
const finalWidth = widthConstraint(alignedWidth, boundaryWidth)
const aspectRatio =
contentHeight && contentWidth ? contentHeight / contentWidth : 1
updateDimensions({
width: Math.max(finalWidth, minWidth),
height: Math.max(
contentWidth
? finalWidth * aspectRatio
: (contentHeight ?? minHeight),
minHeight,
),
})
},
[
widthConstraint,
resizeDirection,
boundaryWidth,
resizeOrigin,
gridInterval,
contentHeight,
contentWidth,
initialDimensions.width,
minWidth,
minHeight,
],
)
const handlePointerUp = useCallback(
(event: PointerEvent) => {
event.preventDefault()
event.stopPropagation()
setResizeOrigin(0)
setResizeDirection(undefined)
onDimensionsChange?.(dimensions)
},
[onDimensionsChange, dimensions],
)
const handleKeydown = useCallback(
(event: KeyboardEvent) => {
if (event.key === "Escape") {
event.preventDefault()
event.stopPropagation()
updateDimensions({
width: Math.max(initialDimensions.width, minWidth),
height: Math.max(initialDimensions.height, minHeight),
})
setResizeDirection(undefined)
}
},
[initialDimensions, minWidth, minHeight],
)
const initiateResize = useCallback(
(direction: ResizeDirection) =>
(event: React.PointerEvent<HTMLDivElement>) => {
event.preventDefault()
event.stopPropagation()
setBoundaryWidth(maxWidth)
setInitialDimensions({
width: Math.max(
widthConstraint(dimensions.width, maxWidth),
minWidth,
),
height: Math.max(dimensions.height, minHeight),
})
setResizeOrigin(event.pageX)
setResizeDirection(direction)
},
[
maxWidth,
widthConstraint,
dimensions.width,
dimensions.height,
minWidth,
minHeight,
],
)
useEffect(() => {
if (resizeDirection) {
document.addEventListener("keydown", handleKeydown)
document.addEventListener("pointermove", handlePointerMove)
document.addEventListener("pointerup", handlePointerUp)
return () => {
document.removeEventListener("keydown", handleKeydown)
document.removeEventListener("pointermove", handlePointerMove)
document.removeEventListener("pointerup", handlePointerUp)
}
}
}, [resizeDirection, handleKeydown, handlePointerMove, handlePointerUp])
return {
initiateResize,
isResizing: !!resizeDirection,
updateDimensions,
currentWidth: Math.max(dimensions.width, minWidth),
currentHeight: Math.max(dimensions.height, minHeight),
}
}

View File

@@ -0,0 +1,61 @@
import * as React from "react"
import type { Editor } from "@tiptap/core"
import type { Node } from "@tiptap/pm/model"
import { isUrl } from "@shared/editor/lib/utils"
interface UseImageActionsProps {
editor: Editor
node: Node
src: string
onViewClick: (value: boolean) => void
}
export type ImageActionHandlers = {
onView?: () => void
onDownload?: () => void
onCopy?: () => void
onCopyLink?: () => void
onRemoveImg?: () => void
}
export const useImageActions = ({
editor,
node,
src,
onViewClick,
}: UseImageActionsProps) => {
const isLink = React.useMemo(() => isUrl(src), [src])
const onView = React.useCallback(() => {
onViewClick(true)
}, [onViewClick])
const onDownload = React.useCallback(() => {
editor.commands.downloadImage({ src: node.attrs.src, alt: node.attrs.alt })
}, [editor.commands, node.attrs.alt, node.attrs.src])
const onCopy = React.useCallback(() => {
editor.commands.copyImage({ src: node.attrs.src })
}, [editor.commands, node.attrs.src])
const onCopyLink = React.useCallback(() => {
editor.commands.copyLink({ src: node.attrs.src })
}, [editor.commands, node.attrs.src])
const onRemoveImg = React.useCallback(() => {
editor.commands.command(({ tr, dispatch }) => {
const { selection } = tr
const nodeAtSelection = tr.doc.nodeAt(selection.from)
if (nodeAtSelection && nodeAtSelection.type.name === "image") {
if (dispatch) {
tr.deleteSelection()
return true
}
}
return false
})
}, [editor.commands])
return { isLink, onView, onDownload, onCopy, onCopyLink, onRemoveImg }
}