Greasy Fork is available in English.

SpringerLink – Zugriffsfilter

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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();
    }

})();