From 4ce825097d3d85891288ec7914686d651e7b66b5 Mon Sep 17 00:00:00 2001 From: Gimle Larpes Date: Fri, 4 Jul 2025 19:23:46 +0200 Subject: [PATCH] implement some requested changes --- src/app/hooks/useTimeoutToggle.ts | 37 +++++++++ src/app/plugins/react-custom-html-parser.tsx | 85 +++++++------------- src/app/styles/CustomHtml.css.ts | 4 +- 3 files changed, 69 insertions(+), 57 deletions(-) create mode 100644 src/app/hooks/useTimeoutToggle.ts diff --git a/src/app/hooks/useTimeoutToggle.ts b/src/app/hooks/useTimeoutToggle.ts new file mode 100644 index 00000000..7eda99c1 --- /dev/null +++ b/src/app/hooks/useTimeoutToggle.ts @@ -0,0 +1,37 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; + +/** + * Temporarily sets a boolean state. + * + * @param duration - Duration in milliseconds before resetting (default: 1500) + * @param initial - Initial value (default: false) + */ +export function useTimeoutToggle(duration = 1500, initial = false): [boolean, () => void] { + const [active, setActive] = useState(initial); + const timeoutRef = useRef(null); + + const clear = () => { + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }; + + const trigger = useCallback(() => { + setActive(!initial); + clear(); + timeoutRef.current = window.setTimeout(() => { + setActive(initial); + timeoutRef.current = null; + }, duration); + }, [duration, initial]); + + useEffect( + () => () => { + clear(); + }, + [] + ); + + return [active, trigger]; +} diff --git a/src/app/plugins/react-custom-html-parser.tsx b/src/app/plugins/react-custom-html-parser.tsx index ee7b0086..51e411ea 100644 --- a/src/app/plugins/react-custom-html-parser.tsx +++ b/src/app/plugins/react-custom-html-parser.tsx @@ -41,6 +41,7 @@ import { } from './matrix-to'; import { onEnterOrSpace } from '../utils/keyboard'; import { copyToClipboard, tryDecodeURIComponent } from '../utils/dom'; +import { useTimeoutToggle } from '../hooks/useTimeoutToggle'; const ReactPrism = lazy(() => import('./react-prism/ReactPrism')); @@ -204,51 +205,6 @@ export const highlightText = ( ); }); -type CodeBlockControlsProps = { - copied: boolean; - onCopy: () => void; - collapsible: boolean; - collapsed: boolean; - onToggle: () => void; -}; - -function CodeBlockControls({ - copied, - onCopy, - collapsible, - collapsed, - onToggle, -}: CodeBlockControlsProps) { - return ( - // Needs a better copy icon -
- - - - {collapsible && ( - - - - )} -
- ); -} - export function CodeBlock(children: ChildNode[], opts: HTMLReactParserOptions) { const LINE_LIMIT = 14; @@ -272,7 +228,7 @@ export function CodeBlock(children: ChildNode[], opts: HTMLReactParserOptions) { return text; }, []); - const [copied, setCopied] = useState(false); + const [copied, setCopied] = useTimeoutToggle(); const collapsible = useMemo( () => extractTextFromChildren(children).split('\n').length > LINE_LIMIT, [children, extractTextFromChildren] @@ -281,8 +237,8 @@ export function CodeBlock(children: ChildNode[], opts: HTMLReactParserOptions) { const handleCopy = useCallback(() => { copyToClipboard(extractTextFromChildren(children)); - setCopied(true); - }, [children, extractTextFromChildren]); + setCopied(); + }, [children, extractTextFromChildren, setCopied]); const toggleCollapse = useCallback(() => { setCollapsed((prev) => !prev); @@ -290,13 +246,32 @@ export function CodeBlock(children: ChildNode[], opts: HTMLReactParserOptions) { return ( <> - +
+ + + + {collapsible && ( + + + + )} +