const FILTER_RE = /\b(from|has|mentions|before|after|in|pinned):(\S+)/gi; export function parseFilters(rawQuery) { const filters = {}; let textQuery = rawQuery; let match; while ((match = FILTER_RE.exec(rawQuery)) !== null) { const key = match[1].toLowerCase(); const val = match[2]; switch (key) { case 'from': filters.senderName = val; break; case 'has': if (val === 'link') filters.hasLink = true; else if (val === 'file' || val === 'attachment') filters.hasFile = true; else if (val === 'image') filters.hasImage = true; else if (val === 'video') filters.hasVideo = true; else if (val === 'mention') filters.hasMention = true; break; case 'mentions': filters.hasMention = true; filters.mentionName = val; break; case 'before': filters.before = val; break; case 'after': filters.after = val; break; case 'in': filters.channelName = val; break; case 'pinned': filters.pinned = val === 'true' || val === 'yes'; break; } textQuery = textQuery.replace(match[0], ''); } FILTER_RE.lastIndex = 0; return { textQuery: textQuery.trim(), filters }; } /** * Detects if the user is mid-typing a filter token. * e.g. "hello from:par" → { prefix: 'from', partial: 'par' } * e.g. "from:" → { prefix: 'from', partial: '' } * Returns null if no active prefix is detected at the end of text. */ export function detectActivePrefix(text) { if (!text) return null; // Match a filter prefix at the end of the string, possibly with a partial value const m = text.match(/\b(from|in|has|mentions):(\S*)$/i); if (m) { return { prefix: m[1].toLowerCase(), partial: m[2].toLowerCase() }; } return null; }