Greasy Fork is available in English.

TrueAchievements Live List Filter

Adds filter/search capability for achievement titles and descriptions.

// ==UserScript==
// @name         TrueAchievements Live List Filter
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  Adds filter/search capability for achievement titles and descriptions.
// @author       joequincy
// @match        https://www.trueachievements.com/game/*/achievements
// @grant        none
// @license      GPLv3
// ==/UserScript==


(function () {
  'use strict'
  
  const selectors = {
    inputInsertionPoint: 'h1.pagetitle+div+div.frm-row',
    sectionPanel: 'ul.ach-panels',
    achievementItem: 'li[id]',
    achievementTitle: 'a.title',
    achievementDescription: 'p'
  }

  RegExp.escape = function (s) {
    return s.replace(/[-\/\\$*+?.()|[\]{}]/g, '\\$&');
  }

  insertStyles()
  insertFilterInput()

  let sections = [...document.querySelectorAll(selectors.sectionPanel)].reduce((acc, section) => {
    const previousSibling = section.previousElementSibling
    if(previousSibling.classList.contains('pnl-hd')) {
      acc.push({
        header: previousSibling,
        achievements: []
      })
    }

    acc.at(-1).achievements.push(...section.querySelectorAll(selectors.achievementItem))

    return acc
  }, [{
    header: null,
    achievements: []
  }])

  function filterList(e) {
    var filterText = new RegExp(RegExp.escape(e.target.value), "i")
    for (let section of sections) {
      let shown = 0
      for (let achievement of section.achievements) {
        const title = achievement.querySelector(selectors.achievementTitle).textContent
        const description = achievement.querySelector(selectors.achievementDescription).textContent
        const matchesEither = filterText.test(title) || filterText.test(description)
        achievement.classList.toggle("tmhidden", !matchesEither)
        if (matchesEither) { shown++ }
      }

      // hide section header if no achievements are shown
      if (section.header) {
        section.header.classList.toggle("tmhidden", shown === 0)
      }
    }
  }

  function insertFilterInput() {
    // create a block for the input element, and an insertion point
    const insertPoint = document.querySelector(selectors.inputInsertionPoint)
    const insertBlock = document.createElement("div")

    insertBlock.classList.add("tmfilter")
    insertBlock.appendChild(document.createElement("span"))
    insertBlock.appendChild(document.createElement("input"))
    insertBlock.children[0].innerHTML = "Filter/Search achievement names: "

    insertPoint.parentElement.insertBefore(insertBlock, insertPoint.nextSibling)
    insertBlock.children[1].addEventListener("input", filterList)
  }

  function insertStyles() {
    // create and insert style element
    const styleElm = document.createElement("style")
    styleElm.title = 'tmfilter'
    document.head.appendChild(styleElm)

    // add specific styles for userscript use
    styleElm.sheet.insertRule("div.tmfilter{padding-bottom:15px}", 0)
    styleElm.sheet.insertRule("div.tmfilter span{padding-right:5px}", 0)
    styleElm.sheet.insertRule("div.tmfilter input{color:#bbb;background-color:#444;border-color:#555}", 0)
    styleElm.sheet.insertRule(".tmhidden{display:none!important;}", 0)
  }
})()