hidden shit, and online status bars, search settings
This commit is contained in:
114
src/app/utils/fuzzy.ts
Normal file
114
src/app/utils/fuzzy.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
export type FuzzyMatch = {
|
||||
score: number;
|
||||
indices: number[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Subsequence fuzzy match with fzf-style scoring.
|
||||
* All query characters must appear in order; higher scores rank first.
|
||||
*/
|
||||
export function fuzzyMatch(query: string, text: string): FuzzyMatch | null {
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) return { score: 0, indices: [] };
|
||||
|
||||
const t = text.toLowerCase();
|
||||
if (!t) return null;
|
||||
|
||||
// Fast path: contiguous substring
|
||||
const substringIndex = t.indexOf(q);
|
||||
if (substringIndex !== -1) {
|
||||
const indices = Array.from({ length: q.length }, (_, i) => substringIndex + i);
|
||||
// Prefer earlier matches and shorter haystacks
|
||||
const score = 10_000 - substringIndex * 10 - (t.length - q.length);
|
||||
return { score, indices };
|
||||
}
|
||||
|
||||
let ti = 0;
|
||||
const indices: number[] = [];
|
||||
let score = 0;
|
||||
let prevIndex = -1;
|
||||
let consecutive = 0;
|
||||
|
||||
for (let qi = 0; qi < q.length; qi += 1) {
|
||||
const ch = q[qi];
|
||||
let found = -1;
|
||||
for (; ti < t.length; ti += 1) {
|
||||
if (t[ti] === ch) {
|
||||
found = ti;
|
||||
ti += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found === -1) return null;
|
||||
|
||||
indices.push(found);
|
||||
|
||||
// Consecutive bonus
|
||||
if (prevIndex === found - 1) {
|
||||
consecutive += 1;
|
||||
score += 15 + consecutive * 5;
|
||||
} else {
|
||||
consecutive = 0;
|
||||
score += 1;
|
||||
}
|
||||
|
||||
// Word-boundary / start bonus
|
||||
if (
|
||||
found === 0 ||
|
||||
t[found - 1] === ' ' ||
|
||||
t[found - 1] === '-' ||
|
||||
t[found - 1] === '_' ||
|
||||
t[found - 1] === '&' ||
|
||||
t[found - 1] === '/'
|
||||
) {
|
||||
score += 20;
|
||||
}
|
||||
|
||||
// Prefer earlier matches
|
||||
score -= found;
|
||||
|
||||
prevIndex = found;
|
||||
}
|
||||
|
||||
// Prefer tighter overall spans
|
||||
const span = indices[indices.length - 1] - indices[0] + 1;
|
||||
score += Math.max(0, 50 - (span - q.length) * 2);
|
||||
// Prefer shorter labels slightly
|
||||
score -= Math.max(0, t.length - q.length) * 0.1;
|
||||
|
||||
return { score, indices };
|
||||
}
|
||||
|
||||
/** Best fuzzy match across multiple strings (title, keywords, etc.). */
|
||||
export function fuzzyMatchAny(query: string, texts: Array<string | undefined | null>): FuzzyMatch | null {
|
||||
let best: FuzzyMatch | null = null;
|
||||
for (const text of texts) {
|
||||
if (!text) continue;
|
||||
const match = fuzzyMatch(query, text);
|
||||
if (match && (!best || match.score > best.score)) {
|
||||
best = match;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
export function fuzzyFilter<T>(
|
||||
items: T[],
|
||||
query: string,
|
||||
getTexts: (item: T) => Array<string | undefined | null>
|
||||
): Array<{ item: T; score: number }> {
|
||||
const q = query.trim();
|
||||
if (!q) {
|
||||
return items.map((item) => ({ item, score: 0 }));
|
||||
}
|
||||
|
||||
const results: Array<{ item: T; score: number }> = [];
|
||||
for (const item of items) {
|
||||
const match = fuzzyMatchAny(q, getTexts(item));
|
||||
if (match) {
|
||||
results.push({ item, score: match.score });
|
||||
}
|
||||
}
|
||||
results.sort((a, b) => b.score - a.score);
|
||||
return results;
|
||||
}
|
||||
@@ -181,10 +181,8 @@ export function isValidChild(mEvent: MatrixEvent): boolean {
|
||||
if (mEvent.getType() !== StateEvent.SpaceChild) return false;
|
||||
const stateKey = mEvent.getStateKey();
|
||||
if (!stateKey || !stateKey.startsWith('!')) return false;
|
||||
const { via } = mEvent.getContent<{ via?: string[] }>();
|
||||
// via is optional on m.space.child; only reject malformed values
|
||||
if (via !== undefined && !Array.isArray(via)) return false;
|
||||
return true;
|
||||
// via is required; omitting it (e.g. empty {}) removes the child per Matrix spec
|
||||
return Array.isArray(mEvent.getContent<{ via?: string[] }>().via);
|
||||
}
|
||||
|
||||
export const getAllParents = (roomToParents: RoomToParents, roomId: string): Set<string> => {
|
||||
|
||||
Reference in New Issue
Block a user