generated from nhcarrigan/template
94991796be
## Summary This PR includes a batch of bug fixes and new features: ### Bug Fixes - **Links in chat history now open in default browser** instead of navigating within the app - Closes #54 - **Allow spaces in tab names** - space key no longer acts like enter when renaming tabs - Closes #52 ### New Features - **`/cd` command** - Change the working directory of an active tab with context preservation - Closes #55 - **`/search` command** - Search and highlight matches within the conversation - Closes #32 ## Test Plan - [ ] Click a link in chat history and verify it opens in the default browser - [ ] Rename a tab and verify spaces can be typed - [ ] Use `/cd <path>` and verify the directory changes while preserving conversation context - [ ] Use `/search <query>` and verify matches are highlighted in yellow - [ ] Use `/search` with no args to clear the search highlighting ✨ This PR was created with help from Hikari~ 🌸 Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com> Reviewed-on: #56 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
62 lines
1.4 KiB
Svelte
62 lines
1.4 KiB
Svelte
<script lang="ts">
|
|
export let content: string;
|
|
export let searchQuery: string;
|
|
|
|
interface TextPart {
|
|
text: string;
|
|
isMatch: boolean;
|
|
}
|
|
|
|
function getHighlightedParts(text: string, query: string): TextPart[] {
|
|
if (!query) {
|
|
return [{ text, isMatch: false }];
|
|
}
|
|
|
|
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
const regex = new RegExp(`(${escapedQuery})`, "gi");
|
|
const parts: TextPart[] = [];
|
|
let lastIndex = 0;
|
|
let match: RegExpExecArray | null;
|
|
|
|
while ((match = regex.exec(text)) !== null) {
|
|
// Add non-matching text before the match
|
|
if (match.index > lastIndex) {
|
|
parts.push({
|
|
text: text.slice(lastIndex, match.index),
|
|
isMatch: false,
|
|
});
|
|
}
|
|
|
|
// Add the matching text
|
|
parts.push({
|
|
text: match[1],
|
|
isMatch: true,
|
|
});
|
|
|
|
lastIndex = regex.lastIndex;
|
|
}
|
|
|
|
// Add any remaining text after the last match
|
|
if (lastIndex < text.length) {
|
|
parts.push({
|
|
text: text.slice(lastIndex),
|
|
isMatch: false,
|
|
});
|
|
}
|
|
|
|
return parts;
|
|
}
|
|
|
|
$: parts = getHighlightedParts(content, searchQuery);
|
|
</script>
|
|
|
|
<span class="whitespace-pre-wrap">
|
|
{#each parts as part, index (index)}
|
|
{#if part.isMatch}
|
|
<mark class="search-highlight">{part.text}</mark>
|
|
{:else}
|
|
{part.text}
|
|
{/if}
|
|
{/each}
|
|
</span>
|