您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Will allow you to easily organize and sort packages you might want to use in the future
// ==UserScript== // @name NPM Favorites ❤ // @namespace http://tampermonkey.net/ // @version 2024-08-11 // @description Will allow you to easily organize and sort packages you might want to use in the future // @author GV3Dev // @match https://www.npmjs.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=npmjs.com // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @require http://code.jquery.com/jquery-latest.js // @require https://code.jquery.com/ui/1.12.1/jquery-ui.js // @license MIT // ==/UserScript== let $ = window.jQuery; var j = $.noConflict(); const main = async () => { if (location.href.includes("https://www.npmjs.com")) { setupPage(); monitorUrlChanges(); } } main(); function setupPage() { const mainMenu = document.querySelector("#main-menu"); if (!mainMenu) return; let bookmarksBtn = document.querySelector("#open-favorites-npm"); if (!bookmarksBtn) { bookmarksBtn = document.createElement("li"); bookmarksBtn.innerHTML = `<a style="cursor:pointer;" role="menuitem" class="c6c55db4 no-underline f6-ns f7 fw5 dim pr2 pl2" id="open-favorites-npm">Favorites</a>`; bookmarksBtn.className = "dib"; bookmarksBtn.title = "view favorited packages ♥"; mainMenu.append(bookmarksBtn); bookmarksBtn.addEventListener("click", openFavorites); } const heart = mainMenu.parentElement.parentElement.querySelector("span"); if (location.href.includes("https://www.npmjs.com/package/")) { const packageName = location.href.split("/").pop(); const savedPackage = GM_getValue(packageName); heart.style = "cursor:pointer; transition: .5s;"; heart.title = savedPackage ? `Remove package from favorites` : `Add package to favorites`; heart.style.color = savedPackage ? "red" : ""; heart.addEventListener("click", () => { toggleFavorite(packageName, heart); }); if (savedPackage) { addHeartEmojiToHeader(); }else{ removeHeartEmojiFromHeader(); } }else{ heart.title = ""; heart.style = ""; } } function openFavorites(evt) { evt.preventDefault(); let menu = document.querySelector('#favorites-menu-npm'); if (menu) { menu.style.display = menu.style.display === 'none' ? 'flex' : 'none'; if (menu.style.display === 'flex') { const toBeFilled = document.querySelector("#fav-contain"); populateFavorites(toBeFilled); } } else { menu = document.createElement('div'); menu.id = 'favorites-menu-npm'; menu.style.cssText = ` position: fixed; top: 10px; right: 10px; width: 300px; min-height: 250px; max-height: 500px; background-color: #fff; border: 1px solid lightgray; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); z-index: 1000; display: flex; justify-content:flex-start;align-items:center; flex-direction:column;border-radius:5px; padding:5px; font-family: 'Source Sans Pro', 'Lucida Grande', sans-serif; `; menu.innerHTML = ` <h2 style="width:100%; text-align:center;margin-bottom:0; padding-bottom:0;">NPM Favorites <span style="color:red">❤</span></h2> <p style="text-align:center;width:95%;">Your favorite packages, within reach!</p> <div style="display:flex;justify-content:flex-start;align-items:center;flex-direction:row;background-color:rgba(0,0,0,0.04);padding:8px;padding-left:12px;border-radius:5px;width:90%;margin-top:5px;margin-bottom:10px;"> <svg width="15px" height="15px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" aria-hidden="true"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g stroke="#777777" stroke-width="1.3"><g><path d="M13.4044,7.0274 C13.4044,10.5494 10.5494,13.4044 7.0274,13.4044 C3.5054,13.4044 0.6504,10.5494 0.6504,7.0274 C0.6504,3.5054 3.5054,0.6504 7.0274,0.6504 C10.5494,0.6504 13.4044,3.5054 13.4044,7.0274 Z"></path><path d="M11.4913,11.4913 L17.8683,17.8683"></path></g></g></g></svg> <input id="search-fav-npm" type="text" placeholder="Search favorites" style="outline:none;border:none;background-color:transparent; padding-left:10px; font-family: var(--code); font-size:12px;"> </div> <div id="fav-contain" style="margin-top:5px; width:95%; height:fit-content; max-height:85%; overflow:hidden; overflow-y:auto; padding:5px; margin-bottom:10px;"></div> <p style="padding:0;margin:0;text-align:center;margin-top:15px;margin-bottom:20px;font-size:13px;opacity:0.85;">Brought to you by <a href="https://github.com/gv3dev" target="_blank" style="font-weight:bold; color:red; cursor:pointer; text-decoration:none;">GV3Dev</a<p> `; document.body.appendChild(menu); $(menu).draggable(); injectScrollbarCSS(); const searchBar = menu.querySelector("#search-fav-npm"); const toBeFilled = document.querySelector("#fav-contain"); populateFavorites(toBeFilled); searchBar.addEventListener("keyup", (evt)=>{handleSearch(evt, toBeFilled)}) } } function handleSearch(evt, toBeFilled) { const searchQuery = evt.target.value.toLowerCase(); const favorites = GM_listValues(); if (searchQuery === '') { populateFavorites(toBeFilled); } else { const filteredFavorites = favorites.filter(packageName => { const packageData = GM_getValue(packageName); return packageName.toLowerCase().includes(searchQuery) || (packageData.description && packageData.description.toLowerCase().includes(searchQuery)); }); populateFavorites(toBeFilled, filteredFavorites); } } function populateFavorites(menu, filteredFavorites = null) { menu.innerHTML = ''; const favorites = filteredFavorites || GM_listValues(); if (favorites.length > 0) { const sortedFavorites = favorites .map(packageName => { const packageData = GM_getValue(packageName); return { name: packageName, data: packageData }; }) .sort((a, b) => new Date(b.data.addedAt) - new Date(a.data.addedAt)); sortedFavorites.forEach(({ name, data }) => { let item = document.createElement("div"); item.style = ` width: 100%; min-height: 50px; height: fit-content; border-bottom: 1px solid rgba(0,0,0,0.05); display: flex; align-items: flex-start; justify-content: center; flex-direction: column; padding: 5px; padding-bottom:8px; margin-top:5px; margin-bottom:5px; cursor:pointer; transition: background-color 0.3s ease; `; item.innerHTML = ` <span style="width: 100%; display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin-bottom: 5px;"> <h2 style="margin: 0; font-size: 16px;">${name}</h2> <span title="remove from favorites" style="cursor: pointer; transition: color 0.3s; font-size: 18px; padding: 2px 5px;" class="removeFavorite">×</span> </span> <p style="margin: 0; font-size: 14px; color: gray; text-align:left;">${data.description.length > 0 ? data.description : "For use in future projects."}</p> `; item.addEventListener('mouseover', () => { item.style.backgroundColor = '#f5f5f5'; }); item.addEventListener('mouseout', () => { item.style.backgroundColor = ''; }); item.addEventListener('click', () => { window.location.href = data.url; }); item.querySelector('.removeFavorite').addEventListener('click', (e) => { e.stopPropagation(); GM_deleteValue(name); populateFavorites(menu); }); item.querySelector('.removeFavorite').addEventListener('mouseover', (e) => { e.target.style.color = 'red'; }); item.querySelector('.removeFavorite').addEventListener('mouseout', (e) => { e.target.style.color = ''; }); menu.append(item); }); } else { menu.innerHTML = `<p style="text-align:center;">You have no favorites</p>`; } } function toggleFavorite(packageName, btn) { if (location.href.includes("https://www.npmjs.com/package/")) { const savedPackage = GM_getValue(packageName); if (savedPackage) { GM_deleteValue(packageName); btn.style = "color:; transition:.5s; cursor:pointer;"; btn.title = `Add package to favorites`; removeHeartEmojiFromHeader(); } else { const description = prompt("💡 Add a reminder\n\nWhat do you plan to use this package for? Describe it here to help you remember later!\n\nYou can leave empty if you would like.\n","For use in future projects."); if (description !== null) { GM_setValue(packageName, { url: location.href, description, addedAt: new Date().toISOString() }); btn.style = "color:red; transition:.5s; cursor:pointer;"; btn.title = `Remove package from favorites`; addHeartEmojiToHeader(); } } } } function addHeartEmojiToHeader() { const header = document.querySelector("#top").firstChild.firstChild; if (header && !header.innerText.includes("😍")) { header.innerText += "😍"; header.title = "You have favorited this package ♥"; } } function removeHeartEmojiFromHeader() { const header = document.querySelector("#top").firstChild.firstChild; if (header) { header.innerText = header.innerText.replace("😍", ""); header.title = ""; } } function monitorUrlChanges() { let previousUrl = location.href; setInterval(() => { const currentUrl = location.href; if (currentUrl !== previousUrl) { previousUrl = currentUrl; setupPage(); } }, 100); } // helper functions function injectScrollbarCSS() { const css = ` #fav-contain::-webkit-scrollbar { width: 5px; } #fav-contain::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 10px; } #fav-contain::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 10px; } #fav-contain::-webkit-scrollbar-thumb:hover { background:rgba(0,0,0,0.5); } `; const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); } async function waitForElem(selector, all = false) { return new Promise((resolve) => { const checkElements = () => { const elements = all ? document.querySelectorAll(selector) : document.querySelector(selector); if (!all) { if (elements) { resolve(elements); } else { requestAnimationFrame(checkElements); } } else { if (elements.length > 0) { resolve(elements); } else { requestAnimationFrame(checkElements); } } }; checkElements(); }); }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址