Greasy Fork

AniList List Card Title Shortener

Trim overly long manga/anime titles on AniList entry cards

// ==UserScript==
// @name         AniList List Card Title Shortener
// @namespace    plennhar-anilist-list-card-title-shortener
// @version      1.0.2
// @description  Trim overly long manga/anime titles on AniList entry cards
// @author       Plennhar
// @match        https://anilist.co/*
// @run-at       document-idle
// @license      GPL-3.0-or-later
// @grant        none
// ==/UserScript==
// SPDX-FileCopyrightText: 2025 Plennhar
// SPDX-License-Identifier: GPL-3.0-or-later

(function () {
    'use strict';

    // Hook into React Router to enable shortening when navigating
    function wrap(type) {
        const orig = history[type];
        return function (...args) {
            const res = orig.apply(this, args);
            window.dispatchEvent(new Event('locationchange'));
            return res;
        };
    }
    history.pushState = wrap('pushState');
    history.replaceState = wrap('replaceState');
    window.addEventListener('popstate', () => {
        window.dispatchEvent(new Event('locationchange'));
    });

    window.addEventListener('locationchange', () => {
        setTimeout(processAll, 100);
    });

    const maxLen = 100;  // Max characters for a title if it doesn't want to be shortened
    const displayLen = 100;   // Max number of title characters to show

    // Shortening function
    function shortenTitle(a) {
        let txt = a.textContent.trim();
        if (txt.length > maxLen) {
            a.textContent = txt.slice(0, displayLen) + '…';
        }
    }

    // Scan the page for all entry-card title links
    function processAll() {
        document
            .querySelectorAll('.entry-card .title a[href*="/manga/"], .entry-card .title a[href*="/anime/"]')
            .forEach(shortenTitle);
    }

    // Run on initial load
    processAll();

    // Watch for DOM injections and shorten if new cards appear
    const obs = new MutationObserver(mutations => {
        for (const m of mutations) {
            for (const n of m.addedNodes) {
                if (!(n instanceof Element)) continue;
                if (n.matches('.entry-card') || n.querySelector('.entry-card')) {
                    processAll();
                    return;
                }
            }
        }
    });

    obs.observe(document.body, { childList: true, subtree: true });
})();