// ==UserScript== // @name Overleaf-Bib-Helper // @namespace com.Xunjian.overleaf // @version 1.3 // @description Enhances Overleaf by allowing article searches and BibTeX retrieval from DBLP and Google Scholar // @author Xunjian Yin // @match https://www.overleaf.com/project/* // @match https://cn.overleaf.com/project* // @match https://latex.pku.edu.cn/project/* // @icon https://www.overleaf.com/favicon.ico // @require https://cdn.jsdelivr.net/npm/@floating-ui/core@1.6.8 // @require https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.6.12 // @require https://cdn.jsdelivr.net/npm/simple-notify@1.0.6/dist/simple-notify.min.js // @resource notifycss https://cdn.jsdelivr.net/npm/simple-notify/dist/simple-notify.css // @grant GM_setClipboard // @grant GM_xmlhttpRequest // @grant GM_getResourceText // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_openInTab // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; GM_addStyle(GM_getResourceText('notifycss')); injectScript(); setInterval(() => { if (!document.getElementById('toggleIcon')) { injectScript(); } }, 2000); })(); function injectScript() { waitUtil('div.ol-cm-toolbar-button-group.ol-cm-toolbar-end', el => { let iconBox = createToggleIcon(); el.appendChild(iconBox); let popupBox = createBox(); let oldPopup = document.querySelector("#popup"); if (oldPopup) { popupBox = oldPopup; } document.body.appendChild(popupBox); FloatingUIDOM.autoUpdate(iconBox, popupBox, () => { FloatingUIDOM.computePosition(iconBox, popupBox, { middleware: [FloatingUICore.shift(), FloatingUICore.flip(), FloatingUICore.offset(6)], }).then(({ x, y }) => { Object.assign(popupBox.style, { top: `${y}px`, left: `${x}px` }); }); }); iconBox.onclick = () => { togglePopup(popupBox); }; let searchIcon = document.getElementById('search-word'); let searchInput = document.querySelector('.search-input'); searchIcon.onclick = () => { queryArticle(); }; searchInput.onkeydown = (env) => { if (env.key === 'Enter') { queryArticle(); } }; // Global Esc key listener document.onkeydown = (env) => { if (env.key === 'Escape' && showBox) { togglePopup(popupBox); } }; let content = document.getElementById("search-content"); content.onclick = (env) => { if (env.target.className == 'scholar-data') { let source = document.getElementById("source").value; let id = env.target.getAttribute("data-cid"); if (source === "DBLP") { getBibTexDBLP(id).then(bib => { new Notify({ status: 'success', title: 'Copy successfully', text: 'Bib has been copied to clipboard', effect: 'slide', type: 'filled' }); GM_setClipboard(bib); }).catch(_ => { new Notify({ status: 'error', title: "Copy failed", text: "Failed to get BibTeX from DBLP", effect: "slide", type: "filled" }); }); } else if (source === "GoogleScholar") { getBibTexGoogleScholar(id).then(bib => { new Notify({ status: 'success', title: 'Copy successfully', text: 'Bib has been copied to clipboard', effect: 'slide', type: 'filled' }); GM_setClipboard(bib); }).catch(_ => { new Notify({ status: 'error', title: "Copy failed", text: "Failed to get BibTeX from Google Scholar", effect: "slide", type: "filled" }); }); } } }; }); } function togglePopup(popupBox) { showBox = !showBox; popupBox.style.display = showBox ? 'block' : 'none'; if (showBox) { document.querySelector('.search-input').focus(); // Optional: Focus input when popup opens } } function queryArticle() { let content = document.getElementById("search-content"); content.innerHTML = "Loading......"; let word = document.querySelector('input.search-input').value; let source = document.getElementById("source").value; let resultCount = document.getElementById("resultCount").value; if (source === "DBLP") { getArticleIDListDBLP(word, resultCount).then(lists => { if (lists.length === 0) { content.innerHTML = "No articles found."; throw new Error("No articles found"); } let searchText = ""; lists.forEach(article => { searchText += scholarContent(`${article.title}@${article.author}`, article.url); }); content.innerHTML = searchText; }).catch(err => { console.log("Error:", err); if (content.innerHTML !== "No articles found.") { content.innerHTML = "Failed to load articles."; } new Notify({ status: 'error', title: 'Request failed', text: 'Please check your query or try again later.', effect: 'slide', type: 'filled' }); }); } else if (source === "GoogleScholar") { let yearFrom = document.getElementById("yearFrom").value; let yearTo = document.getElementById("yearTo").value; let sortBy = document.getElementById("sortBy").value; getArticleIDListGoogleScholar(word, resultCount, yearFrom, yearTo, sortBy).then(lists => { if (lists.length === 0) { content.innerHTML = "No articles found."; throw new Error("No articles found"); } let searchText = ""; lists.forEach(article => { searchText += scholarContent(`${article.title}@${article.author}`, article.id); }); content.innerHTML = searchText; }).catch(err => { console.log("Error:", err); if (content.innerHTML !== "No articles found.") { content.innerHTML = "Failed to load articles."; } new Notify({ status: 'error', title: 'Request failed', text: 'Please check your query or try again later.', effect: 'slide', type: 'filled' }); }); } } function waitUtil(el, callback, timeout = 6000) { let query = setInterval(() => { let target = document.querySelector(el); if (target) { clearInterval(query); callback(target); } }); setTimeout(() => { clearInterval(query); }, timeout); } function createToggleIcon() { let iconBox = document.createElement('div'); iconBox.className = 'ol-cm-toolbar-button'; iconBox.style.display = 'flex'; iconBox.style.justifyContent = 'center'; iconBox.style.alignItems = 'center'; iconBox.id = "toggleIcon"; iconBox.innerHTML = ''; return iconBox; } function createBox() { let box = document.createElement('div'); box.id = "popup"; box.style = 'width:300px;background:#eef;padding:10px;border:1px solid #ccc;border-radius:5px;position:absolute;display:none;top:0px;left:0px'; box.innerHTML = `