// ==UserScript== // @name Page Read Mode // @name:zh-CN 网页阅读模式 // @name:zh-TW 網頁閱讀模式 // @description [ALT+R] Content reader on any page, selecting the text area automatically or manually. // @description:zh-CN [ALT+R] 将任何一个网页中影响您阅读的图片,视频,广告等无关内容过滤,仅查看最关注的那一部分内容。特别适合各种内容阅读型网页。同时提供将所选区域的 HTML 代码导出的功能。 // @description:zh-TW [ALT+R] 將任何一個網頁中影響您閱讀的圖片,視頻,廣告等無關內容過濾,僅查看最關注的那一部分內容。特別適合各種內容閱讀型網頁。同時提供將所選區域的 HTML 代碼導出的功能。 // @author Moshel // @namespace https://hzy.pw // @homepageURL https://hzy.pw/ // @supportURL https://github.com/h2y/link-fix // @icon https://wiki.greasespot.net/images/f/f3/Book.png // @license GPL-3.0 // @include * // @grant GM_setClipboard // *run-at context-menu // @require https://cdn.staticfile.org/keymaster/1.6.1/keymaster.min.js // @resource useageIMG https://github.com/h2y/link-fix/raw/master/read_mode/useage.png // @date 12/17/2015 // @modified 01/23/2016 // @version 1.1.2 // @downloadURL https://update.greasyfork.icu/scripts/26709/Page%20Read%20Mode.user.js // @updateURL https://update.greasyfork.icu/scripts/26709/Page%20Read%20Mode.meta.js // ==/UserScript== /* global var */ let mode = 0, //状态标记 topNode = null, //顶层节点 styleNode = null, butNodes = null, useageNode = null; /* Tool functions */ function isNodeShow(node) { const styles = window.getComputedStyle(node); if(styles.display=='none' || styles.visibility=='hidden') return false; if(!parseInt(styles.height) || !parseInt(styles.height)) return false; return true; } /* main functions */ function enterCliping(e) { mode = 1; e.preventDefault(); //add style if(!styleNode) { styleNode = document.createElement('style'); styleNode.innerHTML = `.cliper-top-node { box-shadow: 0 0 20px #777 !important; border: 3px solid red !important; } .read-mode-reading { position: fixed !important; z-index: 9999970 !important; top: 0 !important; left: 0 !important; height: 100% !important; width: 100% !important; background-color: white !important; overflow: scroll !important; padding: 0 !important; border: 0 !important; margin: 0 !important; } .read-mode-buts { position: fixed; z-index: 9999985; top: 2rem; right: 1rem; } .read-mode-button { width: 54px; height: 54px; margin: 0 .5rem; padding: 10px 15px; color: #fff; opacity: .5; transition: 500ms; border-radius: 5px; background-color: black; } .read-mode-button:hover { background-color: white; border-radius: 0; box-shadow: 0 0 10px #000; color: #000; } img.read-mode-useage { position: fixed; right: 3rem; bottom: 2rem; z-index: 9999975; opacity: .7; }`; //styleNode.id = 'read_mode'; document.body.appendChild(styleNode); } // useage image if(!useageNode) { useageNode = document.createElement('img'); useageNode.src = 'https://github.com/h2y/link-fix/raw/master/read_mode/useage.png'; useageNode.className = 'read-mode-useage'; document.body.appendChild(useageNode); } useageNode.style.display = ''; //choose the init node topNode = document.body; let preNode = null; do { preNode = topNode; onDown(e); }while(preNode!=topNode && preNode.clientHeight*0.9 < topNode.clientHeight); } function quitCliping(e) { mode = 0; e.preventDefault(); useageNode.style.display = 'none'; changeTopNode(null); if(butNodes) butNodes.style.display = 'none'; topNode.classList.remove('read-mode-reading'); } function buildButNodes() { butNodes = document.createElement('div'); butNodes.className = 'read-mode-buts'; let buts = [ { text: "Exit read mode", handler: quitCliping, icon: '✘' }, { text: "Save HTML data", handler: onSaveHTML, icon: '❖' } ]; for(let but of buts) { let newBut = document.createElement('a'); newBut.className = 'read-mode-button'; newBut.innerHTML = but.icon; newBut.title = but.text; newBut.onclick = but.handler; butNodes.appendChild(newBut); } document.body.appendChild(butNodes); } function changeTopNode(newNode) { if(topNode) topNode.classList.remove('cliper-top-node'); if(newNode) newNode.classList.add('cliper-top-node'); else return; topNode = newNode; //scroll var winH = window.screen.availHeight, winY = window.scrollY, domH = topNode.clientHeight, domY = topNode.getBoundingClientRect().top + winY; //console.log(winH,winY,domH,domY); if(domH>winH) window.scrollTo(0, domY - 50 ); else window.scrollTo(0, domY - (winH-domH)/2 ); } /* Event handler */ function onSaveHTML(e) { let htmlStr = ''; htmlStr += topNode.outerHTML.split('\n').join('') .replace(/(id|class)=(\'.*?\'|\".*?\")/ig, '') .replace(//g, '') .replace(/>[\t ]+?<') .replace(/<(link|meta).*?>/ig, '') .replace(/.*?<\/style>/ig, '') .replace(/.*?<\/script>/ig, ''); GM_setClipboard(htmlStr); alert('Copied into clipboard.'); } function onUp(e) { if(!mode) return; e.preventDefault(); if(topNode.parentElement) changeTopNode(topNode.parentNode); } function onDown(e) { if(!mode) return; e.preventDefault(); if(!topNode.childElementCount) return; var scanNodes = topNode.children, maxNode = null; var maxHeight = -1; for(let node of scanNodes) if(isNodeShow(node) && node.clientHeight > maxHeight) { maxHeight = node.clientHeight; maxNode = node; } if(maxNode) changeTopNode(maxNode); } function onLeft(e) { if(!mode) return; e.preventDefault(); let nowNode = topNode; for(let node=nowNode; node.previousElementSibling;) { node = node.previousElementSibling; if(isNodeShow(node)) { nowNode = node; break; } } if(nowNode!=topNode) changeTopNode(nowNode); //else: up else if (topNode.parentNode) { let bakNode = nowNode = topNode; onUp(e); nowNode = topNode; onLeft(e); if(nowNode==topNode) changeTopNode(bakNode); else onDown(e); } } function onRight(e) { if(!mode) return; e.preventDefault(); let nowNode = topNode; for(let node=nowNode; node.nextElementSibling;) { node = node.nextElementSibling; if(isNodeShow(node)) { nowNode = node; break; } } if(nowNode!=topNode) changeTopNode(nowNode); //else: up else if (topNode.parentNode) { let bakNode = nowNode = topNode; onUp(e); nowNode = topNode; onRight(e); if(nowNode==topNode) changeTopNode(bakNode); else onDown(e); } } function onEnter(e) { if(!mode) return; e.preventDefault(); quitCliping(e); topNode.classList.add('read-mode-reading'); //buttons if(butNodes) butNodes.style.display = ''; else buildButNodes(); } /* Main */ key('alt+r', function(){ if(mode) quitCliping(new MouseEvent("main")); else enterCliping(new MouseEvent("main")); }); /* bind action */ key('up', onUp); key('down', onDown); key('left', onLeft); key('right', onRight); key('enter', onEnter); key('esc', quitCliping);