mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-01 05:33:49 +08:00
Searching is hard
This commit is contained in:
@@ -110,6 +110,14 @@ function refresh() {
|
||||
return fetchRoutes()
|
||||
}
|
||||
|
||||
function changeSortOrder(event) {
|
||||
const sortOrder = String(event.target.value || "")
|
||||
if (!["newest", "oldest", "longest", "shortest"].includes(sortOrder)) return
|
||||
state.sortOrder = sortOrder
|
||||
// Replacing the array forces the keyed route templates to move immediately.
|
||||
state.routes = [...state.routes]
|
||||
}
|
||||
|
||||
if (!isGalaxyTunnel()) refresh()
|
||||
|
||||
function openDialog(htmlString) {
|
||||
@@ -825,7 +833,7 @@ export function RouteRecordings() {
|
||||
</div>
|
||||
<label class="dashcam-sort">
|
||||
<span>Sort</span>
|
||||
<select value="${() => state.sortOrder}" @change="${event => { state.sortOrder = event.target.value }}">
|
||||
<select value="${() => state.sortOrder}" @input="${changeSortOrder}" @change="${changeSortOrder}">
|
||||
<option value="newest">Newest first</option>
|
||||
<option value="oldest">Oldest first</option>
|
||||
<option value="longest">Longest duration</option>
|
||||
|
||||
+34
-12
@@ -1,4 +1,5 @@
|
||||
export const MAX_RENDERED_ROUTES = 250
|
||||
const SEARCH_MONTHS = ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"]
|
||||
|
||||
function validDate(value) {
|
||||
if (!value) return null
|
||||
@@ -55,12 +56,12 @@ export function normalizeRoute(route, locale) {
|
||||
displayDate,
|
||||
displayName,
|
||||
_startedAtMs: startedAtDate?.getTime() ?? timestampDate?.getTime() ?? null,
|
||||
_searchValues: [
|
||||
normalizeRouteSearchText(route?.name),
|
||||
normalizeRouteSearchText(isCustomName ? displayName : ""),
|
||||
...dateAliases.map(normalizeRouteSearchText),
|
||||
...timeAliases.map(normalizeRouteSearchText),
|
||||
].filter(Boolean),
|
||||
_searchIndex: {
|
||||
ids: [normalizeRouteSearchText(route?.name)].filter(Boolean),
|
||||
titles: [normalizeRouteSearchText(isCustomName ? displayName : "")].filter(Boolean),
|
||||
dates: dateAliases.map(normalizeRouteSearchText).filter(Boolean),
|
||||
times: timeAliases.map(normalizeRouteSearchText).filter(Boolean),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,16 +127,37 @@ export function sortRoutes(routes, sortOrder = "newest") {
|
||||
}
|
||||
|
||||
export function routeMatchesSearch(route, searchQuery) {
|
||||
const rawQuery = String(searchQuery || "").trim()
|
||||
const normalizedQuery = normalizeRouteSearchText(searchQuery)
|
||||
const queryTokens = normalizedQuery.split(" ").filter(Boolean)
|
||||
if (!queryTokens.length) return true
|
||||
|
||||
const searchValues = Array.isArray(route?._searchValues) ? route._searchValues : [
|
||||
route?.name,
|
||||
route?.timestamp,
|
||||
route?.displayName,
|
||||
route?.displayDate,
|
||||
].map(normalizeRouteSearchText).filter(Boolean)
|
||||
const fallbackIndex = {
|
||||
ids: [route?.name],
|
||||
titles: [route?.timestamp, route?.displayName],
|
||||
dates: [route?.displayDate],
|
||||
times: [route?.displayDate],
|
||||
}
|
||||
const searchIndex = route?._searchIndex || Object.fromEntries(
|
||||
Object.entries(fallbackIndex).map(([key, values]) => [key, values.map(normalizeRouteSearchText).filter(Boolean)]),
|
||||
)
|
||||
|
||||
const hasMonth = queryTokens.some(token => token.length >= 3 && SEARCH_MONTHS.some(month => month.startsWith(token)))
|
||||
const isDateQuery = hasMonth || /\d\s*[/-]\s*\d/.test(rawQuery) || /\d+(?:st|nd|rd|th)\b/i.test(rawQuery) || /^\d{4}$/.test(normalizedQuery)
|
||||
const isTimeQuery = /^\d{1,2}$/.test(normalizedQuery) || /\d\s*:\s*\d/.test(rawQuery) || queryTokens.some(token => token === "am" || token === "pm")
|
||||
const compactQuery = normalizedQuery.replaceAll(" ", "")
|
||||
const isHexQuery = /^[0-9a-f]+$/.test(compactQuery)
|
||||
const isIdQuery = rawQuery.includes("--") || (isHexQuery && (
|
||||
compactQuery.length >= 8 || (compactQuery.length >= 4 && /\d/.test(compactQuery) && /[a-f]/.test(compactQuery))
|
||||
))
|
||||
const valuesFor = key => Array.isArray(searchIndex[key]) ? searchIndex[key] : []
|
||||
const searchValues = [
|
||||
...valuesFor("titles"),
|
||||
...(isDateQuery ? valuesFor("dates") : []),
|
||||
...(isTimeQuery ? valuesFor("times") : []),
|
||||
...(isIdQuery ? valuesFor("ids") : []),
|
||||
]
|
||||
|
||||
return searchValues.some(value => {
|
||||
if (value.includes(normalizedQuery)) return true
|
||||
const searchTokens = value.split(" ").filter(Boolean)
|
||||
|
||||
@@ -82,7 +82,8 @@ def test_route_titles_and_custom_name_badges_are_reactive():
|
||||
def test_sort_order_select_and_route_items_are_keyed_and_reactive():
|
||||
source = COMPONENT_PATH.read_text(encoding="utf-8")
|
||||
|
||||
assert '<select value="${() => state.sortOrder}" @change="${event => { state.sortOrder = event.target.value }}">' in source
|
||||
assert '<select value="${() => state.sortOrder}" @input="${changeSortOrder}" @change="${changeSortOrder}">' in source
|
||||
assert "state.routes = [...state.routes]" in source
|
||||
assert ".key(group.key)" in source
|
||||
assert ".key(route.name)" in source
|
||||
|
||||
@@ -189,6 +190,27 @@ def test_search_indexes_the_displayed_time_for_every_route():
|
||||
]
|
||||
|
||||
|
||||
def test_short_numeric_search_does_not_match_hidden_ids_or_unrelated_dates():
|
||||
result = evaluate('''
|
||||
const routes = [
|
||||
route("00000012--9f0a7bdf9c", "2026-08-27T13:05:00Z", { timestamp: "Morning drive", isCustomName: true }),
|
||||
route("0000006b--9f0a7bdf9d", "2026-12-12T13:05:00Z", { timestamp: "Afternoon drive", isCustomName: true }),
|
||||
route("0000006c--9f0a7bdf9e", "2026-08-27T13:05:00Z", { timestamp: "Test_12", isCustomName: true }),
|
||||
]
|
||||
return {
|
||||
plainNumber: buildRouteView(routes, { searchQuery: "12" }).matching.map(item => item.name),
|
||||
ordinalDate: buildRouteView(routes, { searchQuery: "dec 12th" }).matching.map(item => item.name),
|
||||
explicitId: buildRouteView(routes, { searchQuery: "00000012" }).matching.map(item => item.name),
|
||||
}
|
||||
''')
|
||||
|
||||
assert result == {
|
||||
"plainNumber": ["0000006c--9f0a7bdf9e"],
|
||||
"ordinalDate": ["0000006b--9f0a7bdf9d"],
|
||||
"explicitId": ["00000012--9f0a7bdf9c"],
|
||||
}
|
||||
|
||||
|
||||
def test_filters_preserved_routes_before_applying_the_render_limit():
|
||||
view = evaluate('''
|
||||
const routes = Array.from({ length: MAX_RENDERED_ROUTES + 25 }, (_, index) => route(
|
||||
|
||||
Reference in New Issue
Block a user