File tree Expand file tree Collapse file tree 4 files changed +40
-21
lines changed Expand file tree Collapse file tree 4 files changed +40
-21
lines changed Original file line number Diff line number Diff line change @@ -10,30 +10,16 @@ type CopyButtonProps = {
1010 className ?: string
1111}
1212
13- /**
14- * Strips only history highlight spans from text while preserving other HTML
15- * Targets: <span class="history-item-highlight">content</span>
16- * @param text - Text that may contain highlight spans
17- * @returns Text with highlight spans removed but content preserved
18- */
19- const stripHistoryHighlightSpans = ( text : string ) : string => {
20- // Match opening tag, capture content until closing tag
21- // The [\s\S]*? pattern matches any character (including newlines) non-greedily,
22- // which properly handles content with < characters
23- return text . replace ( / < s p a n \s + c l a s s = " h i s t o r y - i t e m - h i g h l i g h t " > ( [ \s \S ] * ?) < \/ s p a n > / g, "$1" )
24- }
25-
2613export const CopyButton = ( { itemTask, className } : CopyButtonProps ) => {
2714 const { isCopied, copy } = useClipboard ( )
2815 const { t } = useAppTranslation ( )
2916
3017 const onCopy = useCallback (
3118 ( e : React . MouseEvent ) => {
3219 e . stopPropagation ( )
20+
3321 if ( ! isCopied ) {
34- // Strip only history highlight spans before copying to clipboard
35- const cleanText = stripHistoryHighlightSpans ( itemTask )
36- copy ( cleanText )
22+ copy ( itemTask )
3723 }
3824 } ,
3925 [ isCopied , copy , itemTask ] ,
Original file line number Diff line number Diff line change @@ -9,8 +9,12 @@ import { useAppTranslation } from "@/i18n/TranslationContext"
99import TaskItemHeader from "./TaskItemHeader"
1010import TaskItemFooter from "./TaskItemFooter"
1111
12+ interface DisplayHistoryItem extends HistoryItem {
13+ highlight ?: string
14+ }
15+
1216interface TaskItemProps {
13- item : HistoryItem
17+ item : DisplayHistoryItem
1418 variant : "compact" | "full"
1519 showWorkspace ?: boolean
1620 isSelectionMode ?: boolean
@@ -103,8 +107,8 @@ const TaskItem = ({
103107 overflowWrap : "anywhere" ,
104108 } }
105109 data-testid = { isCompact ? undefined : "task-content" }
106- { ...( isCompact ? { } : { dangerouslySetInnerHTML : { __html : item . task } } ) } >
107- { isCompact ? item . task : undefined }
110+ { ...( item . highlight ? { dangerouslySetInnerHTML : { __html : item . highlight } } : { } ) } >
111+ { item . highlight ? undefined : item . task }
108112 </ div >
109113
110114 { /* Task Item Footer */ }
Original file line number Diff line number Diff line change @@ -48,7 +48,7 @@ export const useTaskSearch = () => {
4848
4949 return {
5050 ...result . item ,
51- task : highlightFzfMatch (
51+ highlight : highlightFzfMatch (
5252 result . item . task ,
5353 positions . filter ( ( p ) => p < taskEndIndex ) ,
5454 ) ,
Original file line number Diff line number Diff line change 1+ import { LRUCache } from "lru-cache"
2+
3+ // LRU cache for escapeHtml with reasonable size limit
4+ const escapeHtmlCache = new LRUCache < string , string > ( { max : 500 } )
5+
6+ function escapeHtml ( text : string ) : string {
7+ // Check cache first
8+ const cached = escapeHtmlCache . get ( text )
9+ if ( cached !== undefined ) {
10+ return cached
11+ }
12+
13+ // Compute escaped text
14+ const escaped = text
15+ . replace ( / & / g, "&" )
16+ . replace ( / < / g, "<" )
17+ . replace ( / > / g, ">" )
18+ . replace ( / " / g, """ )
19+ . replace ( / ' / g, "'" )
20+
21+ // Cache the result
22+ escapeHtmlCache . set ( text , escaped )
23+
24+ return escaped
25+ }
26+
127export function highlightFzfMatch (
228 text : string ,
329 positions : number [ ] ,
@@ -39,6 +65,9 @@ export function highlightFzfMatch(
3965
4066 // Build final string
4167 return parts
42- . map ( ( part ) => ( part . highlight ? `<span class="${ highlightClassName } ">${ part . text } </span>` : part . text ) )
68+ . map ( ( part ) => {
69+ const escapedText = escapeHtml ( part . text )
70+ return part . highlight ? `<span class="${ highlightClassName } ">${ escapedText } </span>` : escapedText
71+ } )
4372 . join ( "" )
4473}
You can’t perform that action at this time.
0 commit comments