網頁閱讀模式

在任何需要的頁面中開啟閱讀模式,自動或手動選擇正文區域。

目前為 2017-01-20 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Page Read Mode
  3. // @name:zh-CN 网页阅读模式
  4. // @name:zh-TW 網頁閱讀模式
  5. // @description Content reader on any page, selecting the text area automatically or manually.
  6. // @description:zh-CN 在任何需要的页面中开启阅读模式,自动或手动选择正文区域。
  7. // @description:zh-TW 在任何需要的頁面中開啟閱讀模式,自動或手動選擇正文區域。
  8.  
  9. // @authuer Moshel
  10. // @namespace https://hzy.pw
  11. // @@homepageURL https://hzy.pw/p/1364
  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.  
  21. // @date 12/17/2015
  22. // @modified 01/20/2016
  23. // @version 1.0.0
  24. // ==/UserScript==
  25.  
  26.  
  27. /*
  28. global var
  29. */
  30. let mode = 0, //状态标记
  31. topNode = null, //顶层节点
  32. styleNode = null,
  33. butNodes = null,
  34. zoomLevel = 1;
  35.  
  36.  
  37. /*
  38. Tool functions
  39. */
  40. function isNodeShow(node) {
  41. const styles = window.getComputedStyle(node);
  42.  
  43. if(styles.display=='none' || styles.visibility=='hidden')
  44. return false;
  45.  
  46. if(!parseInt(styles.height) || !parseInt(styles.height))
  47. return false;
  48.  
  49. return true;
  50. }
  51.  
  52.  
  53. function nodeStyleInline(node) {
  54. let styleStr = '',
  55. styles = window.getComputedStyle(node);
  56.  
  57. let keys = Object.keys(styles);
  58. for(let key of keys) {
  59. //if(key==='cssText') continue;
  60. //if(parseInt(key)==key) continue;
  61. /*if(/^(webkit|moz|ms)/.test(key))
  62. continue;
  63.  
  64. if(styles[key]=='') continue;*/
  65.  
  66. let value = styles[key];
  67. key = changeStrStyle(key.replace('webkit','-webkit'));
  68.  
  69. styleStr += key + ':' + value + ';';
  70. }
  71.  
  72. node.className = '';
  73. node.id = '';
  74. node.style = styleStr;
  75.  
  76. //child
  77. if(node.childElementCount)
  78. for(let child of node.children)
  79. nodeStyleInline(child);
  80. }
  81.  
  82.  
  83. // textAlign -> text-align
  84. function changeStrStyle(str) {
  85. let chars = str.split('');
  86.  
  87. for(let i=chars.length-1; i>=0; i--) {
  88. let ascii = chars[i].charCodeAt(0);
  89. if(ascii>=65 && ascii<91) {
  90. //A-Z
  91. chars[i] = '-' + String.fromCharCode(ascii+32);
  92. }
  93. }
  94.  
  95. return chars.join('');
  96. }
  97.  
  98.  
  99. /*
  100. main functions
  101. */
  102. function enterCliping(e) {
  103. mode = 1;
  104. e.preventDefault();
  105.  
  106. //add style
  107. if(!styleNode) {
  108. styleNode = document.createElement('style');
  109. styleNode.innerHTML = `.cliper-top-node {
  110. box-shadow: 0 0 20px #777 !important;
  111. border: 3px solid red !important;
  112. } .read-mode-reading {
  113. position: fixed !important;
  114. z-index: 999970 !important;
  115. top: 0 !important;
  116. left: 0 !important;
  117. width: 100% !important;
  118. height: 100% !important;
  119. background-color: white !important;
  120. overflow: scroll !important;
  121. padding: 0 !important;
  122. border: 0 !important;
  123. margin: 0 !important;
  124. } .read-mode-buts {
  125. position: fixed;
  126. z-index: 999985;
  127. top: 2rem; right: 1rem;
  128. } .read-mode-button {
  129. width: 54px;
  130. height: 54px;
  131. margin: 0 .5rem;
  132. padding: 10px 15px;
  133. color: #fff;
  134. opacity: .5;
  135. transition: 500ms;
  136. border-radius: 5px;
  137. background-color: black;
  138. } .read-mode-button:hover {
  139. background-color: white;
  140. border-radius: 0;
  141. box-shadow: 0 0 10px #000;
  142. color: #000;
  143. }`;
  144. styleNode.id = 'read_mode';
  145. document.body.appendChild(styleNode);
  146. }
  147.  
  148. //choose the init node
  149. topNode = document.body;
  150. let preNode = null;
  151.  
  152. do {
  153. preNode = topNode;
  154. onDown(e);
  155. }while(preNode!=topNode && preNode.clientHeight*0.9 < topNode.clientHeight);
  156. }
  157.  
  158. function quitCliping(e) {
  159. mode = 0;
  160. e.preventDefault();
  161.  
  162. topNode.style.zoom = '';
  163.  
  164. changeTopNode(null);
  165.  
  166. if(butNodes)
  167. butNodes.style.display = 'none';
  168.  
  169. topNode.classList.remove('read-mode-reading');
  170. }
  171.  
  172.  
  173. function buildButNodes() {
  174. butNodes = document.createElement('div');
  175. butNodes.className = 'read-mode-buts';
  176.  
  177. let buts = [
  178. {
  179. text: "Exit read mode",
  180. handler: quitCliping,
  181. icon: 'x'
  182. }, {
  183. text: "Enlarge",
  184. handler: onEnlarge,
  185. icon: '+'
  186. }, {
  187. text: "Shrink",
  188. handler: onShrink,
  189. icon: '-'
  190. }, {
  191. text: "Save HTML data",
  192. handler: onSaveHTML,
  193. icon: '↓'
  194. }
  195. ];
  196.  
  197. for(let but of buts) {
  198. let newBut = document.createElement('a');
  199. newBut.className = 'read-mode-button';
  200. newBut.innerHTML = but.icon;
  201. newBut.title = but.text;
  202. newBut.onclick = but.handler;
  203. butNodes.appendChild(newBut);
  204. }
  205.  
  206. document.body.appendChild(butNodes);
  207. }
  208.  
  209.  
  210. function changeTopNode(newNode) {
  211. if(topNode)
  212. topNode.classList.remove('cliper-top-node');
  213.  
  214. if(newNode)
  215. newNode.classList.add('cliper-top-node');
  216. else
  217. return;
  218.  
  219. topNode = newNode;
  220.  
  221. //scroll
  222. var winH = window.screen.availHeight,
  223. winY = window.scrollY,
  224. domH = topNode.clientHeight,
  225. domY = topNode.getBoundingClientRect().top + winY;
  226.  
  227. if(domH>winH)
  228. document.body.scrollTop = domY - 50;
  229. else
  230. document.body.scrollTop = domY - (winH-domH)/2;
  231. }
  232.  
  233.  
  234. /*
  235. Event handler
  236. */
  237. function onEnlarge(e) {
  238. zoomLevel += .1;
  239. topNode.style.zoom = zoomLevel;
  240. }
  241. function onShrink(e) {
  242. zoomLevel -= .1;
  243. topNode.style.zoom = zoomLevel;
  244. }
  245.  
  246.  
  247. function onSaveHTML(e) {
  248. let htmlStr = '';
  249.  
  250. let styleNodes = document.querySelectorAll('style, link[rel=stylesheet]');
  251. for(let node of styleNodes) {
  252. if(node.id == 'read_mode')
  253. continue;
  254.  
  255. if(node.nodeName=="LINK")
  256. htmlStr += `<link rel="stylesheet" href="${node.href}">`;
  257. else
  258. htmlStr += node.outerHTML;
  259. }
  260.  
  261. topNode.style.zoom = '';
  262.  
  263. //TODO: node filter
  264. htmlStr += topNode.outerHTML
  265. .replace(/<style[^>]*>.*?<\/style>/ig, '')
  266. .replace(/<script[^>]*>.*?<\/script>/ig, '');
  267.  
  268. window.GM_setClipboard(htmlStr);
  269.  
  270. topNode.style.zoom = zoomLevel;
  271. alert('Copied into clipboard.');
  272. }
  273.  
  274.  
  275. function onUp(e) {
  276. if(!mode) return;
  277. e.preventDefault();
  278.  
  279. if(topNode.parentElement)
  280. changeTopNode(topNode.parentNode);
  281. }
  282.  
  283. function onDown(e) {
  284. if(!mode) return;
  285. e.preventDefault();
  286.  
  287. if(!topNode.childElementCount)
  288. return;
  289.  
  290. var scanNodes = topNode.children,
  291. maxNode = null;
  292. var maxHeight = -1;
  293.  
  294. for(let node of scanNodes)
  295. if(isNodeShow(node) && node.clientHeight > maxHeight) {
  296. maxHeight = node.clientHeight;
  297. maxNode = node;
  298. }
  299.  
  300. if(maxNode)
  301. changeTopNode(maxNode);
  302. }
  303.  
  304. function onLeft(e) {
  305. if(!mode) return;
  306. e.preventDefault();
  307.  
  308. let nowNode = topNode;
  309. for(let node=nowNode; node.previousElementSibling;) {
  310. node = node.previousElementSibling;
  311. if(isNodeShow(node)) {
  312. nowNode = node;
  313. break;
  314. }
  315. }
  316.  
  317. if(nowNode!=topNode)
  318. changeTopNode(nowNode);
  319. //else: up
  320. else if (topNode.parentNode) {
  321. let bakNode = nowNode = topNode;
  322.  
  323. onUp(e);
  324. nowNode = topNode;
  325.  
  326. onLeft(e);
  327. if(nowNode==topNode)
  328. changeTopNode(bakNode);
  329. else
  330. onDown(e);
  331. }
  332. }
  333.  
  334. function onRight(e) {
  335. if(!mode) return;
  336. e.preventDefault();
  337.  
  338. let nowNode = topNode;
  339. for(let node=nowNode; node.nextElementSibling;) {
  340. node = node.nextElementSibling;
  341. if(isNodeShow(node)) {
  342. nowNode = node;
  343. break;
  344. }
  345. }
  346.  
  347. if(nowNode!=topNode)
  348. changeTopNode(nowNode);
  349. //else: up
  350. else if (topNode.parentNode) {
  351. let bakNode = nowNode = topNode;
  352.  
  353. onUp(e);
  354. nowNode = topNode;
  355.  
  356. onRight(e);
  357. if(nowNode==topNode)
  358. changeTopNode(bakNode);
  359. else
  360. onDown(e);
  361. }
  362. }
  363.  
  364.  
  365. function onEnter(e) {
  366. if(!mode) return;
  367. e.preventDefault();
  368.  
  369. quitCliping(e);
  370.  
  371. topNode.classList.add('read-mode-reading');
  372.  
  373. topNode.style.zoom = 1.2;
  374. zoomLevel = 1.2;
  375.  
  376. //buttons
  377. if(butNodes)
  378. butNodes.style.display = '';
  379. else
  380. buildButNodes();
  381. }
  382.  
  383.  
  384. /*
  385. Main
  386. */
  387. console.log(window.key);
  388. window.key('alt+r', function(){
  389. console.log('reading');
  390. if(mode)
  391. quitCliping(new MouseEvent("main"));
  392. else
  393. enterCliping(new MouseEvent("main"));
  394. });
  395.  
  396.  
  397. /*
  398. bind action
  399. */
  400. window.key('up', onUp);
  401. window.key('down', onDown);
  402. window.key('left', onLeft);
  403. window.key('right', onRight);
  404.  
  405. window.key('enter', onEnter);
  406. window.key('esc', quitCliping);