philomena/assets/js/utils/suggestions-model.ts

79 lines
2.1 KiB
TypeScript
Raw Normal View History

/**
* Matched string is represented as an array of parts that matched and parts that didn't.
* It is designed to be this much generic to allow for matches in random places in case
* if we decide to support more complex matching.
*
* String parts that didn't match are represented as primitive strings. String parts
* that matched are represented as objects with a `matched` property.
*/
export type MatchPart = string | { matched: string };
interface CanonicalTagSuggestion {
/**
* No alias name is present for the canonical tag. It's declared here explicitly
* to make TypeScript pick up this field as the tagged union discriminator.
*/
alias?: undefined;
/**
* The canonical name of the tag (non-alias).
*/
canonical: MatchPart[];
/**
* Number of images tagged with this tag.
*/
images: number;
}
interface AliasTagSuggestion {
/**
* The alias name of the tag.
*/
alias: MatchPart[];
/**
* The canonical tag the alias points to.
*/
canonical: string;
/**
* Number of images tagged with this tag.
*/
images: number;
}
export type TagSuggestion = CanonicalTagSuggestion | AliasTagSuggestion;
/**
* Infers where the prefix match occurred in the target string according to
* the given prefix. It assumes that `target` either starts with `prefix` or
* contains the `prefix` after the namespace separator.
*/
export function prefixMatchParts(target: string, prefix: string): MatchPart[] {
2025-03-17 23:21:54 +00:00
return prefixMatchPartsImpl(target, prefix).filter(part => typeof part !== 'string' || part.length > 0);
}
function prefixMatchPartsImpl(target: string, prefix: string): MatchPart[] {
const targetLower = target.toLowerCase();
const prefixLower = prefix.toLowerCase();
if (targetLower.startsWith(prefixLower)) {
const matched = target.slice(0, prefix.length);
return [{ matched }, target.slice(matched.length)];
}
const separator = target.indexOf(':');
if (separator < 0) {
return [target];
}
const matchStart = separator + 1;
return [
target.slice(0, matchStart),
{ matched: target.slice(matchStart, matchStart + prefix.length) },
target.slice(matchStart + prefix.length),
];
}