Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions webview-ui/src/components/history/CopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@ export const CopyButton = ({ itemTask }: CopyButtonProps) => {
const onCopy = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation()
const tempDiv = document.createElement("div")
tempDiv.innerHTML = itemTask
const text = tempDiv.textContent || tempDiv.innerText || ""

if (!isCopied) {
copy(text)
copy(itemTask)
}
},
[isCopied, copy, itemTask],
Expand Down
10 changes: 7 additions & 3 deletions webview-ui/src/components/history/TaskItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import { useAppTranslation } from "@/i18n/TranslationContext"
import TaskItemHeader from "./TaskItemHeader"
import TaskItemFooter from "./TaskItemFooter"

interface DisplayHistoryItem extends HistoryItem {
highlight?: string
}

interface TaskItemProps {
item: HistoryItem
item: DisplayHistoryItem
variant: "compact" | "full"
showWorkspace?: boolean
isSelectionMode?: boolean
Expand Down Expand Up @@ -103,8 +107,8 @@ const TaskItem = ({
overflowWrap: "anywhere",
}}
data-testid={isCompact ? undefined : "task-content"}
{...(isCompact ? {} : { dangerouslySetInnerHTML: { __html: item.task } })}>
{isCompact ? item.task : undefined}
{...(item.highlight ? { dangerouslySetInnerHTML: { __html: item.highlight } } : {})}>
{item.highlight ? undefined : item.task}
</div>

{/* Task Item Footer */}
Expand Down
2 changes: 1 addition & 1 deletion webview-ui/src/components/history/useTaskSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const useTaskSearch = () => {

return {
...result.item,
task: highlightFzfMatch(
highlight: highlightFzfMatch(
result.item.task,
positions.filter((p) => p < taskEndIndex),
),
Expand Down
31 changes: 30 additions & 1 deletion webview-ui/src/utils/highlight.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
import { LRUCache } from "lru-cache"

// LRU cache for escapeHtml with reasonable size limit
const escapeHtmlCache = new LRUCache<string, string>({ max: 500 })

function escapeHtml(text: string): string {
// Check cache first
const cached = escapeHtmlCache.get(text)
if (cached !== undefined) {
return cached
}

// Compute escaped text
const escaped = text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;")

// Cache the result
escapeHtmlCache.set(text, escaped)

return escaped
}

export function highlightFzfMatch(
text: string,
positions: number[],
Expand Down Expand Up @@ -39,6 +65,9 @@ export function highlightFzfMatch(

// Build final string
return parts
.map((part) => (part.highlight ? `<span class="${highlightClassName}">${part.text}</span>` : part.text))
.map((part) => {
const escapedText = escapeHtml(part.text)
return part.highlight ? `<span class="${highlightClassName}">${escapedText}</span>` : escapedText
})
.join("")
}