// ==UserScript==
// @name Google Scholar Enhancer
// @namespace https://greasyfork.org/users/YourUserName
// @version 0.8
// @description Enhance Google Scholar with column layout options, auto-paging, and advanced search features
// @author knox
// @license MIT
// @match *://scholar.google.com/*
// @match *://scholar.google.com.au/*
// @match *://scholar.google.co.uk/*
// @match *://scholar.google.ca/*
// @match *://scholar.google.com.hk/*
// @match *://scholar.google.co.in/*
// @match *://scholar.google.co.jp/*
// @match *://scholar.google.de/*
// @match *://scholar.google.fr/*
// @match *://scholar.google.es/*
// @match *://scholar.google.it/*
// @match *://scholar.google.nl/*
// @match *://scholar.google.com.sg/*
// @icon https://www.google.com/s2/favicons?domain=scholar.google.com
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// ==/UserScript==
(function() {
'use strict';
const config = {
columnLayout: GM_getValue('columnLayout', 1),
autoPagingEnabled: GM_getValue('autoPagingEnabled', true),
bibtexCopyEnabled: GM_getValue('bibtexCopyEnabled', true),
bibtexCopyAlert: GM_getValue('bibtexCopyAlert', true),
singleResultRedirect: GM_getValue('singleResultRedirect', true),
showFrequentScholars: GM_getValue('showFrequentScholars', true),
language: GM_getValue('language', 'en'),
searchPresets: GM_getValue('searchPresets', []),
showPublicationYearDistribution: GM_getValue('showPublicationYearDistribution', true),
};
const translations = {
en: {
settings: '⚙️ Settings',
keywords: 'Keywords:',
exactPhrase: 'Exact phrase:',
without: 'Without:',
author: 'Author:',
publishedIn: 'Published in:',
titleOnly: 'Title only',
apply: 'Apply',
settingsTitle: 'Google Scholar Enhancer Settings',
layoutOptions: 'Layout Options',
columnLayout: 'Column Layout:',
singleColumn: 'Single Column',
twoColumns: 'Two Columns',
threeColumns: 'Three Columns',
navigationOptions: 'Navigation Options',
enableAutoPaging: 'Enable Automatic Page Turning',
autoRedirect: 'Auto-redirect for Single Results',
bibtexOptions: 'BibTeX Options',
enableDirectBibtex: 'Enable Direct BibTeX Copying',
showBibtexAlert: 'Show Alert on BibTeX Copy',
additionalFeatures: 'Additional Features',
showFrequentScholars: 'Show Frequent Scholars',
language: 'Language',
save: 'Save',
close: 'Close',
allOfTheWords: 'All of the words',
withoutWords: 'Without words',
journalOrConference: 'Journal or conference',
savePreset: 'Save Preset',
selectPreset: 'Select Preset',
enterPresetName: 'Enter a name for this preset:',
selectPublishedIn: 'Select venue',
conferences: 'Conferences',
journals: 'Journals',
selectDefault: 'Select default',
addNew: 'Add new...',
enterNewValue: 'Enter a new value:',
publicationYearDistribution: 'Publication Year Distribution',
showPublicationYearDistribution: 'Show Publication Year Distribution',
},
zh: {
settings: '⚙️ 设置',
keywords: '关键词:',
exactPhrase: '精确短语:',
without: '不包含:',
author: '者:',
publishedIn: '发表于:',
titleOnly: '仅标题',
apply: '应用',
settingsTitle: 'Google Scholar 增强设置',
layoutOptions: '布局选项',
columnLayout: '列局:',
singleColumn: '单列',
twoColumns: '双列',
threeColumns: '三列',
navigationOptions: '导航选项',
enableAutoPaging: '启用自动翻页',
autoRedirect: '单一结果自动重定向',
bibtexOptions: 'BibTeX 选项',
enableDirectBibtex: '启用直接 BibTeX 复制',
showBibtexAlert: '显示 BibTeX 复制提醒',
additionalFeatures: '附加功能',
showFrequentScholars: '显示常见学者',
language: '语言',
save: '保',
close: '关闭',
allOfTheWords: '包含所有这些词',
withoutWords: '不包含这些词',
journalOrConference: '期刊或会议',
savePreset: '保存预',
selectPreset: '选择预设',
enterPresetName: '输入此预设的名称:',
selectPublishedIn: '选择表场所',
conferences: '会议',
journals: '期刊',
selectDefault: '选择默认值',
addNew: '添加新值...',
enterNewValue: '输入新值:',
publicationYearDistribution: '发表年份分布',
showPublicationYearDistribution: '显示发表年份分布',
}
};
const styles = {
singleColumn: `
#gs_res_ccl_mid {
display: block;
max-width: 100%;
margin: 0 auto;
}
`,
doubleColumn: `
#gs_res_ccl_mid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 20px;
max-width: 90vw;
margin: 0 auto;
}
.gs_r.gs_or.gs_scl {
width: 100%;
}
`,
tripleColumn: `
#gs_res_ccl_mid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 20px;
max-width: 95vw;
margin: 0 auto;
}
.gs_r.gs_or.gs_scl {
width: 100%;
}
`,
common: `
.gs_r.gs_or.gs_scl {
border: 1px solid #ddd;
padding: 10px;
border-radius: 5px;
margin-bottom: 10px;
box-sizing: border-box;
}
#gs_top {
max-width: 95vw;
margin: 0 auto;
}
#gs_ab_md {
max-width: none !important;
}
`
};
// Add these constants for default options
const DEFAULT_CONFERENCES = [
'CVPR', 'ICCV', 'ECCV', // Computer Vision
'NeurIPS', 'ICML', 'ICLR', // Machine Learning
'ACL', 'EMNLP', 'NAACL', // Natural Language Processing
'SIGIR', 'WWW', 'KDD', // Information Retrieval and Data Mining
'SIGGRAPH', 'SIGGRAPH Asia', // Computer Graphics
];
const DEFAULT_JOURNALS = [
'Nature', 'Science', 'PNAS',
'IEEE TPAMI', 'IJCV', // Computer Vision
'JMLR', 'IEEE TNNLS', // Machine Learning
'ACM Computing Surveys', 'CACM',
'IEEE/ACM Transactions on Networking',
];
function addStyles() {
GM_addStyle(styles.common);
switch (config.columnLayout) {
case 1:
GM_addStyle(styles.singleColumn);
break;
case 2:
GM_addStyle(styles.doubleColumn);
break;
case 3:
GM_addStyle(styles.tripleColumn);
break;
}
}
function createLayoutSwitcher() {
const switcher = document.createElement('select');
switcher.innerHTML = `
<option value="1" ${config.columnLayout === 1 ? 'selected' : ''}>Single Column</option>
<option value="2" ${config.columnLayout === 2 ? 'selected' : ''}>Two Columns</option>
<option value="3" ${config.columnLayout === 3 ? 'selected' : ''}>Three Columns</option>
`;
switcher.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 9999;';
switcher.addEventListener('change', (e) => {
config.columnLayout = parseInt(e.target.value);
GM_setValue('columnLayout', config.columnLayout);
addStyles();
});
document.body.appendChild(switcher);
}
function createSettingsButton() {
const lang = translations[config.language];
const container = document.createElement('div');
container.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 5px 10px;
background-color: #f1f3f4;
border-bottom: 1px solid #dadce0;
z-index: 1000;
font-size: 12px;
`;
container.appendChild(createSettingsButtonElement(lang));
const advancedSearchFields = [
{ label: lang.keywords, id: 'all-words', width: '150px', placeholder: lang.allOfTheWords },
{ label: lang.exactPhrase, id: 'exact-phrase', width: '150px' },
{ label: lang.without, id: 'without-words', width: '150px', placeholder: lang.withoutWords },
{ label: lang.author, id: 'author', width: '150px' },
{ label: lang.publishedIn, id: 'publication', width: '150px', placeholder: lang.journalOrConference }
];
advancedSearchFields.forEach((field, index) => {
const fieldContainer = createFieldContainer(field, lang, index);
container.appendChild(fieldContainer);
});
container.appendChild(createApplyButton(lang));
const body = document.body;
body.insertBefore(container, body.firstChild);
body.style.paddingTop = `${container.offsetHeight}px`;
}
function createSettingsButtonElement(lang) {
const settingsButton = document.createElement('button');
settingsButton.id = 'settings-button';
settingsButton.textContent = lang.settings;
settingsButton.style.cssText = `
padding: 5px 10px;
margin-right: 10px;
background-color: #fff;
border: 1px solid #dadce0;
border-radius: 4px;
color: #202124;
font-family: arial,sans-serif;
font-size: 14px;
cursor: pointer;
`;
settingsButton.addEventListener('click', openSettingsModal);
return settingsButton;
}
function createFieldContainer(field, lang, index) {
const fieldContainer = document.createElement('div');
fieldContainer.style.cssText = `
margin-left: 10px;
display: inline-flex;
align-items: center;
position: relative;
`;
const label = document.createElement('label');
label.textContent = field.label;
label.style.cssText = `
margin-right: 5px;
font-size: 12px;
color: #666;
`;
const input = document.createElement('input');
input.type = 'text';
input.id = field.id;
input.style.cssText = `
width: 120px; // Increased from 100px to 120px
padding: 2px 5px;
font-size: 12px;
border: 1px solid #ddd;
border-radius: 3px;
`;
if (field.placeholder) {
input.placeholder = field.placeholder;
}
const select = createSelectForField(field.id, lang);
select.style.cssText = `
position: absolute;
right: 0;
top: 100%;
width: 100%;
font-size: 12px;
z-index: 1000;
display: none;
`;
input.addEventListener('focus', () => {
select.style.display = 'block';
});
document.addEventListener('click', (e) => {
if (!fieldContainer.contains(e.target)) {
select.style.display = 'none';
}
});
fieldContainer.appendChild(label);
fieldContainer.appendChild(input);
fieldContainer.appendChild(select);
if (index === 0) {
const titleOnlyCheckbox = createTitleOnlyCheckbox(lang);
titleOnlyCheckbox.style.marginLeft = '5px';
fieldContainer.appendChild(titleOnlyCheckbox);
}
return fieldContainer;
}
function createSelectForField(fieldId, lang) {
const select = document.createElement('select');
select.innerHTML = `<option value="">${lang.selectDefault}</option>`;
const storedValues = GM_getValue(`${fieldId}_defaults`, []);
storedValues.forEach(value => {
select.innerHTML += `<option value="${value}">${value}</option>`;
});
select.innerHTML += `<option value="add_new">${lang.addNew}</option>`;
select.addEventListener('change', (e) => {
const input = document.getElementById(fieldId);
if (e.target.value === 'add_new') {
const newValue = prompt(lang.enterNewValue);
if (newValue) {
input.value = newValue;
storedValues.push(newValue);
GM_setValue(`${fieldId}_defaults`, storedValues);
const newOption = new Option(newValue, newValue);
e.target.insertBefore(newOption, e.target.lastChild);
e.target.value = newValue;
} else {
e.target.value = '';
}
} else {
input.value = e.target.value;
}
select.style.display = 'none';
});
return select;
}
function createTitleOnlyCheckbox(lang) {
const titleOnlyLabel = document.createElement('label');
titleOnlyLabel.style.cssText = `
display: flex;
align-items: center;
font-size: 12px;
color: #666;
`;
const titleOnlyCheckbox = document.createElement('input');
titleOnlyCheckbox.type = 'checkbox';
titleOnlyCheckbox.id = 'title-only';
titleOnlyCheckbox.style.marginRight = '3px';
titleOnlyLabel.appendChild(titleOnlyCheckbox);
titleOnlyLabel.appendChild(document.createTextNode(lang.titleOnly));
return titleOnlyLabel;
}
function createApplyButton(lang) {
const applyButton = document.createElement('button');
applyButton.id = 'apply-button';
applyButton.textContent = lang.apply;
applyButton.style.cssText = `
padding: 5px 10px;
margin-left: 10px;
background-color: #4285f4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
`;
applyButton.addEventListener('click', applyAdvancedSearch);
return applyButton;
}
function createSettingsModal() {
const lang = translations[config.language];
const modalOverlay = document.createElement('div');
modalOverlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
`;
const modal = document.createElement('div');
modal.style.cssText = `
background-color: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1), 0 1px 3px rgba(0,0,0,0.08);
max-width: 600px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
`;
modal.innerHTML = `
<h2 style="margin-top: 0; color: #1a73e8; font-size: 24px; margin-bottom: 20px;">${lang.settingsTitle}</h2>
<div style="display: grid; gap: 20px;">
<div style="border: 1px solid #e0e0e0; padding: 15px; border-radius: 4px;">
<h3 style="margin-top: 0; margin-bottom: 10px; font-size: 18px;">${lang.layoutOptions}</h3>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold;">${lang.columnLayout}</label>
<select id="columnLayout" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
<option value="1" ${config.columnLayout === 1 ? 'selected' : ''}>${lang.singleColumn}</option>
<option value="2" ${config.columnLayout === 2 ? 'selected' : ''}>${lang.twoColumns}</option>
<option value="3" ${config.columnLayout === 3 ? 'selected' : ''}>${lang.threeColumns}</option>
</select>
</div>
</div>
<div style="border: 1px solid #e0e0e0; padding: 15px; border-radius: 4px;">
<h3 style="margin-top: 0; margin-bottom: 10px; font-size: 18px;">${lang.navigationOptions}</h3>
<div>
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="autoPaging" ${config.autoPagingEnabled ? 'checked' : ''} style="margin-right: 10px;">
<span>${lang.enableAutoPaging}</span>
</label>
</div>
<div style="margin-top: 10px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="singleResultRedirect" ${config.singleResultRedirect ? 'checked' : ''} style="margin-right: 10px;">
<span>${lang.autoRedirect}</span>
</label>
</div>
</div>
<div style="border: 1px solid #e0e0e0; padding: 15px; border-radius: 4px;">
<h3 style="margin-top: 0; margin-bottom: 10px; font-size: 18px;">${lang.bibtexOptions}</h3>
<div>
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="bibtexCopy" ${config.bibtexCopyEnabled ? 'checked' : ''} style="margin-right: 10px;">
<span>${lang.enableDirectBibtex}</span>
</label>
</div>
<div style="margin-top: 10px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="bibtexCopyAlert" ${config.bibtexCopyAlert ? 'checked' : ''} style="margin-right: 10px;">
<span>${lang.showBibtexAlert}</span>
</label>
</div>
</div>
<div style="border: 1px solid #e0e0e0; padding: 15px; border-radius: 4px;">
<h3 style="margin-top: 0; margin-bottom: 10px; font-size: 18px;">${lang.additionalFeatures}</h3>
<div>
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="showFrequentScholars" ${config.showFrequentScholars ? 'checked' : ''} style="margin-right: 10px;">
<span>${lang.showFrequentScholars}</span>
</label>
</div>
<div style="margin-top: 10px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="showPublicationYearDistribution" ${config.showPublicationYearDistribution ? 'checked' : ''} style="margin-right: 10px;">
<span>${lang.showPublicationYearDistribution}</span>
</label>
</div>
</div>
<div style="border: 1px solid #e0e0e0; padding: 15px; border-radius: 4px;">
<h3 style="margin-top: 0; margin-bottom: 10px; font-size: 18px;">${lang.language}</h3>
<div>
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="radio" name="language" value="en" ${config.language === 'en' ? 'checked' : ''} style="margin-right: 10px;">
<span>English</span>
</label>
</div>
<div style="margin-top: 10px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="radio" name="language" value="zh" ${config.language === 'zh' ? 'checked' : ''} style="margin-right: 10px;">
<span>中文</span>
</label>
</div>
</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 20px;">
<button id="saveSettings" style="background-color: #1a73e8; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; margin-right: 10px;">${lang.save}</button>
<button id="closeSettings" style="background-color: #f1f3f4; color: #202124; border: 1px solid #dadce0; padding: 10px 20px; border-radius: 4px; cursor: pointer;">${lang.close}</button>
</div>
`;
modalOverlay.appendChild(modal);
return modalOverlay;
}
function openSettingsModal() {
const modalOverlay = createSettingsModal();
document.body.appendChild(modalOverlay);
const closeModal = () => {
document.body.removeChild(modalOverlay);
};
modalOverlay.addEventListener('click', (e) => {
if (e.target === modalOverlay) {
closeModal();
}
});
const saveButton = document.getElementById('saveSettings');
const closeButton = document.getElementById('closeSettings');
saveButton.addEventListener('mouseover', () => {
saveButton.style.backgroundColor = '#1967d2';
});
saveButton.addEventListener('mouseout', () => {
saveButton.style.backgroundColor = '#1a73e8';
});
closeButton.addEventListener('mouseover', () => {
closeButton.style.backgroundColor = '#e8eaed';
});
closeButton.addEventListener('mouseout', () => {
closeButton.style.backgroundColor = '#f1f3f4';
});
document.getElementById('saveSettings').addEventListener('click', () => {
config.columnLayout = parseInt(document.getElementById('columnLayout').value);
config.autoPagingEnabled = document.getElementById('autoPaging').checked;
config.bibtexCopyEnabled = document.getElementById('bibtexCopy').checked;
config.bibtexCopyAlert = document.getElementById('bibtexCopyAlert').checked;
config.singleResultRedirect = document.getElementById('singleResultRedirect').checked;
config.showFrequentScholars = document.getElementById('showFrequentScholars').checked;
config.showPublicationYearDistribution = document.getElementById('showPublicationYearDistribution').checked;
GM_setValue('columnLayout', config.columnLayout);
GM_setValue('autoPagingEnabled', config.autoPagingEnabled);
GM_setValue('bibtexCopyEnabled', config.bibtexCopyEnabled);
GM_setValue('bibtexCopyAlert', config.bibtexCopyAlert);
GM_setValue('singleResultRedirect', config.singleResultRedirect);
GM_setValue('showFrequentScholars', config.showFrequentScholars);
GM_setValue('showPublicationYearDistribution', config.showPublicationYearDistribution);
addStyles();
if (config.autoPagingEnabled) {
initAutoPaging();
}
if (config.bibtexCopyEnabled) {
initBibtexCopy();
}
if (config.showFrequentScholars) {
showFrequentScholars();
} else {
removeFrequentScholars();
}
if (config.showPublicationYearDistribution) {
showPublicationYearDistribution();
} else {
const distributionDiv = document.getElementById('publication-year-distribution');
if (distributionDiv) {
distributionDiv.remove();
}
}
const newLanguage = document.querySelector('input[name="language"]:checked').value;
if (newLanguage !== config.language) {
config.language = newLanguage;
GM_setValue('language', config.language);
updateUILanguage();
}
closeModal();
});
document.getElementById('closeSettings').addEventListener('click', closeModal);
}
function initAutoPaging() {
const pager = {
nextLink: '//a[./span[@class="gs_ico gs_ico_nav_next"]]',
pageElement: '//div[@class="gs_r gs_or gs_scl"]',
HT_insert: ["#gs_res_ccl_mid", 2],
replaceE: '//div[@id="gs_n"]'
};
let curSite = {
SiteTypeID: 4.1,
pager: pager
};
const getElementByXpath = function(xpath, contextNode) {
contextNode = contextNode || document;
try {
const result = document.evaluate(xpath, contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
return result.singleNodeValue && result.singleNodeValue.nodeType === 1 && result.singleNodeValue;
} catch (err) {
console.error(`Invalid xpath: ${xpath}`);
}
};
const getAllElementsByXpath = function(xpath, contextNode) {
contextNode = contextNode || document;
const result = [];
try {
const query = document.evaluate(xpath, contextNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (let i = 0; i < query.snapshotLength; i++) {
const node = query.snapshotItem(i);
if (node.nodeType === 1) result.push(node);
}
} catch (err) {
console.error(`Invalid xpath: ${xpath}`);
}
return result;
};
const loadMoreResults = async function() {
const nextLink = getElementByXpath(curSite.pager.nextLink);
if (!nextLink) return;
const pageElements = getAllElementsByXpath(curSite.pager.pageElement);
if (pageElements.length === 0) return;
const insertPoint = document.querySelector(curSite.pager.HT_insert[0]);
if (!insertPoint) return;
try {
const response = await fetch(nextLink.href);
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const newPageElements = getAllElementsByXpath(curSite.pager.pageElement, doc);
newPageElements.forEach(elem => {
if (curSite.pager.HT_insert[1] === 2) {
insertPoint.appendChild(elem);
} else {
insertPoint.insertBefore(elem, insertPoint.firstChild);
}
});
if (config.showFrequentScholars) {
showFrequentScholars(); // Update frequent scholars after loading more results
}
showPublicationYearDistribution(); // Add this line to update year distribution
const replaceE = getElementByXpath(curSite.pager.replaceE);
if (replaceE) {
const newReplaceE = getElementByXpath(curSite.pager.replaceE, doc);
if (newReplaceE) {
replaceE.parentNode.replaceChild(newReplaceE, replaceE);
}
}
} catch (error) {
console.error('Error loading more results:', error);
}
};
window.addEventListener('scroll', function() {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight;
const clientHeight = document.documentElement.clientHeight;
if (scrollTop + clientHeight >= scrollHeight - 200) {
loadMoreResults();
}
});
}
function initBibtexCopy() {
document.addEventListener('click', function(event) {
if (config.bibtexCopyEnabled && event.target.textContent.includes('BibTeX')) {
event.preventDefault();
event.stopPropagation();
const bibtexLink = event.target.closest('a');
if (!bibtexLink) return;
const bibtexUrl = bibtexLink.href;
GM_xmlhttpRequest({
method: 'GET',
url: bibtexUrl,
onload: function(response) {
if (response.status === 200) {
GM_setClipboard(response.responseText);
if (config.bibtexCopyAlert) {
alert('BibTeX copied to clipboard');
}
} else {
console.error('Failed to fetch BibTeX');
alert('Failed to copy BibTeX');
}
},
onerror: function(error) {
console.error('Error fetching BibTeX:', error);
alert('Error copying BibTeX');
}
});
}
});
}
function redirectSingleResult() {
if (!config.singleResultRedirect) return;
const links = document.querySelectorAll('.gs_rt > a');
if (links.length !== 1) return;
if (sessionStorage.getItem(location.href) === null) {
// Prevent redirection when back button is pressed
sessionStorage.setItem(location.href, '1');
links[0].click();
}
}
function showFrequentScholars() {
const scholarCounts = {};
const scholarInfo = {};
const results = document.querySelectorAll('.gs_r.gs_or.gs_scl');
results.forEach(result => {
const authors = result.querySelectorAll('.gs_a a');
authors.forEach(author => {
const name = author.textContent.trim();
const link = author.href;
if (link.includes('user=')) {
if (link in scholarCounts) {
scholarCounts[link]++;
} else {
scholarCounts[link] = 1;
scholarInfo[link] = { name, link };
}
}
});
});
const sortedScholars = Object.entries(scholarCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
const lang = translations[config.language];
const frequentScholarsDiv = document.createElement('div');
frequentScholarsDiv.id = 'frequent-scholars';
frequentScholarsDiv.style.cssText = `
position: fixed;
right: 20px;
top: 200px;
background-color: white;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
width: 220px;
z-index: 1000;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
font-size: 12px;
line-height: 1.4;
`;
frequentScholarsDiv.innerHTML = `
<h3 style="margin-top: 0; margin-bottom: 10px; font-size: 14px;">${lang.showFrequentScholars}</h3>
<ul style="list-style-type: none; padding-left: 0; margin: 0;">
${sortedScholars.map(([link, count]) => `
<li style="margin-bottom: 8px;">
<a href="${link}" target="_blank" style="text-decoration: none; color: #1a0dab; display: inline-block; max-width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${scholarInfo[link].name}</a>
<span style="color: #006621; font-size: 12px;"> (${count})</span>
</li>
`).join('')}
</ul>
`;
document.body.appendChild(frequentScholarsDiv);
}
function removeFrequentScholars() {
const frequentScholarsDiv = document.getElementById('frequent-scholars');
if (frequentScholarsDiv) {
frequentScholarsDiv.remove();
}
}
function applyAdvancedSearch() {
const mainSearchInput = document.querySelector('input[name="q"]');
if (!mainSearchInput) return;
let query = '';
const allWords = document.getElementById('all-words').value;
const exactPhrase = document.getElementById('exact-phrase').value;
const withoutWords = document.getElementById('without-words').value;
const author = document.getElementById('author').value;
const publication = document.getElementById('publication').value;
const titleOnly = document.getElementById('title-only').checked;
if (titleOnly) {
query += 'allintitle:';
}
if (allWords) query += allWords + ' ';
if (exactPhrase) query += `"${exactPhrase}" `;
if (withoutWords) query += '-' + withoutWords.split(' ').join(' -') + ' ';
if (author) query += `author:"${author}" `;
if (publication) query += `source:"${publication}" `;
mainSearchInput.value = query.trim();
// Store the current values in sessionStorage
sessionStorage.setItem('gs_enhancer_all_words', allWords);
sessionStorage.setItem('gs_enhancer_exact_phrase', exactPhrase);
sessionStorage.setItem('gs_enhancer_without_words', withoutWords);
sessionStorage.setItem('gs_enhancer_author', author);
sessionStorage.setItem('gs_enhancer_publication', publication);
sessionStorage.setItem('gs_enhancer_title_only', titleOnly);
document.querySelector('button[type="submit"]').click();
}
function restoreAdvancedSearchValues() {
document.getElementById('all-words').value = sessionStorage.getItem('gs_enhancer_all_words') || '';
document.getElementById('exact-phrase').value = sessionStorage.getItem('gs_enhancer_exact_phrase') || '';
document.getElementById('without-words').value = sessionStorage.getItem('gs_enhancer_without_words') || '';
document.getElementById('author').value = sessionStorage.getItem('gs_enhancer_author') || '';
document.getElementById('publication').value = sessionStorage.getItem('gs_enhancer_publication') || '';
document.getElementById('title-only').checked = sessionStorage.getItem('gs_enhancer_title_only') === 'true';
}
function switchLanguage() {
config.language = config.language === 'en' ? 'zh' : 'en';
GM_setValue('language', config.language);
updateUILanguage();
}
function updateUILanguage() {
const lang = translations[config.language];
document.getElementById('settings-button').textContent = lang.settings;
const advancedSearchFields = [
{ id: 'all-words', labelKey: 'keywords', placeholderKey: 'allOfTheWords' },
{ id: 'exact-phrase', labelKey: 'exactPhrase' },
{ id: 'without-words', labelKey: 'without', placeholderKey: 'withoutWords' },
{ id: 'author', labelKey: 'author' },
{ id: 'publication', labelKey: 'publishedIn', placeholderKey: 'journalOrConference' }
];
advancedSearchFields.forEach(field => {
const label = document.querySelector(`label[for="${field.id}"]`);
if (label) {
label.textContent = lang[field.labelKey] + ':';
}
const input = document.getElementById(field.id);
if (input) {
if (field.placeholderKey) {
input.placeholder = lang[field.placeholderKey];
}
input.title = lang[field.labelKey]; // Add tooltip
}
});
const titleOnlyLabel = document.querySelector('label[for="title-only"]');
if (titleOnlyLabel) {
titleOnlyLabel.lastChild.textContent = lang.titleOnly;
}
document.getElementById('apply-button').textContent = lang.apply;
// Update frequent scholars title if it exists
const frequentScholarsTitle = document.querySelector('#frequent-scholars h3');
if (frequentScholarsTitle) {
frequentScholarsTitle.textContent = lang.showFrequentScholars;
}
}
function getPublicationYearDistribution() {
const yearCounts = {};
const results = document.querySelectorAll('.gs_r.gs_or.gs_scl');
results.forEach(result => {
const yearElement = result.querySelector('.gs_a');
if (yearElement) {
// Look for a year in the format YYYY, considering years from 1900 to 2099
const yearMatch = yearElement.textContent.match(/\b(19|20)\d{2}\b/);
if (yearMatch) {
const year = yearMatch[0];
yearCounts[year] = (yearCounts[year] || 0) + 1;
} else {
// If no year found, count it as "Unknown"
yearCounts['Unknown'] = (yearCounts['Unknown'] || 0) + 1;
}
}
});
return yearCounts;
}
function showPublicationYearDistribution() {
const existingDistribution = document.getElementById('publication-year-distribution');
if (existingDistribution) {
existingDistribution.remove();
}
if (!config.showPublicationYearDistribution) {
return;
}
const yearCounts = getPublicationYearDistribution();
let years = Object.keys(yearCounts).sort((a, b) => {
if (a === 'Unknown') return 1;
if (b === 'Unknown') return -1;
return b - a;
});
// Group years before 1990 into a single category
const oldYearsCount = years.filter(year => year !== 'Unknown' && parseInt(year) < 1990)
.reduce((sum, year) => sum + yearCounts[year], 0);
if (oldYearsCount > 0) {
yearCounts['<1990'] = oldYearsCount;
years = years.filter(year => year === 'Unknown' || parseInt(year) >= 1990);
years.unshift('<1990');
}
// Limit to the most recent 15 years/categories (including Unknown if present)
if (years.length > 15) {
const unknownIndex = years.indexOf('Unknown');
if (unknownIndex !== -1 && unknownIndex >= 15) {
years = years.slice(0, 14).concat('Unknown');
} else {
years = years.slice(0, 15);
}
}
const distributionDiv = document.createElement('div');
distributionDiv.id = 'publication-year-distribution';
distributionDiv.style.cssText = `
position: fixed;
right: 20px;
top: 200px;
background-color: white;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
width: 220px;
z-index: 1000;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
font-size: 12px;
line-height: 1.4;
`;
const lang = translations[config.language];
distributionDiv.innerHTML = `
<h3 style="margin-top: 0; margin-bottom: 10px; font-size: 14px;">${lang.publicationYearDistribution}</h3>
<div id="year-histogram"></div>
`;
const histogramDiv = distributionDiv.querySelector('#year-histogram');
const maxCount = Math.max(...years.map(year => yearCounts[year]));
years.forEach(year => {
const count = yearCounts[year];
const percentage = (count / maxCount) * 100;
const yearBar = document.createElement('div');
yearBar.style.cssText = `
display: flex;
align-items: center;
margin-bottom: 5px;
`;
yearBar.innerHTML = `
<span style="width: 45px; text-align: right; margin-right: 5px; font-size: 11px;">${year}</span>
<div style="flex-grow: 1; height: 10px; background-color: #e0e0e0; position: relative;">
<div style="position: absolute; top: 0; left: 0; height: 100%; width: ${percentage}%; background-color: #4285f4;"></div>
</div>
<span style="width: 30px; text-align: right; margin-left: 5px; font-size: 11px;">${count}</span>
`;
histogramDiv.appendChild(yearBar);
});
const frequentScholarsDiv = document.getElementById('frequent-scholars');
if (frequentScholarsDiv) {
const frequentScholarsRect = frequentScholarsDiv.getBoundingClientRect();
distributionDiv.style.top = `${frequentScholarsRect.bottom + 10}px`;
}
document.body.appendChild(distributionDiv);
}
function init() {
if (document.querySelector('#gs_top')) {
addStyles();
createSettingsButton();
updateUILanguage();
restoreAdvancedSearchValues();
if (config.autoPagingEnabled) {
initAutoPaging();
}
initBibtexCopy();
redirectSingleResult();
if (config.showFrequentScholars) {
showFrequentScholars();
}
if (config.showPublicationYearDistribution) {
showPublicationYearDistribution();
}
}
}
// Run the script
init();
})();