Guest User

Tampermonkey YouTube Subscriptions Listview layout Update2

a guest
Feb 3rd, 2026
307
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 6.91 KB | Software | 0 0
  1. // ==UserScript==
  2. // @name         YouTube Subscriptions – List Layout + Inline Descriptions (Fixed Flex)
  3. // @namespace    marty-listview
  4. // @version      1.2
  5. // @description  Horizontal list with 160px thumbs and inline descriptions.
  6. // @match        https://www.youtube.com/feed/subscriptions*
  7. // @run-at       document-end
  8. // ==/UserScript==
  9.  
  10. (function() {
  11.     'use strict';
  12.  
  13.     const THUMB_WIDTH = 160;
  14.  
  15.     const css = `
  16. /* Each item becomes a full-width row */
  17. ytd-rich-item-renderer.ytd-rich-grid-renderer {
  18.     display: block !important;
  19.     width: 100% !important;
  20.     padding: 0 !important;
  21.     margin-bottom: 16px !important;
  22.     margin-left: 16px !important;
  23.     border-bottom: 1px solid var(--yt-spec-10-percent-layer) !important;
  24. }
  25. ytd-rich-item-renderer.ytd-rich-shelf-renderer {
  26.     max-width: 15vw !important;
  27. }
  28. .shortsLockupViewModelHost {
  29.     width: 12vw !important;
  30. }
  31.  
  32. /* Remove clicking anywhere on the item from opening the video, only title and thumbnails should open video */
  33. .yt-lockup-view-model {
  34.     cursor: auto !important;
  35.     pointer-events: none;
  36. }
  37. .yt-lockup-view-model__content-image, .yt-lockup-metadata-view-model__title, .yt-lockup-metadata-view-model__menu-button {
  38.     pointer-events: all;
  39. }
  40.  
  41. /* THE REAL FLEX TARGET — THIS IS THE FIX */
  42. .yt-lockup-view-model.yt-lockup-view-model--vertical {
  43.     display: flex !important;
  44.     flex-direction: row !important;
  45.     align-items: flex-start !important;
  46.     gap: 16px !important;
  47. }
  48.  
  49. /* Thumbnail */
  50. .yt-lockup-view-model__content-image {
  51.     width: ${THUMB_WIDTH}px !important;
  52.     min-width: ${THUMB_WIDTH}px !important;
  53.     max-width: ${THUMB_WIDTH}px !important;
  54.     flex-shrink: 0 !important;
  55. }
  56.  
  57. .yt-lockup-view-model__content-image img {
  58.     width: 100% !important;
  59.     height: auto !important;
  60.     border-radius: 6px !important;
  61.     object-fit: cover !important;
  62. }
  63.  
  64. /* Text column */
  65. yt-lockup-metadata-view-model {
  66.     flex: 1 !important;
  67.     max-width: 100% !important;
  68. }
  69.  
  70. /* Title */
  71. .yt-lockup-metadata-view-model__title {
  72.     font-size: 1.15rem !important;
  73.     font-weight: 600 !important;
  74.     margin-bottom: 6px !important;
  75.     line-height: 1.3 !important;
  76.     white-space: normal !important;
  77. }
  78. .yt-lockup-metadata-view-model__text-container .yt-lockup-metadata-view-model__heading-reset .yt-lockup-metadata-view-model__title span.yt-core-attributed-string.yt-core-attributed-string--white-space-pre-wrap {
  79.     font-size: 1.75rem !important;
  80. }
  81.  
  82. /* Metadata */
  83. .marty-meta-inline {
  84.     font-size: 1.1rem !important;
  85.     color: var(--yt-spec-text-secondary) !important;
  86.     margin-bottom: 4px !important;
  87. }
  88.  
  89. /* Description */
  90. .marty-desc {
  91.     font-size: 1.5rem !important;
  92.     color: var(--yt-spec-text-secondary) !important;
  93.     line-height: 1.45 !important;
  94.     white-space: normal !important;
  95. }
  96.  
  97. .yt-lockup-view-model__metadata {
  98.     position: relative;
  99.     display: flex;
  100.     min-width: 0;
  101.     flex-direction: column;
  102.     width: 80%;
  103. }
  104.  
  105. /* put menu button after Avatar (for easier access) */
  106. .yt-lockup-metadata-view-model__avatar {
  107.     order: 0;
  108. }
  109. .yt-lockup-metadata-view-model__menu-button {
  110.     position: relative !important;
  111.     order: 1;
  112.     right: 0 !important;
  113. }
  114. .yt-lockup-metadata-view-model__text-container {
  115.     order: 2;
  116. }
  117. `;
  118.  
  119.     function extractVideoId(href) {
  120.         try {
  121.             const url = new URL(href, location.origin);
  122.             return url.searchParams.get('v');
  123.         } catch {
  124.             const m = href.match(/v=([^&]+)/);
  125.             return m ? m[1] : null;
  126.         }
  127.     }
  128.  
  129.         async function fetchDescription(videoId) {
  130.         if (!videoId) return '';
  131.         try {
  132.             const res = await fetch(`https://www.youtube.com/watch?v=${videoId}`, {
  133.                 credentials: 'same-origin'
  134.             });
  135.             const html = await res.text();
  136.             const match = html.match(/ytInitialPlayerResponse\s*=\s*(\{.*?\});/s);
  137.             if (!match) return '';
  138.             const json = JSON.parse(match[1]);
  139.             let desc = json?.videoDetails?.shortDescription || '';
  140.  
  141.             const sentences = desc.match(/[^.!?]+[.!?]+/g) || [];
  142.             desc = sentences.slice(0, 2).join(' ').trim();
  143.             return desc
  144.  
  145.             //return desc.replace(/\n+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 330) + (desc.length > 330 ? '…' : '');
  146.         } catch {
  147.             return '';
  148.         }
  149.     }
  150.  
  151.  
  152.     async function enhanceDescriptions() {
  153.         const items = document.querySelectorAll('ytd-rich-item-renderer yt-lockup-view-model');
  154.         if (!items.length) return;
  155.  
  156.         for (const lockup of items) {
  157.             if (lockup.dataset.martyDone === '1') continue;
  158.  
  159.             const thumbLink = lockup.querySelector('a.yt-lockup-view-model__content-image');
  160.             if (!thumbLink) continue;
  161.  
  162.             const href = new URL(thumbLink.getAttribute('href'), location.origin).href;
  163.             const videoId = extractVideoId(href);
  164.             if (!videoId) continue;
  165.  
  166.             const metaContainer =
  167.                 lockup.querySelector('.yt-lockup-metadata-view-model__text-container') ||
  168.                 lockup.querySelector('yt-lockup-metadata-view-model');
  169.  
  170.             if (!metaContainer) continue;
  171.  
  172.             const metaRows = lockup.querySelectorAll('.yt-content-metadata-view-model__metadata-row span');
  173.             const channel = metaRows?.[0]?.innerText?.trim() || '';
  174.             const views = metaRows?.[1]?.innerText?.trim() || '';
  175.             const timeAgo = metaRows?.[2]?.innerText?.trim() || '';
  176.             const metaLine = [channel, views, timeAgo].filter(Boolean).join(' • ');
  177.  
  178.             const metaDiv = document.createElement('div');
  179.             metaDiv.className = 'marty-meta-inline';
  180.             metaDiv.textContent = metaLine;
  181.  
  182.             const descDiv = document.createElement('div');
  183.             descDiv.className = 'marty-desc';
  184.             descDiv.textContent = '…';
  185.  
  186.             metaContainer.appendChild(metaDiv);
  187.             metaContainer.appendChild(descDiv);
  188.  
  189.             lockup.dataset.martyDone = '1';
  190.  
  191.             const desc = await fetchDescription(videoId);
  192.             descDiv.textContent = desc;
  193.         }
  194.     }
  195.  
  196.     function init() {
  197.         const ele = document.getElementById('fixSubscriptionListViewStyle');
  198.         if (window.location.pathname === '/feed/subscriptions' && !ele) {
  199.             const style = document.createElement('style');
  200.             style.textContent = css;
  201.             style.id = 'fixSubscriptionListViewStyle';
  202.             document.head.appendChild(style);
  203.             setTimeout(enhanceDescriptions, 800);
  204.             setTimeout(enhanceDescriptions, 2000);
  205.         } else if (window.location.pathname !== '/feed/subscriptions' && ele) {
  206.             ele.parentElement.removeChild(ele);
  207.         }
  208.     }
  209.  
  210.     window.addEventListener('yt-navigate-finish', init);
  211.     window.addEventListener('load', init);
  212.  
  213. })();
Advertisement
Add Comment
Please, Sign In to add comment