您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Create a Reading List in the left area. Long click on a Link to add to the List (or remove from).
// ==UserScript== // @name Wikipedia Reading List // @description Create a Reading List in the left area. Long click on a Link to add to the List (or remove from). // @match https://*.wikipedia.org/* // @run-at document-end // @version 1.1 // @grant none // @namespace https://greasyfork.org/users/213706 // ==/UserScript== if(typeof unsafeWindow == "undefined") { unsafeWindow = window; } const DB = "readList"; var readList = { db: [], index: {}, leftArea: null, //+------------------------------------------------------ //| //| INITIALIZATION //| //+------------------------------------------------------ init: function() { this.initCSS(); this.initDatabase(); this.initEvents(); this.initLeftArea(); }, // Add our CSS to the page initCSS: function() { var css = document.createElement('style'); css.textContent = ` a { transition: background .3s; } .readlist { background: rgba(255,215,0,0.6); } .addToReadList { animation: addToReadList .3s; } #p-readlist ul { position: relative; left: -1em; padding-left: 1em !important; width: 100%; overflow: hidden; } #p-readlist a { white-space: nowrap; } #p-readlist .delete { display: none; width: 1em; margin-left: -1.2em; } #p-readlist a:hover .delete { display: inherit; }`; document.head.appendChild(css); }, /** * Initialize database */ initDatabase: function() { var data = localStorage.getItem(DB); if(!data) { return; } this.db = JSON.parse(data); this.db.forEach(function([href, title]) { this.index[href] = 1; }.bind(this)); // Init classes of links in this page var aList = document.querySelectorAll('a'); for(let i=0; i<aList.length; i++) { let href = aList[i].getAttribute('href'); if(href && typeof this.index[href.trim()] != "undefined") { aList[i].classList.add("readlist"); } } }, /** * Add eventlisteners to handle long click on a link */ initEvents: function() { var timerClickDuration, clicLong = false; var onMousedown = function(e, isNav){ if (!e.target.matches('a')) { return; } if(readList.isRightClick(e)) { return; } timerClickDuration = setTimeout(function(){ clicLong = true; readList.handleClick(e.target, isNav || false); }, 200); }; var onClick = function(e, isNav){ timerClickDuration && clearTimeout(timerClickDuration); if(!clicLong) { return; } e.preventDefault(); clicLong = false; }; document.getElementById('bodyContent').addEventListener('mousedown', onMousedown); document.getElementById('bodyContent').addEventListener('click', onClick); document.getElementById('left-navigation').addEventListener('mousedown', function(e){ onMousedown(e, true); }); document.getElementById('left-navigation').addEventListener('click', function(e){ onClick(e, true); }); // Listen changes to readList from another tab window.addEventListener('storage', function (e) { switch(e.key) { case DB + '_add': var [href, title] = JSON.parse(e.newValue); readList.add(href, title, true); break; case DB + '_remove': readList.remove(e.newValue, true); break; } }); }, /** * Checks if the click event has been triggered * with the right button of the mouse * @param ClickEvent e * @return boolean */ isRightClick: function(e) { // Gecko (Firefox), WebKit (Safari/Chrome) & Opera if ("which" in e) { return e.which == 3; } // IE, Opera if ("button" in e) { return e.button == 2; } return false; }, /** * Display the reading list in the left area */ initLeftArea: function() { // Add readList block to left area var div = document.createElement('div'); div.innerHTML = ` <div class="portal" role="navigation" id="p-readlist" aria-labelledby="p-readlist-label"> <h3 id="p-readlist-label">Reading List</h3> <div class="body"><ul></ul> </div>`; document.getElementById('mw-panel').appendChild(div); this.leftArea = document.getElementById('p-readlist').querySelector('ul'); // Display current readList for(let i=0; i<this.db.length; i++) { var [href, title] = this.db[i]; this.addToLeftArea(href, title); } // Listen click on "delete" this.leftArea.addEventListener('click', function(e) { if (!e.target.matches('.delete')) { return; } e.preventDefault(); // Confirm before delete if (!confirm(`Delete "${e.target.nextElementSibling.innerText}" ?`)) { return; } readList.remove(e.target.parentNode.getAttribute('href')); }); }, /** * Display link in left area * @param string href * @param string title */ addToLeftArea: function(href, title) { var li = document.createElement('li'); li.innerHTML = `<a href="${href}" title="${title}"> <span class="delete" title="Delete">×</span> <span>${title}</span> </a>`; this.leftArea.appendChild(li); }, //+------------------------------------------------------ //| //| ADD OR REMOVE FROM READLIST //| //+------------------------------------------------------ /** * Called after a long click on link * @param DOMElement a * @param boolean isNav */ handleClick: function(a, isNav) { var href = a.getAttribute('href').trim(); if(typeof this.index[href] == "undefined") { var title = (isNav ? document.getElementById('firstHeading') : a).innerText.trim(); this.add(href, title, false); } else { this.remove(href, false); } }, /** * Add link to reading list * @param string href * @param string title * @param boolean isSync */ add: function(href, title, isSync) { if(/^javascript:/.test(href)) { return; } // Add class to every link with this href var aList = document.querySelectorAll("a[href='" + href + "']"); for(let i=0; i<aList.length; i++) { aList[i].classList.add('readlist'); } // Update database this.index[href] = 1; this.db.push([href, title]); if(!isSync) { localStorage.setItem(DB + '_add', JSON.stringify([href, title])); // used to sync all tabs this.saveDb(); } // Update list in left area this.addToLeftArea(href, title); }, /** * Remove link from read link * @param string href * @param boolean isSync */ remove: function(href, isSync) { // Remove class to every link with this href var aList = document.querySelectorAll("a[href='" + href + "']"); for(let i=0; i<aList.length; i++) { aList[i].classList.remove('readlist'); } // Update database delete this.index[href]; var i = this.db.findIndex(([_href, _title]) => _href === href); this.db.splice(i, 1); if(!isSync) { localStorage.setItem(DB + '_remove', href); // used to sync all tabs this.saveDb(); } // Update list in left area var a = this.leftArea.querySelector("a[href='" + href + "']"); a && this.leftArea.removeChild(a.parentNode); }, /** * Save db to localStorage */ saveDb: function() { setTimeout(function(){ localStorage.setItem(DB, JSON.stringify(this.db)); }.bind(this),0); } }; window.addEventListener('load', function(){ readList.init(); }, false);