mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-30 20:56:36 +00:00
Change matcher to accept a TemplateResult as well to avoid using unsafeHtml
This commit is contained in:
parent
7ddafdd45f
commit
448f5d7be1
@ -537,9 +537,6 @@ export function createMatches(score: undefined | FuzzyScore): Match[] {
|
||||
return _createMatches(_score, wordPos);
|
||||
}
|
||||
|
||||
// The first and second elements in score represent total score, and the offset at which
|
||||
// matching started. For this method, we only care about match positions, not the score
|
||||
// or offset.
|
||||
const findFirstOutOfRangeElement = (number, score: FuzzyScore) =>
|
||||
score.findIndex((num) => num < number);
|
||||
|
||||
@ -554,7 +551,12 @@ export function createMatchesFragmented(
|
||||
const matches: Match[][] = [];
|
||||
const wordPos = score[1];
|
||||
let lengthCounter = 0;
|
||||
|
||||
// The first and second elements in score represent total score, and the offset at which
|
||||
// matching started. For this method, we only care about the rest of the score array
|
||||
// which represents matched position indexes.
|
||||
const _score = score.splice(2);
|
||||
|
||||
const fragmentedScores: FuzzyScore[] = [];
|
||||
|
||||
for (const string of strings) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { TemplateResult } from "lit-html";
|
||||
import {
|
||||
createMatches,
|
||||
createMatchesFragmented,
|
||||
@ -24,10 +25,10 @@ type FuzzySequentialMatcher = (
|
||||
export const fuzzySequentialMatch: FuzzySequentialMatcher = (
|
||||
filter,
|
||||
item,
|
||||
decorate = createMatchDecorator("[", "]")
|
||||
decorate = createMatchDecorator((letter) => `[${letter}]`)
|
||||
) => {
|
||||
let topScore = Number.NEGATIVE_INFINITY;
|
||||
const decoratedStrings: string[][] = [];
|
||||
const decoratedStrings: Decoration[][][] = [];
|
||||
const strings = item.treatArrayAsSingleString
|
||||
? [item.strings.join("")]
|
||||
: item.strings;
|
||||
@ -88,7 +89,7 @@ export const fuzzySequentialMatch: FuzzySequentialMatcher = (
|
||||
export interface ScorableTextItem {
|
||||
score?: number;
|
||||
strings: string[];
|
||||
decoratedStrings?: string[][];
|
||||
decoratedStrings?: Decoration[][][];
|
||||
treatArrayAsSingleString?: boolean;
|
||||
}
|
||||
|
||||
@ -101,7 +102,7 @@ type FuzzyFilterSort = <T extends ScorableTextItem>(
|
||||
export const fuzzyFilterSort: FuzzyFilterSort = (
|
||||
filter,
|
||||
items,
|
||||
decorate = createMatchDecorator("[", "]")
|
||||
decorate = createMatchDecorator((letter) => `[${letter}]`)
|
||||
) => {
|
||||
return items
|
||||
.map((item) => {
|
||||
@ -118,48 +119,56 @@ export const fuzzyFilterSort: FuzzyFilterSort = (
|
||||
);
|
||||
};
|
||||
|
||||
type Decoration = string | TemplateResult;
|
||||
|
||||
export type Surrounder = (matchedChunk: Decoration) => Decoration;
|
||||
|
||||
type MatchDecorator = (
|
||||
word: string,
|
||||
item: ScorableTextItem,
|
||||
scores?: FuzzyScore
|
||||
) => string[];
|
||||
) => Decoration[][];
|
||||
|
||||
export const createMatchDecorator: (
|
||||
left: string,
|
||||
right: string
|
||||
) => MatchDecorator = (left, right) => (word, item, scores) =>
|
||||
_decorateMatch(word, [left, right], item, scores);
|
||||
surrounder: Surrounder
|
||||
) => MatchDecorator = (surrounder) => (word, item, scores) =>
|
||||
_decorateMatch(word, surrounder, item, scores);
|
||||
|
||||
const _decorateMatch: (
|
||||
word: string,
|
||||
surroundWith: [string, string],
|
||||
surrounder: Surrounder,
|
||||
item: ScorableTextItem,
|
||||
scores?: FuzzyScore
|
||||
) => string[] = (word, surroundWith, item, scores) => {
|
||||
) => Decoration[][] = (word, surrounder, item, scores) => {
|
||||
if (!scores) {
|
||||
return [word];
|
||||
return [[word]];
|
||||
}
|
||||
|
||||
const decoratedText: string[] = [];
|
||||
const decoratedText: Decoration[][] = [];
|
||||
const matches = item.treatArrayAsSingleString
|
||||
? createMatchesFragmented(scores, item.strings)
|
||||
: [createMatches(scores)];
|
||||
const [left, right] = surroundWith;
|
||||
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
const match = matches[i];
|
||||
const _word = item.treatArrayAsSingleString ? item.strings[i] : word;
|
||||
let pos = 0;
|
||||
let actualWord = "";
|
||||
const actualWord: Decoration[] = [];
|
||||
|
||||
for (const fragmentedMatch of match) {
|
||||
actualWord +=
|
||||
_word.substring(pos, fragmentedMatch.start) +
|
||||
left +
|
||||
_word.substring(fragmentedMatch.start, fragmentedMatch.end) +
|
||||
right;
|
||||
const unmatchedChunk = _word.substring(pos, fragmentedMatch.start);
|
||||
const matchedChunk = _word.substring(
|
||||
fragmentedMatch.start,
|
||||
fragmentedMatch.end
|
||||
);
|
||||
|
||||
actualWord.push(unmatchedChunk);
|
||||
actualWord.push(surrounder(matchedChunk));
|
||||
|
||||
pos = fragmentedMatch.end;
|
||||
}
|
||||
actualWord += _word.substring(pos);
|
||||
|
||||
actualWord.push(_word.substring(pos));
|
||||
decoratedText.push(actualWord);
|
||||
}
|
||||
return decoratedText;
|
||||
|
@ -55,7 +55,6 @@ import {
|
||||
import { QuickBarParams } from "./show-dialog-quick-bar";
|
||||
import "../../components/ha-chip";
|
||||
import { toTitleCase } from "../../common/string/casing";
|
||||
import { unsafeHTML } from "lit-html/directives/unsafe-html";
|
||||
|
||||
interface QuickBarItem extends ScorableTextItem {
|
||||
primaryText: string;
|
||||
@ -121,6 +120,7 @@ export class QuickBar extends LitElement {
|
||||
this._focusSet = false;
|
||||
this._filter = "";
|
||||
this._search = "";
|
||||
this._resetDecorations();
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@ -258,7 +258,7 @@ export class QuickBar extends LitElement {
|
||||
></ha-icon>`}
|
||||
<span class="item-text primary"
|
||||
>${item.decoratedStrings
|
||||
? unsafeHTML(item.decoratedStrings[0])
|
||||
? item.decoratedStrings[0]
|
||||
: item.primaryText}</span
|
||||
>
|
||||
${item.altText
|
||||
@ -266,7 +266,7 @@ export class QuickBar extends LitElement {
|
||||
<span slot="secondary" class="item-text secondary">
|
||||
<span
|
||||
>${item.decoratedStrings
|
||||
? unsafeHTML(item.decoratedStrings[1])
|
||||
? item.decoratedStrings[1]
|
||||
: item.altText}</span
|
||||
></span
|
||||
>
|
||||
@ -298,16 +298,12 @@ export class QuickBar extends LitElement {
|
||||
slot="icon"
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
${decoratedItem
|
||||
? unsafeHTML(decoratedItem[0])
|
||||
: item.categoryText}</ha-chip
|
||||
${decoratedItem ? decoratedItem[0] : item.categoryText}</ha-chip
|
||||
>
|
||||
</span>
|
||||
|
||||
<span class="command-text"
|
||||
>${decoratedItem
|
||||
? unsafeHTML(decoratedItem[1])
|
||||
: item.primaryText}</span
|
||||
>${decoratedItem ? decoratedItem[1] : item.primaryText}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
`;
|
||||
@ -629,7 +625,10 @@ export class QuickBar extends LitElement {
|
||||
return fuzzyFilterSort<QuickBarItem>(
|
||||
filter.trimLeft(),
|
||||
items,
|
||||
createMatchDecorator("<span class='highlight-letter'>", "</span>")
|
||||
createMatchDecorator(
|
||||
(matchedChunk) =>
|
||||
html`<span class="highlight-letter">${matchedChunk}</span>`
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -77,8 +77,13 @@ describe("fuzzySequentialMatch", () => {
|
||||
|
||||
it(`decorates '${expectation.pattern}' as '${expectation.expected?.decoratedString}'`, () => {
|
||||
const res = fuzzySequentialMatch(expectation.pattern, item);
|
||||
assert.includeDeepMembers(res!.decoratedStrings!, [
|
||||
[expectation.expected!.decoratedString!],
|
||||
const allDecoratedStrings = [
|
||||
res!.decoratedStrings![0][0].join(""),
|
||||
res!.decoratedStrings![1][0].join(""),
|
||||
];
|
||||
|
||||
assert.includeDeepMembers(allDecoratedStrings, [
|
||||
expectation.expected!.decoratedString!,
|
||||
]);
|
||||
});
|
||||
}
|
||||
@ -114,7 +119,7 @@ describe("fuzzyFilterSort", () => {
|
||||
strings: ["light.chandelier", "Chandelier"],
|
||||
score: 0,
|
||||
};
|
||||
const itemsBeforeFilter = [
|
||||
const itemsBeforeFilter: ScorableTextItem[] = [
|
||||
automationTicker,
|
||||
sensorTicker,
|
||||
timerCheckRouter,
|
||||
@ -149,7 +154,13 @@ describe("fuzzyFilterSort", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const res = fuzzyFilterSort(filter, itemsBeforeFilter);
|
||||
const res = fuzzyFilterSort(filter, itemsBeforeFilter).map((item) => ({
|
||||
...item,
|
||||
decoratedStrings: [
|
||||
[item.decoratedStrings![0][0].join("")],
|
||||
[item.decoratedStrings![1][0].join("")],
|
||||
],
|
||||
}));
|
||||
|
||||
assert.deepEqual(res, expectedItemsAfterFilter);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user