YouTube (New Design) | Compact Subscription Feed

Optimized YouTube Subscription feed list view with more information on less space

  1. // ==UserScript==
  2. // @name YouTube (New Design) | Compact Subscription Feed
  3. // @namespace de.sidneys.userscripts
  4. // @homepage https://gist.githubusercontent.com/sidneys/1aec97019cde4fe37619e36eb730b216/raw/
  5. // @version 1.0.0
  6. // @description Optimized YouTube Subscription feed list view with more information on less space
  7. // @author sidneys
  8. // @icon https://www.youtube.com/favicon.ico
  9. // @include http*://www.youtube.com/*
  10. // @require https://greasyfork.org/scripts/38888-greasemonkey-color-log/code/Greasemonkey%20%7C%20Color%20Log.js
  11. // @require https://greasyfork.org/scripts/38889-greasemonkey-waitforkeyelements-2018/code/Greasemonkey%20%7C%20waitForKeyElements%202018.js
  12. // @run-at document-end
  13. // @grant GM_addStyle
  14. // ==/UserScript==
  15.  
  16. /**
  17. * @default
  18. * @constant
  19. * @global
  20. */
  21. DEBUG = false;
  22.  
  23.  
  24. /**
  25. * @default
  26. * @constant
  27. */
  28. const urlPath = '/feed/subscriptions';
  29.  
  30.  
  31. /**
  32. * Inject Stylesheet
  33. */
  34. let injectStylesheet = () => {
  35. console.debug('injectStylesheet');
  36.  
  37. GM_addStyle(`
  38. /* ==========================================================================
  39. GLOBAL
  40. ========================================================================== */
  41.  
  42. .hide
  43. {
  44. opacity: 0 !important;
  45. }
  46.  
  47.  
  48. /* ==========================================================================
  49. LIST
  50. ========================================================================== */
  51.  
  52. /* Container
  53. ========================================================================== */
  54.  
  55. .layout-list div#content
  56. {
  57. position: relative !important;
  58. margin-top: 2% !important;
  59. width: 95% !important;
  60. }
  61.  
  62. .layout-list div.branded-page-v2-primary-col > div.yt-card
  63. {
  64. margin-top: 0 !important;
  65. }
  66.  
  67. /* Header
  68. ========================================================================== */
  69.  
  70. .layout-list ol.section-list > li:first-child div.feed-item-container
  71. {
  72. border-top: none !important;
  73. background-image: linear-gradient(to bottom, rgba(0,0,0,0.25) 50%, rgba(0,0,0,0.25)) !important;
  74. background-repeat: no-repeat !important;
  75. background-size: 100% 45px !important;
  76. }
  77.  
  78. .layout-list ol.section-list > li:first-child div.feed-item-container div.menu-container
  79. {
  80. margin-bottom: 20px !important;
  81. }
  82.  
  83. .layout-list ol.section-list > li:first-child div.feed-item-container div.shelf-title-table
  84. {
  85. display: table !important;
  86. width: 100% !important;
  87. }
  88.  
  89. .layout-list ol.section-list > li:first-child div.feed-item-container div.shelf-title-table h2
  90. {
  91. display: none !important;
  92. }
  93.  
  94. .layout-list ol.section-list > li:first-child div.feed-item-container div.shelf-title-table .menu-container
  95. {
  96. display: block !important;
  97. }
  98.  
  99. /* ==========================================================================
  100. ITEM
  101. ========================================================================== */
  102.  
  103. /* Stripes
  104. ========================================================================== */
  105.  
  106. .layout-list #contents.ytd-section-list-renderer > *.ytd-section-list-renderer:nth-child(odd)
  107. {
  108. background-color: rgba(0, 0, 0, 0.1) !important;
  109. }
  110.  
  111. /* Container
  112. ========================================================================== */
  113.  
  114. .layout-list ytd-shelf-renderer
  115. {
  116. border-bottom: 1px solid #5a5a5a !important;
  117. border-top: none !important;
  118. padding-bottom: 12px !important;
  119. padding-top: 12px !important;
  120. }
  121.  
  122. .layout-list .text-wrapper.ytd-video-renderer
  123. {
  124. max-width: none !important;
  125. }
  126.  
  127. .layout-list .grid-subheader.ytd-shelf-renderer
  128. {
  129. display: none !important;
  130. }
  131.  
  132. .layout-list #contents.ytd-shelf-renderer
  133. {
  134. margin-top: 0 !important;
  135. }
  136.  
  137. .layout-list ytd-expanded-shelf-contents-renderer
  138. {
  139. margin-bottom: 0 !important;
  140. }
  141.  
  142. /* Thumbnail
  143. ========================================================================== */
  144.  
  145. .layout-list ytd-thumbnail
  146. {
  147. width: 72px !important;
  148. height: auto !important;
  149. margin-left: 16px !important;
  150. }
  151.  
  152. .layout-list ytd-thumbnail-overlay-time-status-renderer
  153. {
  154. color: rgb(118, 118, 118) !important;
  155. font-size: 12px !important;
  156. background-color: transparent !important;
  157. bottom: 15% !important;
  158. }
  159.  
  160. .layout-list ytd-thumbnail-overlay-resume-playback-renderer:before
  161. {
  162. top: -37px !important;
  163. width: 72px !important;
  164. height: 41px !important;
  165. line-height: 36px !important;
  166. font-size: 1em !important;
  167. }
  168.  
  169. .layout-list ytd-thumbnail-overlay-resume-playback-renderer:after
  170. {
  171. font-size: 0.75rem !important;
  172. font-weight: !important;
  173. }
  174.  
  175. /* Title
  176. ========================================================================== */
  177.  
  178. /*.layout-list #title-wrapper.ytd-video-renderer
  179. {
  180. padding: 4px 0 !important;
  181. display: block !important;
  182. white-space: nowrap !important;
  183. overflow: hidden !important;
  184. text-overflow: ellipsis !important;
  185. max-width: none !important;
  186. margin-right: 50px !important;
  187. }*/
  188.  
  189. .layout-list #video-title.ytd-video-renderer
  190. {
  191. font-size: 13px !important;
  192. line-height: 1.3em !important;
  193. font-weight: 500 !important;
  194. display: inline !important;
  195. white-space: nowrap !important;
  196. text-overflow: ellipsis !important;
  197. overflow: hidden !important;
  198. background: none !important;
  199. }
  200.  
  201. .layout-list .badge-style-type-verified.ytd-badge-supported-renderer
  202. {
  203. margin: 0 0 0 5px !important;
  204. }
  205.  
  206. /* Channel
  207. ========================================================================== */
  208.  
  209. .layout-list a.yt-simple-endpoint.yt-formatted-string
  210. {
  211. color: #1ea4e8 !important;
  212. }
  213.  
  214. /* Description
  215. ========================================================================== */
  216.  
  217. .layout-list #description-text.ytd-video-renderer
  218. {
  219. max-width: 70% !important;
  220. margin-top: 4px !important;
  221. display: none !important;
  222. }
  223.  
  224. /* Badges
  225. ========================================================================== */
  226.  
  227. .layout-list ytd-video-meta-block #byline-container
  228. {
  229. display: none !important;
  230. }
  231.  
  232. /* Time
  233. ========================================================================== */
  234.  
  235. .layout-list span.timestamp.ytd-video-meta-block
  236. {
  237. float: right !important;
  238. position: absolute !important;
  239. right: 0 !important;
  240. margin-right: 16px !important;
  241. }
  242.  
  243. .layout-list span.timestamp.ytd-video-meta-block br
  244. {
  245. display: none !important;
  246. }
  247. `);
  248. };
  249.  
  250. /**
  251. * Feed in list / grid layout?
  252. * @returns {Boolean} - Page layout
  253. */
  254. let isListLayout = () => !Boolean(document.querySelector('div.multirow-shelf'));
  255.  
  256. /**
  257. * Adapt feed item element
  258. * @param {HTMLElement} element - Feed item container
  259. */
  260. let adaptItemLayout = (element) => {
  261. console.debug('adaptItemLayout');
  262.  
  263. // DOM
  264. const titleElement = element.querySelector('#video-title.ytd-video-renderer');
  265. const verifiedElement = element.querySelector('#byline-container.ytd-video-meta-block yt-icon.ytd-badge-supported-renderer');
  266. const channelElement = element.querySelector('a.yt-simple-endpoint.yt-formatted-string');
  267. const videoMetaDivList = element.querySelectorAll('div.yt-lockup-meta');
  268. const videoBadgesDiv = element.querySelector('div.yt-lockup-badges');
  269. const videoLiveSpan = element.querySelector('span.yt-badge-live');
  270. const videoReminderSpan = element.querySelector('span.yt-uix-livereminder');
  271.  
  272. // Create Separator
  273. let elementSeparator = document.createElement('span');
  274. elementSeparator.innerText = ' | ';
  275.  
  276. // Title
  277. if (titleElement) {
  278. titleElement.insertBefore(elementSeparator, titleElement.firstChild);
  279. }
  280.  
  281. // Verified
  282. if (verifiedElement) {
  283. titleElement.insertBefore(verifiedElement, titleElement.firstChild);
  284. verifiedElement.style.marginLeft = '4px';
  285. }
  286.  
  287. // Channel
  288. if (channelElement && titleElement) {
  289. titleElement.insertBefore(channelElement, titleElement.firstChild);
  290. }
  291.  
  292. // Live Badge
  293. if (videoLiveSpan && videoBadgesDiv && videoMetaDivList && videoMetaDivList[0] && videoMetaDivList[0].style) {
  294. videoBadgesDiv.style.display = 'inline-block';
  295. videoMetaDivList[0].style.display = 'inline-block';
  296. }
  297.  
  298. // Reminder Badge
  299. if (videoReminderSpan && videoBadgesDiv && videoMetaDivList && videoMetaDivList[0]) {
  300. videoBadgesDiv.style.display = 'inline-block';
  301. videoMetaDivList.forEach((videoMetaDiv) => {
  302. videoMetaDiv.style.display = 'inline-block';
  303. });
  304. }
  305. };
  306.  
  307. /**
  308. * Adapt timestamp element
  309. * @param {HTMLElement} element - Timestamp element
  310. */
  311. let adaptTimestampLayout = (element) => {
  312. console.debug('adaptTimestampLayout');
  313.  
  314. // DOM
  315. const itemElement = element.closest('ytd-shelf-renderer');
  316. const videoMetadataLineDiv = itemElement.querySelector('#metadata-line.ytd-video-meta-block');
  317.  
  318. // Create new timestamp element
  319. const timestampElement = document.createElement('span');
  320. timestampElement.className = 'timestamp ytd-video-meta-block';
  321. timestampElement.innerText = element.innerText;
  322. videoMetadataLineDiv.appendChild(timestampElement);
  323.  
  324. // Remove old timestamp element
  325. element.closest('ytd-thumbnail-overlay-time-status-renderer').parentNode.removeChild(element.closest('ytd-thumbnail-overlay-time-status-renderer'));
  326. };
  327.  
  328.  
  329. /**
  330. * Init
  331. */
  332. let init = () => {
  333. console.info('init');
  334.  
  335. // Check URL
  336. if (!location.pathname.startsWith(urlPath)) { return; }
  337.  
  338. // Add Stylesheet
  339. injectStylesheet();
  340.  
  341. // Set layout helper class (document.body)
  342. if (isListLayout()) {
  343. document.body.classList.add('layout-list');
  344. } else {
  345. document.body.classList.remove('layout-list');
  346. }
  347.  
  348. // Watch items
  349. waitForKeyElements('.layout-list ytd-shelf-renderer', (item) => {
  350. adaptItemLayout(item);
  351. });
  352.  
  353. // Watch timestamps
  354. waitForKeyElements('span.ytd-thumbnail-overlay-time-status-renderer', (item) => {
  355. adaptTimestampLayout(item);
  356. });
  357. };
  358.  
  359.  
  360. /**
  361. * @listens window:Event#load
  362. */
  363. window.addEventListener('load', () => {
  364. console.debug('window#load');
  365.  
  366. init();
  367. });
  368.  
  369. /**
  370. * @listens window:Event#spfdone
  371. */
  372. window.addEventListener('spfdone', () => {
  373. console.debug('window#spfdone');
  374.  
  375. init();
  376. });