useLocalMessageSearch Composable
File: src/composables/useLocalMessageSearch.ts
Overview
Exports
- LocalSearchOptions - interface export
- LocalSearchResult - interface export
- useLocalMessageSearch - function export
- LocalMessageSearchReturn - type export
Functions
extractTextFromContent(content: MessagePart[])
No description available.
Parameters:
content: MessagePart[]
Returns: string
typescript
/**
* useLocalMessageSearch - Client-side search for encrypted/decrypted messages
*
* This composable provides local (client-side) search functionality for messages
* that are already loaded and decrypted in memory. This is essential for E2EE
* messages where server-side search cannot access the decrypted content.
*
* Use cases:
* - Searching encrypted DM conversations
* - Searching encrypted server channels
* - Filtering loaded messages without server round-trips
*/
import { ref, computed, watch, type Ref, type ComputedRef } from 'vue'
import type { Message, MessagePart } from '@/types'
export interface LocalSearchOptions {
/** Minimum query length to trigger search (default: 2) */
minQueryLength?: number
/** Debounce delay in ms (default: 150) */
debounceMs?: number
/** Case sensitive search (default: false) */
caseSensitive?: boolean
/** Search in user mentions (default: true) */
searchMentions?: boolean
/** Search in URLs (default: true) */
searchUrls?: boolean
}
export interface LocalSearchResult {
message: Message
/** Array of matching text snippets with context */
matches: string[]
/** Relevance score (higher = more matches) */
score: number
}
/**
* Extract searchable text from message content parts
*/
function extractTextFromContent(content: MessagePart[]): string1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
findMatchingSnippets(text: string, query: string, caseSensitive: boolean, contextChars: number = 30)
No description available.
Parameters:
text: stringquery: stringcaseSensitive: booleancontextChars: number = 30
Returns: string[]
typescript
/**
* Find matching snippets in text with context
*/
function findMatchingSnippets(
text: string,
query: string,
caseSensitive: boolean,
contextChars: number = 30
): string[]1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
useLocalMessageSearch(messages: Ref<Message[]> | ComputedRef<Message[]>, options: LocalSearchOptions = {})
No description available.
Parameters:
messages: Ref<Message[]> | ComputedRef<Message[]>options: LocalSearchOptions = {}
Returns: void
typescript
/**
* Local message search composable
*
* @param messages - Reactive array of messages to search through
* @param options - Search configuration options
*/
export function useLocalMessageSearch(
messages: Ref<Message[]> | ComputedRef<Message[]>,
options: LocalSearchOptions = {}
)1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
executeSearch()
No description available.
Parameters: None
Returns: Unknown
typescript
/**
* useLocalMessageSearch - Client-side search for encrypted/decrypted messages
*
* This composable provides local (client-side) search functionality for messages
* that are already loaded and decrypted in memory. This is essential for E2EE
* messages where server-side search cannot access the decrypted content.
*
* Use cases:
* - Searching encrypted DM conversations
* - Searching encrypted server channels
* - Filtering loaded messages without server round-trips
*/
import { ref, computed, watch, type Ref, type ComputedRef } from 'vue'
import type { Message, MessagePart } from '@/types'
export interface LocalSearchOptions {
/** Minimum query length to trigger search (default: 2) */
minQueryLength?: number
/** Debounce delay in ms (default: 150) */
debounceMs?: number
/** Case sensitive search (default: false) */
caseSensitive?: boolean
/** Search in user mentions (default: true) */
searchMentions?: boolean
/** Search in URLs (default: true) */
searchUrls?: boolean
}
export interface LocalSearchResult {
message: Message
/** Array of matching text snippets with context */
matches: string[]
/** Relevance score (higher = more matches) */
score: number
}
/**
* Extract searchable text from message content parts
*/
function extractTextFromContent(content: MessagePart[]): string {
if (!content || !Array.isArray(content)) {
return ''
}
return content
.map((part) => {
switch (part.type) {
case 'text':
return part.text || ''
case 'mention':
return part.mention || ''
case 'url':
return part.url || ''
case 'emoji':
return part.emoji?.name ? `:${part.emoji.name}:` : ''
case 'hashtag':
return part.name ? `#${part.name}` : ''
default:
return ''
}
})
.join(' ')
.trim()
}
/**
* Find matching snippets in text with context
*/
function findMatchingSnippets(
text: string,
query: string,
caseSensitive: boolean,
contextChars: number = 30
): string[] {
const snippets: string[] = []
const searchText = caseSensitive ? text : text.toLowerCase()
const searchQuery = caseSensitive ? query : query.toLowerCase()
let index = searchText.indexOf(searchQuery)
while (index !== -1) {
const start = Math.max(0, index - contextChars)
const end = Math.min(text.length, index + query.length + contextChars)
let snippet = text.slice(start, end)
if (start > 0) snippet = '...' + snippet
if (end < text.length) snippet = snippet + '...'
snippets.push(snippet)
index = searchText.indexOf(searchQuery, index + 1)
}
return snippets
}
/**
* Local message search composable
*
* @param messages - Reactive array of messages to search through
* @param options - Search configuration options
*/
export function useLocalMessageSearch(
messages: Ref<Message[]> | ComputedRef<Message[]>,
options: LocalSearchOptions = {}
) {
const {
minQueryLength = 2,
debounceMs = 150,
caseSensitive = false,
searchMentions = true,
searchUrls = true
} = options
// State
const query = ref('')
const isSearching = ref(false)
const searchResults = ref<LocalSearchResult[]>([])
// Debounce timer
let debounceTimer: ReturnType<typeof setTimeout> | null = null
// Computed
const hasQuery = computed(() => query.value.trim().length >= minQueryLength)
const hasResults = computed(() => searchResults.value.length > 0)
const resultCount = computed(() => searchResults.value.length)
/**
* Filter messages that are encrypted but not decrypted (can't search these)
*/
const searchableMessages = computed(() => {
return messages.value.filter(msg => {
// Skip deleted messages
if (msg.is_system) return false
// If encrypted but not decrypted, we can't search it
if (msg.encrypted && !msg.decrypted) {
return false
}
// Has content to search
return msg.content && Array.isArray(msg.content) && msg.content.length > 0
})
})
/**
* Count of encrypted messages that cannot be searched
*/
const unsearchableCount = computed(() => {
return messages.value.filter(msg =>
msg.encrypted && !msg.decrypted && !msg.is_system
).length
})
/**
* Execute local search
*/
const executeSearch = () =>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
searchDebounced()
No description available.
Parameters: None
Returns: Unknown
typescript
/**
* Execute search with debouncing
*/
const searchDebounced = () =>1
2
3
4
2
3
4
setQuery(newQuery: string)
No description available.
Parameters:
newQuery: string
Returns: Unknown
typescript
/**
* Set search query
*/
const setQuery = (newQuery: string) =>1
2
3
4
2
3
4
clearSearch()
No description available.
Parameters: None
Returns: Unknown
typescript
/**
* Clear search
*/
const clearSearch = () =>1
2
3
4
2
3
4
highlightMatches(text: string)
No description available.
Parameters:
text: string
Returns: string
typescript
/**
* Highlight matching text in a message's content
*/
const highlightMatches = (text: string): string =>1
2
3
4
2
3
4
getMessageIndex(messageId: string)
No description available.
Parameters:
messageId: string
Returns: number
typescript
/**
* Navigate to a specific search result (for use with UI)
*/
const getMessageIndex = (messageId: string): number =>1
2
3
4
2
3
4
Interfaces
LocalSearchOptions
No description available.
typescript
interface LocalSearchOptions {
/** Minimum query length to trigger search (default: 2) */
minQueryLength?: number
/** Debounce delay in ms (default: 150) */
debounceMs?: number
/** Case sensitive search (default: false) */
caseSensitive?: boolean
/** Search in user mentions (default: true) */
searchMentions?: boolean
/** Search in URLs (default: true) */
searchUrls?: boolean
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
LocalSearchResult
No description available.
typescript
interface LocalSearchResult {
message: Message
/** Array of matching text snippets with context */
matches: string[]
/** Relevance score (higher = more matches) */
score: number
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Source Code Insights
File Size: 8149 characters Lines of Code: 316 Imports: 2
Usage Example
typescript
import { LocalSearchOptions, LocalSearchResult, useLocalMessageSearch, LocalMessageSearchReturn } from '@/composables/useLocalMessageSearch'
// Example usage
extractTextFromContent()1
2
3
4
2
3
4
This documentation was automatically generated from the source code.
