mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
Do not downgrade favorites to recent search results when you reselect them via the search results list. PR Close #63906
121 lines
3.3 KiB
TypeScript
121 lines
3.3 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
|
|
import {computed, inject, Injectable, signal} from '@angular/core';
|
|
import {LOCAL_STORAGE} from '../providers';
|
|
import {SearchResultItem} from '../interfaces';
|
|
|
|
// Add version postfix to the key in case (if ever) the data model changes in the future.
|
|
export const SEARCH_HISTORY_LS_KEY = 'docs-search-history-v1';
|
|
|
|
export const MAX_RECENT_HISTORY_SIZE = 10;
|
|
|
|
// Represents V1 history item
|
|
export interface HistoryItem {
|
|
id: string;
|
|
labelHtml: string;
|
|
subLabelHtml?: string;
|
|
url: string;
|
|
isFavorite: boolean;
|
|
createdAt: number;
|
|
}
|
|
|
|
function cleanUpHtml(label: string | null): string {
|
|
return (label || '').replace(/<\/?mark>/g, '');
|
|
}
|
|
|
|
@Injectable({providedIn: 'root'})
|
|
export class SearchHistory {
|
|
private readonly localStorage = inject(LOCAL_STORAGE);
|
|
private readonly history = signal<Map<string, HistoryItem>>(new Map());
|
|
|
|
private readonly allItems = computed(() =>
|
|
Array.from(this.history().values()).sort((a, b) => b.createdAt - a.createdAt),
|
|
);
|
|
|
|
readonly items = computed<{recent: HistoryItem[]; favorite: HistoryItem[]}>(() => ({
|
|
recent: this.allItems().filter((v) => !v.isFavorite),
|
|
favorite: this.allItems().filter((v) => v.isFavorite),
|
|
}));
|
|
|
|
readonly hasItems = computed(() => this.allItems().length > 0);
|
|
|
|
constructor() {
|
|
this.loadHistory();
|
|
}
|
|
|
|
addItem(item: SearchResultItem | HistoryItem): void {
|
|
// We don't want to reset nor update the creation date of favorites
|
|
if (this.history().get(item.id)?.isFavorite) {
|
|
return;
|
|
}
|
|
|
|
this.updateHistory((map) => {
|
|
map.set(item.id, {
|
|
id: item.id,
|
|
labelHtml: cleanUpHtml(item.labelHtml),
|
|
subLabelHtml: item.subLabelHtml ? cleanUpHtml(item.subLabelHtml) : undefined,
|
|
url: item.url,
|
|
isFavorite: false,
|
|
createdAt: Date.now(),
|
|
});
|
|
|
|
// `items` still hasn't been updated so we should use `>=`.
|
|
if (this.items().recent.length >= MAX_RECENT_HISTORY_SIZE) {
|
|
const {id} = this.items().recent.at(-1)!;
|
|
map.delete(id);
|
|
}
|
|
});
|
|
}
|
|
|
|
removeItem(item: SearchResultItem | HistoryItem): void {
|
|
this.updateHistory((map) => {
|
|
map.delete(item.id);
|
|
});
|
|
}
|
|
|
|
makeFavorite(item: SearchResultItem | HistoryItem): void {
|
|
this.updateHistory((map) => {
|
|
const updated = map.get(item.id);
|
|
if (updated) {
|
|
map.set(item.id, {
|
|
...updated,
|
|
isFavorite: true,
|
|
createdAt: Date.now(),
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
private loadHistory(): void {
|
|
let parsedData: HistoryItem[];
|
|
|
|
try {
|
|
const historyData = this.localStorage?.getItem(SEARCH_HISTORY_LS_KEY) as string | null;
|
|
parsedData = JSON.parse(historyData ?? '[]') as HistoryItem[];
|
|
} catch {
|
|
parsedData = [];
|
|
}
|
|
|
|
const history = new Map();
|
|
for (const item of parsedData) {
|
|
history.set(item.id, item);
|
|
}
|
|
this.history.set(history);
|
|
}
|
|
|
|
private updateHistory(updateFn: (map: Map<string, HistoryItem>) => void): void {
|
|
const history = new Map(this.history());
|
|
updateFn(history);
|
|
this.history.set(history);
|
|
|
|
try {
|
|
this.localStorage?.setItem(SEARCH_HISTORY_LS_KEY, JSON.stringify(this.allItems()));
|
|
} catch {}
|
|
}
|
|
}
|