Better Scholar

按引用/年份排序,自动提取年份和出版商并打标签,左侧边栏添加出版商过滤功能。

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Better Scholar
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  按引用/年份排序,自动提取年份和出版商并打标签,左侧边栏添加出版商过滤功能。
// @author       Albert & Gemini
// @match        *://scholar.google.com/*
// @match        *://scholar.google.cz/*
// @match        *://scholar.google.co.jp/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let originalOrder = [];
    let isInitialized = false;
    let uniquePublishers = new Set();

    // --- 注入自定义 CSS (完全匹配 Google Scholar 原生风格) ---
    function injectStyles() {
        if (document.getElementById('gs-custom-styles')) return;
        const style = document.createElement('style');
        style.id = 'gs-custom-styles';
        style.textContent = `
            /* 标签样式:白底、浅灰框、深灰字、小圆角 */
            .gs-custom-tag {
                display: inline-block;
                color: #555;
                background-color: #fff;
                border: 1px solid #ccc;
                border-radius: 3px;
                padding: 1px 6px;
                font-size: 12px;
                margin-right: 8px;
                line-height: 1.4;
            }
            /* 顶部按钮样式:白底、谷歌蓝字、悬停变灰 */
            .gs-custom-btn {
                margin-right: 8px;
                padding: 4px 10px;
                cursor: pointer;
                background-color: #fff;
                color: #1a0dab; /* 经典的谷歌学术链接蓝 */
                border: 1px solid #ccc;
                border-radius: 3px;
                font-size: 13px;
                transition: background-color 0.2s, box-shadow 0.2s;
            }
            .gs-custom-btn:hover {
                background-color: #f8f9fa;
                border-color: #b5b5b5;
                box-shadow: 0 1px 2px rgba(0,0,0,0.05);
            }
            /* 侧边栏标题样式:匹配原生 #777 灰色和字号 */
            #custom-publisher-filter h3 {
                font-size: 13px;
                color: #777;
                margin-bottom: 8px;
                font-weight: normal;
                padding-left: 16px;
            }
            /* 侧边栏复选框列表样式 */
            .gs-custom-checkbox-label {
                display: flex;
                align-items: center;
                margin-bottom: 6px;
                font-size: 13px;
                color: #222;
                cursor: pointer;
            }
            .gs-custom-checkbox-label input {
                margin-right: 8px;
                cursor: pointer;
            }
        `;
        document.head.appendChild(style);
    }

    // --- 智能提取出版商 ---
    function extractPublisher(text) {
        const lowerText = text.toLowerCase();
        if (lowerText.includes('ieee')) return 'IEEE';
        if (lowerText.includes('arxiv')) return 'arXiv';
        if (lowerText.includes('acm')) return 'ACM';
        if (lowerText.includes('nature')) return 'Nature';
        if (lowerText.includes('science')) return 'Science';
        if (lowerText.includes('springer')) return 'Springer';
        if (lowerText.includes('elsevier') || lowerText.includes('sciencedirect')) return 'Elsevier';
        if (lowerText.includes('wiley')) return 'Wiley';
        if (lowerText.includes('acs ') || lowerText.includes('acs publications') || lowerText.includes('chemical reviews')) return 'ACS';
        if (lowerText.includes('iop ')) return 'IOP';

        const parts = text.split('-');
        if (parts.length > 1) {
            let lastPart = parts[parts.length - 1].trim();
            lastPart = lastPart.replace(/\.\.\./g, '').trim();
            lastPart = lastPart.replace(/\.com$/i, '').trim();
            lastPart = lastPart.replace(/\.org$/i, '').trim();
            lastPart = lastPart.replace(/\.net$/i, '').trim();
            if (lastPart.length > 0) return lastPart;
        }
        return 'Other';
    }

    // --- 步骤 1:解析文献并打标签 ---
    function processArticles() {
        const parent = document.getElementById('gs_res_ccl_mid');
        if (!parent || isInitialized) return false;

        const articles = Array.from(parent.querySelectorAll('.gs_r.gs_or.gs_scl'));
        if (articles.length === 0) return false;

        articles.forEach(article => {
            let year = "未知";
            let publisher = "未知";

            const infoNode = article.querySelector('.gs_a');
            if (infoNode) {
                const text = infoNode.textContent;
                const yearMatch = text.match(/\b(19|20)\d{2}\b/g);
                if (yearMatch) year = yearMatch[yearMatch.length - 1];
                publisher = extractPublisher(text);
            }

            article.dataset.year = year === "未知" ? "0" : year;
            article.dataset.publisher = publisher;

            uniquePublishers.add(publisher);

            const contentContainer = article.querySelector('.gs_ri');
            if (contentContainer && !article.querySelector('.custom-tags-container')) {
                const tagsDiv = document.createElement('div');
                tagsDiv.className = 'custom-tags-container';
                tagsDiv.style.cssText = 'margin-bottom: 4px;'; // 减小标签与标题的间距

                // 使用原生的纯文本灰白黑风格标签
                tagsDiv.innerHTML = `
                    <span class="gs-custom-tag">${year}</span>
                    <span class="gs-custom-tag">${publisher}</span>
                `;
                contentContainer.insertBefore(tagsDiv, contentContainer.firstChild);
            }
        });

        originalOrder = articles;
        isInitialized = true;
        return true;
    }

    // --- 步骤 2:创建左侧边栏过滤面板 ---
    function createSidebarFilter() {
        const sidebar = document.getElementById('gs_bdy_sb_in') || document.getElementById('gs_bdy_sb');
        if (!sidebar || document.getElementById('custom-publisher-filter')) return;

        const filterPanel = document.createElement('div');
        filterPanel.id = 'custom-publisher-filter';
        // 分割线采用原生的 #ebebeb
        filterPanel.style.cssText = 'margin-top: 20px; padding-top: 15px; border-top: 1px solid #ebebeb; font-family: Arial, sans-serif; padding-bottom: 15px;';

        const title = document.createElement('h3');
        title.innerText = '出版商';
        filterPanel.appendChild(title);

        const listContainer = document.createElement('div');
        listContainer.style.cssText = 'padding-left: 16px;';

        Array.from(uniquePublishers).sort().forEach(pub => {
            const label = document.createElement('label');
            label.className = 'gs-custom-checkbox-label';
            label.innerHTML = `<input type="checkbox" checked value="${pub}" class="pub-filter-cb"> ${pub}`;
            listContainer.appendChild(label);
        });

        filterPanel.appendChild(listContainer);
        sidebar.appendChild(filterPanel);

        const checkboxes = filterPanel.querySelectorAll('.pub-filter-cb');
        checkboxes.forEach(cb => {
            cb.addEventListener('change', updateFilterDisplay);
        });
    }

    // --- 过滤逻辑 ---
    function updateFilterDisplay() {
        const checkedPubs = Array.from(document.querySelectorAll('.pub-filter-cb:checked')).map(cb => cb.value);
        const parent = document.getElementById('gs_res_ccl_mid');
        if (!parent) return;

        const articles = parent.querySelectorAll('.gs_r.gs_or.gs_scl');
        articles.forEach(article => {
            article.style.display = checkedPubs.includes(article.dataset.publisher) ? '' : 'none';
        });
    }

    // --- 步骤 3:创建顶部排序按钮 ---
    function createSortingButtons() {
        const toolbar = document.querySelector('#gs_ab_md');
        if (!toolbar || document.querySelector('#custom-sort-container')) return;

        const container = document.createElement('div');
        container.id = 'custom-sort-container';
        container.style.cssText = 'display: inline-block; margin-left: 15px; vertical-align: middle;';

        const btnCite = document.createElement('button');
        btnCite.className = 'gs-custom-btn';
        btnCite.innerHTML = '按引用排序';
        btnCite.onclick = (e) => { e.preventDefault(); sortResults('citations'); };

        const btnYear = document.createElement('button');
        btnYear.className = 'gs-custom-btn';
        btnYear.innerHTML = '按年份排序';
        btnYear.onclick = (e) => { e.preventDefault(); sortResults('year'); };

        const btnDefault = document.createElement('button');
        btnDefault.className = 'gs-custom-btn';
        btnDefault.innerHTML = '默认排序';
        btnDefault.style.color = '#555'; // 默认排序按钮使用深灰色文字以示区分
        btnDefault.onclick = (e) => { e.preventDefault(); restoreDefault(); };

        container.append(btnCite, btnYear, btnDefault);
        toolbar.appendChild(container);
    }

    // --- 排序逻辑 ---
    function sortResults(type) {
        const parent = document.getElementById('gs_res_ccl_mid');
        if (!parent || originalOrder.length === 0) return;

        let articles = Array.from(parent.querySelectorAll('.gs_r.gs_or.gs_scl'));

        articles.sort((a, b) => {
            let valA = 0, valB = 0;
            if (type === 'citations') {
                const citeA = a.querySelector('a[href*="/scholar?cites="]');
                if (citeA) valA = parseInt((citeA.textContent.match(/\d+/) || [0])[0], 10);
                const citeB = b.querySelector('a[href*="/scholar?cites="]');
                if (citeB) valB = parseInt((citeB.textContent.match(/\d+/) || [0])[0], 10);
            } else if (type === 'year') {
                valA = parseInt(a.dataset.year, 10);
                valB = parseInt(b.dataset.year, 10);
            }
            return valB - valA;
        });

        articles.forEach(article => parent.appendChild(article));
    }

    function restoreDefault() {
        const parent = document.getElementById('gs_res_ccl_mid');
        if (parent && originalOrder.length > 0) {
            originalOrder.forEach(article => parent.appendChild(article));
        }
    }

    // --- 主初始化函数 ---
    function init() {
        injectStyles(); // 注入自定义原生样式
        if (processArticles()) {
            createSidebarFilter();
            createSortingButtons();
        } else {
            setTimeout(init, 200);
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();