您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Force YouTube to show compact grid (max 6 videos per row) rather than "slim" grid (max 3 videos per row)
当前为
// ==UserScript== // @name YouTube - Force Compact Grid (increases max # videos per row) // @namespace https://gist.github.com/lbmaian/8c6961584c0aebf41ee7496609f60bc3 // @version 0.2 // @description Force YouTube to show compact grid (max 6 videos per row) rather than "slim" grid (max 3 videos per row) // @author lbmaian // @match https://www.youtube.com/* // @exclude https://www.youtube.com/embed/* // @icon https://www.youtube.com/favicon.ico // @run-at document-start // @grant none // ==/UserScript== (function() { 'use strict'; const DEBUG = false; const logContext = '[YouTube - Force Compact Grid]'; var debug; if (DEBUG) { debug = function(...args) { console.debug(logContext, ...args); }; } else { debug = function() {}; } function log(...args) { console.log(logContext, ...args); } function info(...args) { console.info(logContext, ...args); } function warn(...args) { console.warn(logContext, ...args); } function error(...args) { console.error(logContext, ...args); } function updateResponseData(response, label) { const tabs = response?.contents?.twoColumnBrowseResultsRenderer?.tabs; if (DEBUG) { debug(label, 'contents.twoColumnBrowseResultsRenderer.tabs (snapshot)', window.structuredClone(tabs)); } if (tabs) { for (const tab of tabs) { const tabRenderer = tab.tabRenderer; if (tabRenderer) { const richGridRenderer = tabRenderer.content?.richGridRenderer; if (richGridRenderer && (!richGridRenderer.style || richGridRenderer.style == 'RICH_GRID_STYLE_SLIM')) { log(label, 'tab', tabRenderer.title ?? tabRenderer.tabIdentifier, 'tabRenderer.content.richGridRenderer.style:', richGridRenderer.style, '=> RICH_GRID_STYLE_COMPACT'); richGridRenderer.style = 'RICH_GRID_STYLE_COMPACT'; } } } } } // Note: Both of the following commented-out event listeners are too late: // ytd-app's own yt-page-data-fetched event listener (onYtPageDataFetched) already fires // by the time our own yt-page-data-fetched event listener fires, // and yt-page-data-fetched fires before yt-navigate-finish fires // document.addEventListener('yt-page-data-fetched', evt => { // debug('Navigated to', evt.detail.pageData.url); // debug(evt); // updateResponseData(evt.detail.pageData.response, 'yt-page-data-fetched pageData.response'); // }); // document.addEventListener('yt-navigate-finish', evt => { // debug('Navigated to', evt.detail.response.url); // debug(evt); // updateResponseData(evt.detail.response.response, 'yt-navigate-finish response.response'); // }); // yt-page-data-fetched event fires on both new page load and channel tab change // Need to hook into ytd-app's ytd-app's own yt-page-data-fetched event listener (onYtPageDataFetched), // so that we can modify the data before that event listener fires function setupYtdApp(ytdApp) { const origOnYtPageDataFetched = ytdApp.onYtPageDataFetched; ytdApp.onYtPageDataFetched = function(evt, detail) { debug('Navigated to', detail.pageData.url); debug(evt); updateResponseData(evt.detail.pageData.response, 'yt-page-data-fetched pageData.response'); return origOnYtPageDataFetched.call(this, evt, detail); }; log('ytd-app onYtPageDataFetched hook set up'); } // Wait for ytd-app element to exist AND for its prototype to be populated with the onYtPageDataFetched method const ytdApp = document.getElementsByTagName('ytd-app')[0]; if (ytdApp && ytdApp.onYtPageDataFetched) { debug('ytd-app immediately found', ytdApp); setupYtdApp(ytdApp); } else { new MutationObserver((records, observer) => { const ytdApp = document.getElementsByTagName('ytd-app')[0]; if (ytdApp && ytdApp.onYtPageDataFetched) { observer.disconnect(); debug('ytd-app found', ytdApp); setupYtdApp(ytdApp); } }).observe(document, { childList: true, subtree: true, }); } // Note: updating ytInitialData may not be necessary, since yt-page-data-fetched also fires for new page load, // and in that case, the event's detail.pageData.response is the same object as ytInitialData, // but DOMContentLoaded sometimes fires before ytd-app's onYtPageDataFetched fires (or rather, before we can hook into it), // so this is done just in case document.addEventListener('DOMContentLoaded', evt => { debug('ytInitialData', window.ytInitialData); updateResponseData(window.ytInitialData, 'ytInitialData'); }); })();