Reddit Multi Column

Multi column layout for reddit redesign

  1. // ==UserScript==
  2. // @name Reddit Multi Column
  3. // @namespace https://gist.github.com/c6p/463892bb243f611f2a3cfa4268c6435e
  4. // @version 0.2.6
  5. // @description Multi column layout for reddit redesign
  6. // @author Can Altıparmak
  7. // @homepageURL https://gist.github.com/c6p/463892bb243f611f2a3cfa4268c6435e
  8. // @match https://www.reddit.com/*
  9. // @match https://new.reddit.com/*
  10. // @grant none
  11. // ==/UserScript==
  12. /* jshint esversion: 6 */
  13.  
  14. (function() {
  15. 'use strict';
  16. const MIN_WIDTH = 400;
  17. const COLUMNS = 4;
  18. let columns = COLUMNS;
  19. let cleanup = null;
  20.  
  21. let parent = null;
  22. const cardIcon = () => document?.querySelector('shreddit-sort-dropdown[header-text="View"]')?.shadowRoot?.querySelector('svg');
  23. const shouldClean =(icon) => icon === undefined ? false : icon.getAttribute('icon-name') !== "view-card-outline";
  24. cleanup = shouldClean()
  25.  
  26. let postMap = new Map()
  27.  
  28. const indexOfSmallest = function (a) {
  29. let lowest = 0;
  30. for (let i = 1; i<a.length; i++) {
  31. if (a[i] < (a[lowest]-1)) lowest = i;
  32. }
  33. return lowest;
  34. };
  35.  
  36. const makeLayout = function(changes=[]) {
  37. if (cleanup) return;
  38. if (!parent) return;
  39.  
  40. if (parent.style.position !== "relative") {
  41. document.querySelector("main").style.maxWidth = "100%";
  42. const mainContainer = document.querySelector("div.main-container");
  43. mainContainer.className = [...mainContainer.classList].filter(c => !c.includes(":grid-cols-")).join(" ") // make wide
  44. document.querySelector("div.subgrid-container").classList.remove("m:w-[1120px]") // make wide
  45. document.getElementById("right-sidebar-container").style.display = "none" // hide sidebar
  46. parent.style.position = "relative"
  47. }
  48.  
  49. const cols = Math.floor(parent.offsetWidth / MIN_WIDTH);
  50. columns = cols;
  51. const WIDTH = Math.floor((100-columns)/columns);
  52.  
  53.  
  54. const nodes = [...parent.querySelectorAll("article, shreddit-ad-post, faceplate-partial").values()]
  55. for (const article of nodes) {
  56. const key = article.ariaLabel
  57. if (key === null) /* faceplate-partial */ {
  58. } else if (key in postMap) {
  59. const post = postMap[key]
  60. if (post.height !== article.offsetHeight) {
  61. post.height = article.offsetHeight
  62. }
  63. } else {
  64. postMap.set(key, {height:article.offsetHeight, col:0, top:0})
  65. }
  66. }
  67.  
  68. let tops = Array(columns).fill(0);
  69. for (const post of postMap.values()) {
  70. post.col = indexOfSmallest(tops)
  71. post.top = tops[post.col]
  72. tops[post.col] += post.height
  73. }
  74. const height = Math.max(...tops)
  75. if (height) {
  76. parent.style.height = height + 500 + "px"
  77. }
  78.  
  79. for (const article of nodes) {
  80. const key = article.ariaLabel
  81. const {col, top} = postMap.get(key) ?? {col:0, top:tops[0]}
  82. article.setAttribute("style", cleanup ? "" : `position:absolute; width:${WIDTH}%; top:${top}px; left:${col*(WIDTH+1)}%`)
  83. }
  84.  
  85. for (const batch of parent.querySelectorAll("faceplate-batch").values()) {
  86. if (!batch.style.height) {
  87. batch.style.height = [...batch.childNodes].reduce((height,c) => height + c.clientHeight, 0) + "px"
  88. }
  89. }
  90. };
  91.  
  92. const setLayout = function(changes, observer) {
  93. const c = shouldClean(cardIcon());
  94. if (c !== cleanup) {
  95. cleanup = c;
  96. window.requestAnimationFrame(makeLayout)
  97. }
  98. };
  99.  
  100. const requestLayout = () => window.requestAnimationFrame(makeLayout)
  101. const pageChange = new MutationObserver(requestLayout);
  102. window.addEventListener('resize', requestLayout);
  103. window.addEventListener('scrollend', requestLayout);
  104. const layoutSwitch = new MutationObserver(setLayout);
  105.  
  106. const watch = function(changes, observer) {
  107. postMap = new Map()
  108. parent = document.querySelector("article + hr + faceplate-partial").parentNode
  109. if (parent === null) return;
  110. pageChange.observe(parent, {childList: true});
  111. const timeout = setTimeout(() => {
  112. const icon = cardIcon();
  113. if (icon !== undefined) {
  114. clearTimeout(timeout);
  115. layoutSwitch.observe(icon, {attributes: true});
  116. }
  117. })
  118. window.requestAnimationFrame(makeLayout);
  119. };
  120.  
  121. const apply = new MutationObserver(watch);
  122. const app = document.querySelector("shreddit-app")
  123. apply.observe(app, {attributes: true});
  124. watch();
  125. })();