Hacker News - Folding Subtrees

Fold and unfold arbitrary comment subtrees.

  1. // ==UserScript==
  2. // @name Hacker News - Folding Subtrees
  3. // @namespace https://github.com/pdkl95/user_scripts
  4. // @description Fold and unfold arbitrary comment subtrees.
  5. // @include https://news.ycombinator.com/item*
  6. // @version 1.2
  7. // @grant none
  8. // ==/UserScript==
  9.  
  10. /*
  11. hacker_news-folding_subtrees.user.js
  12. A user script to fold Hacker News.comments.
  13. Copyright (C) 2015 Brent Sanders
  14.  
  15. This program is free software: you can redistribute it and/or modify
  16. it under the terms of the GNU General Public License as published by
  17. the Free Software Foundation, either version 3 of the License, or
  18. (at your option) any later version.
  19.  
  20. This program is distributed in the hope that it will be useful,
  21. but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. GNU General Public License for more details.
  24.  
  25. You should have received a copy of the GNU General Public License
  26. along with this program. If not, see <http://www.gnu.org/licenses/>.
  27. */
  28.  
  29. (function () {
  30. var icon_padding_px = 6;
  31.  
  32. var post_selector = '.athing';
  33. var indent_selector = '.ind';
  34. var header_selector = '.default div:nth-child(1)';
  35. var votebox_selector = 'tr td:nth-child(2)';
  36. var style_r90 = ".rotate90 { transform: rotate(90deg); }";
  37. var style_r270 = ".rotate270 { transform: rotate(270deg); }";
  38. var style_foldunfold = ".foldunfoldbtn { position: relative; top: -1px; }";
  39. var style_fold = '.foldbtn { opacity: 0.5; }';
  40. var style_unfold = '.unfoldbtn { opacity: 0.7; }';
  41. var style_ctlbox = '.foldctlbox { position: absolute; padding-left: ' + icon_padding_px + 'px; }';
  42. var style_list = [style_r90, style_r270, style_foldunfold, style_fold, style_unfold, style_ctlbox];
  43. var extra_style_sheet = document.createElement('style');
  44. extra_style_sheet.type = 'text/css';
  45. extra_style_sheet.appendChild(document.createTextNode(style_list.join("\n")));
  46. var head = document.querySelector('head');
  47. head.appendChild(extra_style_sheet);
  48. var foldified_count = 0;
  49. function hide (el) {
  50. el.style.display = 'none';
  51. }
  52.  
  53. function show_element (el, mode) {
  54. if (el) {
  55. el.style.display = mode;
  56. }
  57. }
  58.  
  59. function show_block (el) { show_element(el, 'block' ); }
  60. function show_inline (el) { show_element(el, 'inline' ); }
  61. function show_inline_block (el) { show_element(el, 'inline-block'); }
  62.  
  63. function set_toggle_button_fold (btn) {
  64. btn.classList.add('foldbtn');
  65. btn.classList.add('rotate90');
  66. btn.classList.remove('unfoldbtn');
  67. btn.classList.remove('rotate270');
  68. }
  69. function set_toggle_button_unfold (btn) {
  70. btn.classList.remove('foldbtn');
  71. btn.classList.remove('rotate90');
  72. btn.classList.add('unfoldbtn');
  73. btn.classList.add('rotate270');
  74. }
  75.  
  76. function make_toggle_button (btn_type, rotation_class) {
  77. var btn = document.createElement('div');
  78. btn.classList.add('votearrow');
  79. btn.classList.add('foldunfoldbtn');
  80. set_toggle_button_fold(btn);
  81. show_inline_block(btn);
  82. return btn;
  83. }
  84. function each_subtree_post (post, func) {
  85. if (!post.hasOwnProperty('indent_width')) { return; }
  86.  
  87. var width = post.indent_width || 0;
  88. post = post.nextElementSibling;
  89.  
  90. while (post && post.hasOwnProperty('indent_width') && (post.indent_width > width)) {
  91. func(post);
  92. post = post.nextElementSibling;
  93. }
  94. }
  95.  
  96. function foldify_post(post_el, idx) {
  97. var ind_el = post_el.querySelector(indent_selector);
  98. if (!ind_el) { return; }
  99.  
  100. var spacer_img = ind_el.firstChild;
  101. if (!spacer_img) { return; }
  102. post_el.indent_width = spacer_img.width;
  103. var header_el = post_el.querySelector(header_selector);
  104. var votebox_el = post_el.querySelector(votebox_selector);
  105. var votepad_el = null;
  106. var body_br_el = null;
  107. var body_comment_el = null;
  108. var body_pad_el = null;
  109. if (header_el) {
  110. post_el.header_el = header_el;
  111. votepad_el = document.createElement('span');
  112. votepad_el.style.display = 'none';
  113.  
  114. body_pad_el = document.createElement('div');
  115. body_pad_el.classList.add('comment');
  116.  
  117. var comhead_el = header_el.querySelector('.comhead');
  118.  
  119. if (votebox_el) {
  120. var icon_width = votebox_el.offsetWidth;
  121. votepad_el.style.width = icon_width;
  122.  
  123. if (comhead_el) {
  124. var comhead_width = comhead_el.offsetWidth;
  125. body_pad_el.style.width = '' + (comhead_width + icon_width + icon_padding_px) + 'px';
  126. }
  127. }
  128. header_el.insertBefore(votepad_el, header_el.firstChild);
  129.  
  130. body_br_el = header_el.parentElement.children[1];
  131. body_comment_el = header_el.parentElement.children[2];
  132. header_el.parentElement.appendChild(body_pad_el);
  133. }
  134. var toggle_btn = make_toggle_button();
  135. post_el.fold_self = function () {
  136. post_el.is_folded = true;
  137.  
  138. set_toggle_button_unfold(toggle_btn);
  139. hide(votebox_el);
  140. show_inline_block(votepad_el);
  141. hide(body_br_el);
  142. hide(body_comment_el);
  143. show_block(body_pad_el);
  144. };
  145. post_el.unfold_self = function () {
  146. post_el.is_folded = false;
  147.  
  148. set_toggle_button_fold(toggle_btn);
  149. show_block(votebox_el);
  150. hide(votepad_el);
  151. show_inline(body_br_el);
  152. show_block(body_comment_el);
  153. hide(body_pad_el);
  154. };
  155. post_el.fold_subtree = function () {
  156. each_subtree_post(post_el, function (post) { post.fold_self(); });
  157. };
  158. post_el.unfold_subtree = function () {
  159. each_subtree_post(post_el, function (post) { post.unfold_self(); });
  160. };
  161.  
  162. post_el.fold = function () {
  163. post_el.fold_self();
  164. post_el.fold_subtree();
  165. };
  166. post_el.unfold = function () {
  167. post_el.unfold_self();
  168. post_el.unfold_subtree();
  169. };
  170. post_el.is_folded = false;
  171. post_el.toggle_fold = function () {
  172. if (post_el.is_folded) {
  173. post_el.unfold();
  174. } else {
  175. post_el.fold();
  176. }
  177. };
  178.  
  179. var toggle_event = function (event) {
  180. post_el.toggle_fold();
  181. };
  182. toggle_btn.addEventListener('click', toggle_event, false);
  183.  
  184. var ctl_box = document.createElement('div');
  185. ctl_box.classList.add('foldctlbox');
  186. ctl_box.appendChild(toggle_btn);
  187. show_inline_block(ctl_box);
  188. header_el.appendChild(ctl_box);
  189. foldified_count = foldified_count + 1;
  190. }
  191. function setup_posts () {
  192. var posts = document.querySelectorAll(post_selector);
  193. /*console.log('found ' + posts.length + ' posts');*/
  194. if (posts) {
  195. Array.prototype.forEach.call(posts, foldify_post);
  196. }
  197. }
  198. setup_posts();
  199. /*console.log("foldified " + foldified_count + ' posts');*/
  200. })();