网页阅读模式

[ALT+R] 在任何需要的页面中开启阅读模式,自动或手动选择正文区域。

目前为 2017-01-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Page Read Mode
  3. // @name:zh-CN 网页阅读模式
  4. // @name:zh-TW 網頁閱讀模式
  5. // @description [ALT+R] Content reader on any page, selecting the text area automatically or manually.
  6. // @description:zh-CN [ALT+R] 在任何需要的页面中开启阅读模式,自动或手动选择正文区域。
  7. // @description:zh-TW [ALT+R] 在任何需要的頁面中開啟閱讀模式,自動或手動選擇正文區域。
  8.  
  9. // @authuer Moshel
  10. // @namespace https://hzy.pw
  11. // @homepageURL https://greasyfork.org/zh-CN/scripts/26709
  12. // @supportURL https://github.com/h2y/link-fix
  13. // @icon https://wiki.greasespot.net/images/f/f3/Book.png
  14. // @license GPL-3.0
  15.  
  16. // @include *
  17. // @grant GM_setClipboard
  18. // *run-at context-menu
  19. // @require https://cdn.staticfile.org/keymaster/1.6.1/keymaster.min.js
  20. // @resource useageIMG https://github.com/h2y/link-fix/raw/master/read_mode/useage.png
  21.  
  22. // @date 12/17/2015
  23. // @modified 01/21/2016
  24. // @version 1.1.1
  25. // ==/UserScript==
  26.  
  27.  
  28. /*
  29. global var
  30. */
  31. let mode = 0, //状态标记
  32. topNode = null, //顶层节点
  33. styleNode = null,
  34. butNodes = null,
  35. useageNode = null;
  36.  
  37.  
  38. /*
  39. Tool functions
  40. */
  41. function isNodeShow(node) {
  42. const styles = window.getComputedStyle(node);
  43.  
  44. if(styles.display=='none' || styles.visibility=='hidden')
  45. return false;
  46.  
  47. if(!parseInt(styles.height) || !parseInt(styles.height))
  48. return false;
  49.  
  50. return true;
  51. }
  52.  
  53.  
  54. /*
  55. main functions
  56. */
  57. function enterCliping(e) {
  58. mode = 1;
  59. e.preventDefault();
  60.  
  61. //add style
  62. if(!styleNode) {
  63. styleNode = document.createElement('style');
  64. styleNode.innerHTML = `.cliper-top-node {
  65. box-shadow: 0 0 20px #777 !important;
  66. border: 3px solid red !important;
  67. } .read-mode-reading {
  68. position: fixed !important;
  69. z-index: 9999970 !important;
  70. top: 0 !important;
  71. left: 0 !important;
  72. height: 100% !important;
  73. width: 100% !important;
  74. width: calc(100%-30px) !important;
  75. background-color: white !important;
  76. overflow: scroll !important;
  77. padding: 30px !important;
  78. border: 0 !important;
  79. margin: 0 !important;
  80. } .read-mode-buts {
  81. position: fixed;
  82. z-index: 9999985;
  83. top: 2rem; right: 1rem;
  84. } .read-mode-button {
  85. width: 54px;
  86. height: 54px;
  87. margin: 0 .5rem;
  88. padding: 10px 15px;
  89. color: #fff;
  90. opacity: .5;
  91. transition: 500ms;
  92. border-radius: 5px;
  93. background-color: black;
  94. } .read-mode-button:hover {
  95. background-color: white;
  96. border-radius: 0;
  97. box-shadow: 0 0 10px #000;
  98. color: #000;
  99. } img.read-mode-useage {
  100. position: fixed;
  101. right: 3rem;
  102. bottom: 2rem;
  103. z-index: 9999975;
  104. opacity: .7;
  105. }`;
  106. //styleNode.id = 'read_mode';
  107. document.body.appendChild(styleNode);
  108. }
  109.  
  110. // useage image
  111. if(!useageNode) {
  112. useageNode = document.createElement('img');
  113. useageNode.src = 'https://github.com/h2y/link-fix/raw/master/read_mode/useage.png';
  114. useageNode.className = 'read-mode-useage';
  115. document.body.appendChild(useageNode);
  116. }
  117.  
  118. useageNode.style.display = '';
  119.  
  120. //choose the init node
  121. topNode = document.body;
  122. let preNode = null;
  123.  
  124. do {
  125. preNode = topNode;
  126. onDown(e);
  127. }while(preNode!=topNode && preNode.clientHeight*0.9 < topNode.clientHeight);
  128. }
  129.  
  130. function quitCliping(e) {
  131. mode = 0;
  132. e.preventDefault();
  133.  
  134. useageNode.style.display = 'none';
  135.  
  136. changeTopNode(null);
  137.  
  138. if(butNodes)
  139. butNodes.style.display = 'none';
  140.  
  141. topNode.classList.remove('read-mode-reading');
  142. }
  143.  
  144.  
  145. function buildButNodes() {
  146. butNodes = document.createElement('div');
  147. butNodes.className = 'read-mode-buts';
  148.  
  149. let buts = [
  150. {
  151. text: "Exit read mode",
  152. handler: quitCliping,
  153. icon: '✘'
  154. }, {
  155. text: "Save HTML data",
  156. handler: onSaveHTML,
  157. icon: '❖'
  158. }
  159. ];
  160.  
  161. for(let but of buts) {
  162. let newBut = document.createElement('a');
  163. newBut.className = 'read-mode-button';
  164. newBut.innerHTML = but.icon;
  165. newBut.title = but.text;
  166. newBut.onclick = but.handler;
  167. butNodes.appendChild(newBut);
  168. }
  169.  
  170. document.body.appendChild(butNodes);
  171. }
  172.  
  173.  
  174. function changeTopNode(newNode) {
  175. if(topNode)
  176. topNode.classList.remove('cliper-top-node');
  177.  
  178. if(newNode)
  179. newNode.classList.add('cliper-top-node');
  180. else
  181. return;
  182.  
  183. topNode = newNode;
  184.  
  185. //scroll
  186. var winH = window.screen.availHeight,
  187. winY = window.scrollY,
  188. domH = topNode.clientHeight,
  189. domY = topNode.getBoundingClientRect().top + winY;
  190. //console.log(winH,winY,domH,domY);
  191.  
  192. if(domH>winH)
  193. window.scrollTo(0, domY - 50 );
  194. else
  195. window.scrollTo(0, domY - (winH-domH)/2 );
  196. }
  197.  
  198.  
  199. /*
  200. Event handler
  201. */
  202. function onSaveHTML(e) {
  203. let htmlStr = '';
  204.  
  205. htmlStr += topNode.outerHTML.split('\n').join('')
  206. .replace(/(id|class)=(\'.*?\'|\".*?\")/ig, '')
  207. .replace(/<!--.*?-->/g, '')
  208. .replace(/>[\t ]+?</g, '><')
  209. .replace(/<(link|meta).*?>/ig, '')
  210. .replace(/<style.*?>.*?<\/style>/ig, '')
  211. .replace(/<script.*?>.*?<\/script>/ig, '');
  212.  
  213. GM_setClipboard(htmlStr);
  214.  
  215. alert('Copied into clipboard.');
  216. }
  217.  
  218.  
  219. function onUp(e) {
  220. if(!mode) return;
  221. e.preventDefault();
  222.  
  223. if(topNode.parentElement)
  224. changeTopNode(topNode.parentNode);
  225. }
  226.  
  227. function onDown(e) {
  228. if(!mode) return;
  229. e.preventDefault();
  230.  
  231. if(!topNode.childElementCount)
  232. return;
  233.  
  234. var scanNodes = topNode.children,
  235. maxNode = null;
  236. var maxHeight = -1;
  237.  
  238. for(let node of scanNodes)
  239. if(isNodeShow(node) && node.clientHeight > maxHeight) {
  240. maxHeight = node.clientHeight;
  241. maxNode = node;
  242. }
  243.  
  244. if(maxNode)
  245. changeTopNode(maxNode);
  246. }
  247.  
  248. function onLeft(e) {
  249. if(!mode) return;
  250. e.preventDefault();
  251.  
  252. let nowNode = topNode;
  253. for(let node=nowNode; node.previousElementSibling;) {
  254. node = node.previousElementSibling;
  255. if(isNodeShow(node)) {
  256. nowNode = node;
  257. break;
  258. }
  259. }
  260.  
  261. if(nowNode!=topNode)
  262. changeTopNode(nowNode);
  263. //else: up
  264. else if (topNode.parentNode) {
  265. let bakNode = nowNode = topNode;
  266.  
  267. onUp(e);
  268. nowNode = topNode;
  269.  
  270. onLeft(e);
  271. if(nowNode==topNode)
  272. changeTopNode(bakNode);
  273. else
  274. onDown(e);
  275. }
  276. }
  277.  
  278. function onRight(e) {
  279. if(!mode) return;
  280. e.preventDefault();
  281.  
  282. let nowNode = topNode;
  283. for(let node=nowNode; node.nextElementSibling;) {
  284. node = node.nextElementSibling;
  285. if(isNodeShow(node)) {
  286. nowNode = node;
  287. break;
  288. }
  289. }
  290.  
  291. if(nowNode!=topNode)
  292. changeTopNode(nowNode);
  293. //else: up
  294. else if (topNode.parentNode) {
  295. let bakNode = nowNode = topNode;
  296.  
  297. onUp(e);
  298. nowNode = topNode;
  299.  
  300. onRight(e);
  301. if(nowNode==topNode)
  302. changeTopNode(bakNode);
  303. else
  304. onDown(e);
  305. }
  306. }
  307.  
  308.  
  309. function onEnter(e) {
  310. if(!mode) return;
  311. e.preventDefault();
  312.  
  313. quitCliping(e);
  314.  
  315. topNode.classList.add('read-mode-reading');
  316.  
  317. //buttons
  318. if(butNodes)
  319. butNodes.style.display = '';
  320. else
  321. buildButNodes();
  322. }
  323.  
  324.  
  325. /*
  326. Main
  327. */
  328. key('alt+r', function(){
  329. if(mode)
  330. quitCliping(new MouseEvent("main"));
  331. else
  332. enterCliping(new MouseEvent("main"));
  333. });
  334.  
  335.  
  336. /*
  337. bind action
  338. */
  339. key('up', onUp);
  340. key('down', onDown);
  341. key('left', onLeft);
  342. key('right', onRight);
  343.  
  344. key('enter', onEnter);
  345. key('esc', quitCliping);