SpringerLink – Zugriffsfilter

Blendet Suchergebnisse ohne "Vollen Zugriff" aus und zeigt ein Steuerfeld

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

Advertisement:

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

Advertisement:

// ==UserScript==
// @name         SpringerLink – Zugriffsfilter
// @namespace    https://link.springer.com/
// @version      1.3
// @description  Blendet Suchergebnisse ohne "Vollen Zugriff" aus und zeigt ein Steuerfeld
// @author       Nader Rupiani
// @match        https://link.springer.com/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // ── Styles ────────────────────────────────────────────────────────────────
    GM_addStyle(`
        #sl-access-panel {
            position: fixed;
            bottom: 24px;
            right: 24px;
            z-index: 99999;
            background: #fff;
            border: 1.5px solid #006EAF;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
            font-size: 13px;
            color: #1a1a1a;
            min-width: 220px;
            user-select: none;
            overflow: hidden;
        }
        #sl-panel-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 11px 14px;
            cursor: pointer;
            background: #f5f9fd;
            border-bottom: 1.5px solid #d0e4f0;
            gap: 8px;
        }
        #sl-panel-header:hover { background: #e8f1f9; }
        #sl-panel-title {
            font-size: 13px;
            font-weight: 700;
            color: #006EAF;
            letter-spacing: 0.3px;
            display: flex;
            align-items: center;
            gap: 6px;
            margin: 0;
        }
        #sl-panel-title::before { content: "🔒"; font-size: 14px; }
        #sl-collapse-icon {
            font-size: 11px;
            color: #006EAF;
            transition: transform 0.2s;
            flex-shrink: 0;
        }
        #sl-collapse-icon.collapsed { transform: rotate(-90deg); }
        #sl-panel-body {
            padding: 14px 18px;
            transition: max-height 0.25s ease, opacity 0.2s ease, padding 0.2s ease;
            max-height: 300px;
            opacity: 1;
            overflow: hidden;
        }
        #sl-panel-body.collapsed {
            max-height: 0;
            opacity: 0;
            padding-top: 0;
            padding-bottom: 0;
        }
        #sl-stats {
            margin-bottom: 12px;
            line-height: 1.6;
        }
        #sl-stats .stat-row {
            display: flex;
            justify-content: space-between;
            gap: 12px;
        }
        #sl-stats .stat-value {
            font-weight: 700;
        }
        #sl-stats .stat-value.full { color: #2e7d32; }
        #sl-stats .stat-value.no   { color: #c62828; }
        .sl-toggle-btn {
            width: 100%;
            padding: 7px 10px;
            border: none;
            border-radius: 7px;
            cursor: pointer;
            font-size: 12.5px;
            font-weight: 600;
            transition: background 0.2s, opacity 0.2s;
        }
        #sl-btn-hide {
            background: #006EAF;
            color: #fff;
            margin-bottom: 6px;
        }
        #sl-btn-hide:hover { background: #005a8e; }
        #sl-btn-dim {
            background: #f0f4f8;
            color: #444;
            margin-bottom: 6px;
        }
        #sl-btn-dim:hover  { background: #dce6ef; }
        #sl-btn-show {
            background: #f0f4f8;
            color: #444;
        }
        #sl-btn-show:hover { background: #dce6ef; }

        /* States applied to result cards */
        .sl-no-access--hidden {
            display: none !important;
        }
        .sl-no-access--dimmed {
            opacity: 0.25 !important;
            filter: grayscale(60%) !important;
            pointer-events: none;
        }
        .sl-no-access-badge {
            display: inline-block;
            background: #c62828;
            color: #fff;
            font-size: 10px;
            font-weight: 700;
            padding: 2px 6px;
            border-radius: 4px;
            margin-left: 6px;
            vertical-align: middle;
            letter-spacing: 0.5px;
        }
    `);

    // ── State ─────────────────────────────────────────────────────────────────
    let mode = 'badge'; // 'badge' | 'dim' | 'hide'

    // ── Panel ─────────────────────────────────────────────────────────────────
    function buildPanel() {
        if (document.getElementById('sl-access-panel')) return;

        const panel = document.createElement('div');
        panel.id = 'sl-access-panel';
        panel.innerHTML = `
            <div id="sl-panel-header">
                <h4 id="sl-panel-title">Zugriffsfilter</h4>
                <span id="sl-collapse-icon">▼</span>
            </div>
            <div id="sl-panel-body">
                <div id="sl-stats">
                    <div class="stat-row">
                        <span>✅ Voller Zugriff</span>
                        <span class="stat-value full" id="sl-count-full">–</span>
                    </div>
                    <div class="stat-row">
                        <span>🔒 Kein Zugriff</span>
                        <span class="stat-value no" id="sl-count-no">–</span>
                    </div>
                </div>
                <button class="sl-toggle-btn" id="sl-btn-hide">Ausblenden</button>
                <button class="sl-toggle-btn" id="sl-btn-dim">Abdunkeln</button>
                <button class="sl-toggle-btn" id="sl-btn-show">Alle einblenden</button>
            </div>
        `;
        document.body.appendChild(panel);

        // Collapse toggle
        let collapsed = false;
        document.getElementById('sl-panel-header').addEventListener('click', () => {
            collapsed = !collapsed;
            document.getElementById('sl-panel-body').classList.toggle('collapsed', collapsed);
            document.getElementById('sl-collapse-icon').classList.toggle('collapsed', collapsed);
        });

        document.getElementById('sl-btn-hide').addEventListener('click', () => setMode('hide'));
        document.getElementById('sl-btn-dim').addEventListener('click',  () => setMode('dim'));
        document.getElementById('sl-btn-show').addEventListener('click', () => setMode('badge'));
    }

    function setMode(newMode) {
        mode = newMode;
        applyMode();
    }

    // ── Core logic ────────────────────────────────────────────────────────────
    function scanAndTag() {
        const cards = document.querySelectorAll('li.app-card-open');
        let countFull = 0;
        let countNo   = 0;

        cards.forEach(card => {
            // Remove previous badges to avoid duplicates on re-scan
            card.querySelectorAll('.sl-no-access-badge').forEach(b => b.remove());
            card.classList.remove('sl-no-access', 'sl-no-access--hidden', 'sl-no-access--dimmed');

            const hasFullAccess = !!card.querySelector(
                '.app-entitlement__icon--full-access, [data-test="entitlements"]'
            ) && card.querySelector('[data-test="entitlements"]')?.textContent.trim().toLowerCase().includes('full access');

            if (hasFullAccess) {
                countFull++;
            } else {
                countNo++;
                card.classList.add('sl-no-access');

                // Add badge next to title
                const title = card.querySelector('.app-card-open__heading, h3');
                if (title && !title.querySelector('.sl-no-access-badge')) {
                    const badge = document.createElement('span');
                    badge.className = 'sl-no-access-badge';
                    badge.textContent = 'KEIN ZUGRIFF';
                    title.appendChild(badge);
                }
            }
        });

        // Update counters
        const elFull = document.getElementById('sl-count-full');
        const elNo   = document.getElementById('sl-count-no');
        if (elFull) elFull.textContent = countFull;
        if (elNo)   elNo.textContent   = countNo;

        applyMode();
    }

    function applyMode() {
        const noAccessCards = document.querySelectorAll('.sl-no-access');
        noAccessCards.forEach(card => {
            card.classList.remove('sl-no-access--hidden', 'sl-no-access--dimmed');
            if (mode === 'hide') {
                card.classList.add('sl-no-access--hidden');
            } else if (mode === 'dim') {
                card.classList.add('sl-no-access--dimmed');
            }
            // 'badge' mode: leave visible, badge already attached
        });

        // Update button active state
        ['sl-btn-hide', 'sl-btn-dim', 'sl-btn-show'].forEach(id => {
            const btn = document.getElementById(id);
            if (btn) btn.style.outline = '';
        });
        const activeMap = { hide: 'sl-btn-hide', dim: 'sl-btn-dim', badge: 'sl-btn-show' };
        const activeBtn = document.getElementById(activeMap[mode]);
        if (activeBtn) activeBtn.style.outline = '2px solid #006EAF';
    }

    // ── MutationObserver – handles pagination / dynamic content ───────────────
    let debounceTimer = null;
    const observer = new MutationObserver(() => {
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => {
            buildPanel();
            scanAndTag();
        }, 400);
    });

    function init() {
        buildPanel();
        scanAndTag();

        // Watch the search results container (or body as fallback)
        const target = document.querySelector('#results-list, [data-test="results"], main') || document.body;
        observer.observe(target, { childList: true, subtree: true });
    }

    // Wait for DOM ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();