diff --git a/badges.js b/badges.js index 63325c0c..5ccc12c9 100644 --- a/badges.js +++ b/badges.js @@ -15,6 +15,7 @@ @property {boolean} [unlocked] @property {boolean} hidden @property {boolean} [secret] + @property {boolean} [secretCondition] @property {number} originalOrder */ @@ -202,7 +203,7 @@ function initBadgeControls() { return -1; return 1; } - return a.originalOrder - b.originalOrder; + return sortOrderDesc ? b.originalOrder - a.originalOrder : a.originalOrder - b.originalOrder; }; // If we already have cache and it hasn't been invalidated, only update the modal. @@ -566,6 +567,14 @@ function initBadgeControls() { exactMatch = true; } + function isMatch(haystack, needle, exact) { + if (!haystack) return false; + if (exact) { + return haystack.localeCompare(needle, undefined, { sensitivity: 'base', usage: 'search' }) === 0; + } + return haystack.toLocaleLowerCase().indexOf(needle) > -1; + } + const gameVisibilities = {}; const gameGroupVisibilities = {}; @@ -579,9 +588,10 @@ function initBadgeControls() { visible &= item.el.classList.contains('locked') === !parseInt(unlockStatus); if (searchTerm.trim().length) { switch (searchMode) { - case 'name': + case 'name': { visible &= item.title && (exactMatch ? item.title === parsedSearchTerm : item.title.indexOf(parsedSearchTerm) > -1); break; + } case 'location': { const localizedLocation = gameLocalizedMapLocations[item.game]?.[item.mapId]; let title = determineTitle(localizedLocation, item.mapX, item.mapY); @@ -596,13 +606,22 @@ function initBadgeControls() { title = globalConfig.lang === 'ja' ? desc.titleJP : desc.title; } } - visible &= title && (exactMatch ? title.toLocaleLowerCase() === parsedSearchTerm : title.toLocaleLowerCase().indexOf(parsedSearchTerm) > -1); + visible &= isMatch(title, parsedSearchTerm, exactMatch); break; } case 'artist': { visible &= item.art && (exactMatch ? item.art.split(/ & |, /).includes(parsedSearchTerm) : item.art.indexOf(parsedSearchTerm) > -1); break; } + case 'contents': { + const gameBadgeLocalizations = localizedBadges && localizedBadges[item.game]; + const badgeLocalization = gameBadgeLocalizations && gameBadgeLocalizations[item.badgeId]; + visible &= badgeLocalization && ( + (item.unlocked || !item.secret) && isMatch(badgeLocalization.description, parsedSearchTerm, exactMatch) + || (item.unlocked || !item.secretCondition) && isMatch(badgeLocalization.condition, parsedSearchTerm, exactMatch) + ); + break; + } } } if (!(item.game in gameVisibilities)) { @@ -638,29 +657,30 @@ function initBadgeControls() { return; } + let parsedValue = this.value; + let modifier = null; + if (parsedValue.startsWith('=')) { + parsedValue = parsedValue.slice(1); + modifier = 'exactMatch'; + } + let value = parsedValue; + if (modifier) + value += localizedMessages.badges.search.modifier.template.replace('{MODIFIER}', localizedMessages.badges.search.modifier[modifier]); + + // mappings for user input sanitization + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + "/": '/', + }; + const reg = /[&<>"'/]/ig; + value = value.replace(reg, match => map[match]); + for (const span of badgeDropdown.querySelectorAll('.dropdownItem span')) { - let parsedValue = this.value; - let modifier = null; - if (parsedValue.startsWith('=')) { - parsedValue = parsedValue.slice(1); - modifier = 'exactMatch'; - } - let value = parsedValue; - if (modifier) - value += localizedMessages.badges.search.modifier.template.replace('{MODIFIER}', localizedMessages.badges.search.modifier[modifier]); - - // mappings for user input sanitization - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - "/": '/', - }; - - const reg = /[&<>"'/]/ig; - span.innerHTML = value.replace(reg, (match)=>(map[match])); + span.innerHTML = value; } }; @@ -703,6 +723,9 @@ function initBadgeControls() { const searchArtist = document.getElementById('searchArtist').parentElement; onDropdownActivate(searchArtist, () => searchBadges('artist')); + const searchContents = document.getElementById('searchContents').parentElement; + onDropdownActivate(searchContents, () => searchBadges('contents')); + function searchBadges(mode) { badgeDropdown.classList.add('hidden'); updateBadgeVisibility(mode); @@ -1083,6 +1106,10 @@ function getBadgeItem(badge, includeTooltip, emptyIcon, lockedIcon, scaled, filt percent: badge.percent, badgeId: badge.badgeId, art: '', + originalOrder: badge.originalOrder, + unlocked: badge.unlocked, + secret: badge.secret, + secretCondition: badge.secretCondition, }; item.dataset.cacheIndex = badgeFilterCache.push(filterItem) - 1; } diff --git a/index.php b/index.php index fce75fcd..022feb73 100644 --- a/index.php +++ b/index.php @@ -1080,6 +1080,7 @@ class="joystickBase" />
@@ -1101,6 +1102,10 @@ class="joystickBase" /> + diff --git a/lang/ar.json b/lang/ar.json index 11d1e8b7..f577b59a 100644 --- a/lang/ar.json +++ b/lang/ar.json @@ -403,7 +403,8 @@ "label": "البحث: ", "name": "Name:", "location": "Location:", - "artist": "Artist:" + "artist": "Artist:", + "contents": "Text:" } } }, diff --git a/lang/de.json b/lang/de.json index 0e8c88a3..b369a831 100644 --- a/lang/de.json +++ b/lang/de.json @@ -403,7 +403,8 @@ "label": "Suchen nach: ", "name": "Name:", "location": "Ort:", - "artist": "Artist:" + "artist": "Artist:", + "contents": "Text:" } } }, diff --git a/lang/en.json b/lang/en.json index 87e28083..1bb740ce 100644 --- a/lang/en.json +++ b/lang/en.json @@ -403,7 +403,8 @@ "label": "Search: ", "name": "Name:", "location": "Location:", - "artist": "Artist:" + "artist": "Artist:", + "contents": "Text:" } } }, diff --git a/lang/eo.json b/lang/eo.json index bdd021dd..cef5f2e9 100644 --- a/lang/eo.json +++ b/lang/eo.json @@ -403,7 +403,8 @@ "label": "Serĉi: ", "name": "Nomo:", "location": "Loko:", - "artist": "Artist:" + "artist": "Artist:", + "contents": "Text:" } } }, diff --git a/lang/es.json b/lang/es.json index 0716401d..f3468d27 100644 --- a/lang/es.json +++ b/lang/es.json @@ -403,7 +403,8 @@ "label": "Buscar: ", "name": "Nombre:", "location": "Ubicación:", - "artist": "Artista:" + "artist": "Artista:", + "contents": "Text:" } } }, diff --git a/lang/fr.json b/lang/fr.json index 1398cf79..ccc8c425 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -403,7 +403,8 @@ "label": "Rechercher : ", "name": "Nom :", "location": "Lieu :", - "artist": "Artiste :" + "artist": "Artiste :", + "contents": "Text:" } } }, diff --git a/lang/id.json b/lang/id.json index cf4d5de8..6e44e8e7 100644 --- a/lang/id.json +++ b/lang/id.json @@ -403,7 +403,8 @@ "label": "Search: ", "name": "Name:", "location": "Location:", - "artist": "Artist:" + "artist": "Artist:", + "contents": "Text:" } } }, diff --git a/lang/it.json b/lang/it.json index f87bafcb..6352fc9b 100644 --- a/lang/it.json +++ b/lang/it.json @@ -403,7 +403,8 @@ "label": "Cerca : ", "name": "Nome :", "location": "Luogo :", - "artist": "Artist:" + "artist": "Artist:", + "contents": "Text:" } } }, diff --git a/lang/ja.json b/lang/ja.json index 9c67b9cd..ca6307e3 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -403,7 +403,8 @@ "label": "検索:", "name": "名:", "location": "場所:", - "artist": "作者:" + "artist": "作者:", + "contents": "Text:" } } }, diff --git a/lang/ko.json b/lang/ko.json index a38690e1..56717b5f 100644 --- a/lang/ko.json +++ b/lang/ko.json @@ -403,7 +403,8 @@ "label": "검색: ", "name": "이름:", "location": "장소:", - "artist": "배지 디자이너:" + "artist": "배지 디자이너:", + "contents": "Text:" } } }, diff --git a/lang/pl.json b/lang/pl.json index f40d596a..5c8671e2 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -403,7 +403,8 @@ "label": "Szukaj: ", "name": "Nazwa:", "location": "Lokalizacja:", - "artist": "Artist:" + "artist": "Artist:", + "contents": "Text:" } } }, diff --git a/lang/pt.json b/lang/pt.json index 28ae509a..1b9eeb7f 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -403,7 +403,8 @@ "label": "Buscar: ", "name": "Nome:", "location": "Localização:", - "artist": "Artista:" + "artist": "Artista:", + "contents": "Text:" } } }, diff --git a/lang/ro.json b/lang/ro.json index 5fde7651..ed875da1 100644 --- a/lang/ro.json +++ b/lang/ro.json @@ -403,7 +403,8 @@ "label": "Caută: ", "name": "Nume:", "location": "Locație:", - "artist": "Artist:" + "artist": "Artist:", + "contents": "Text:" } } }, diff --git a/lang/ru.json b/lang/ru.json index 5025dae1..a4fcad22 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -403,7 +403,8 @@ "label": "Поиск: ", "name": "Название:", "location": "Локация:", - "artist": "Художник:" + "artist": "Художник:", + "contents": "Text:" } } }, diff --git a/lang/tr.json b/lang/tr.json index 05ffb20c..7bfe9bc1 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -403,7 +403,8 @@ "label": "Ara: ", "name": "İsim:", "location": "Konum:", - "artist": "Artist:" + "artist": "Artist:", + "contents": "Text:" } } }, diff --git a/lang/uk.json b/lang/uk.json index aa977a96..349dbe7d 100644 --- a/lang/uk.json +++ b/lang/uk.json @@ -403,7 +403,8 @@ "label": "Пошук: ", "name": "Назва:", "location": "Місце:", - "artist": "Artist:" + "artist": "Artist:", + "contents": "Text:" } } }, diff --git a/lang/vi.json b/lang/vi.json index 1c8984c1..81c5919a 100644 --- a/lang/vi.json +++ b/lang/vi.json @@ -403,7 +403,8 @@ "label": "Tìm kiếm: ", "name": "Tên:", "location": "Địa điểm:", - "artist": "Họa sĩ:" + "artist": "Họa sĩ:", + "contents": "Nội dung:" } } }, diff --git a/lang/zh.json b/lang/zh.json index cc377d1d..667a0383 100644 --- a/lang/zh.json +++ b/lang/zh.json @@ -403,7 +403,8 @@ "label": "搜索: ", "name": "名称:", "location": "取得位置:", - "artist": "作者:" + "artist": "作者:", + "contents": "Text:" } } },