您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds 150+ dropdown menus with a total of 1000+ entries to the header navigation bar for one-click access to just about any page on the entire website. See README for details.
// ==UserScript== // @name Trakt.tv | Nested Header Navigation Menus // @description Adds 150+ dropdown menus with a total of 1000+ entries to the header navigation bar for one-click access to just about any page on the entire website. See README for details. // @version 1.0.2 // @namespace https://github.com/Fenn3c401 // @author Fenn3c401 // @license GPL-3.0-or-later // @homepageURL https://github.com/Fenn3c401/Trakt.tv-Userscript-Collection#readme // @supportURL https://github.com/Fenn3c401/Trakt.tv-Userscript-Collection/issues // @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMCAwIDQ4IDQ4Ij4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLmNscy0xIHsKICAgICAgICBmaWxsOiB1cmwoI3JhZGlhbC1ncmFkaWVudCk7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxyYWRpYWxHcmFkaWVudCBpZD0icmFkaWFsLWdyYWRpZW50IiBjeD0iNDguNDYiIGN5PSItLjk1IiBmeD0iNDguNDYiIGZ5PSItLjk1IiByPSI2NC44NCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM5ZjQyYzYiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuMjciIHN0b3AtY29sb3I9IiNhMDQxYzMiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuNDIiIHN0b3AtY29sb3I9IiNhNDNlYmIiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuNTMiIHN0b3AtY29sb3I9IiNhYTM5YWQiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuNjQiIHN0b3AtY29sb3I9IiNiNDMzOWEiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuNzMiIHN0b3AtY29sb3I9IiNjMDJiODEiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuODIiIHN0b3AtY29sb3I9IiNjZjIwNjEiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIuOSIgc3RvcC1jb2xvcj0iI2UxMTQzYyIvPgogICAgICA8c3RvcCBvZmZzZXQ9Ii45NyIgc3RvcC1jb2xvcj0iI2Y1MDYxMyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9InJlZCIvPgogICAgPC9yYWRpYWxHcmFkaWVudD4KICA8L2RlZnM+CiAgPGcgaWQ9Il94MkRfLXByb2R1Y3Rpb24iPgogICAgPGcgaWQ9ImxvZ29tYXJrLnNxdWFyZS5ncmFkaWVudCI+CiAgICAgIDxwYXRoIGlkPSJiYWNrZ3JvdW5kIiBjbGFzcz0iY2xzLTEiIGQ9Ik00OCwxMS4yNnYyNS40N2MwLDYuMjItNS4wNSwxMS4yNy0xMS4yNywxMS4yN0gxMS4yNmMtNi4yMiwwLTExLjI2LTUuMDUtMTEuMjYtMTEuMjdWMTEuMjZDMCw1LjA0LDUuMDQsMCwxMS4yNiwwaDI1LjQ3YzMuMzIsMCw2LjMsMS40Myw4LjM3LDMuNzIuNDcuNTIuODksMS4wOCwxLjI1LDEuNjguMTguMjkuMzQuNTkuNS44OS4zMy42OC42LDEuMzkuNzksMi4xNC4xLjM3LjE4Ljc2LjIzLDEuMTUuMDkuNTQuMTMsMS4xMS4xMywxLjY4WiIvPgogICAgICA8ZyBpZD0iY2hlY2tib3giPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMiIgZD0iTTEzLjYyLDE3Ljk3bDcuOTIsNy45MiwxLjQ3LTEuNDctNy45Mi03LjkyLTEuNDcsMS40N1pNMjguMDEsMzIuMzdsMS40Ny0xLjQ2LTIuMTYtMi4xNiwyMC4zMi0yMC4zMmMtLjE5LS43NS0uNDYtMS40Ni0uNzktMi4xNGwtMjIuNDYsMjIuNDYsMy42MiwzLjYyWk0xMi45MiwxOC42N2wtMS40NiwxLjQ2LDE0LjQsMTQuNCwxLjQ2LTEuNDctNC4zMi00LjMxTDQ2LjM1LDUuNGMtLjM2LS42LS43OC0xLjE2LTEuMjUtMS42OGwtMjMuNTYsMjMuNTYtOC42Mi04LjYxWk00Ny44Nyw5LjU4bC0xOS4xNywxOS4xNywxLjQ3LDEuNDYsMTcuODMtMTcuODN2LTEuMTJjMC0uNTctLjA0LTEuMTQtLjEzLTEuNjhaTTI1LjE2LDIyLjI3bC03LjkyLTcuOTItMS40NywxLjQ3LDcuOTIsNy45MiwxLjQ3LTEuNDdaTTQxLjMyLDM1LjEyYzAsMy40Mi0yLjc4LDYuMi02LjIsNi4ySDEyLjg4Yy0zLjQyLDAtNi4yLTIuNzgtNi4yLTYuMlYxMi44OGMwLTMuNDIsMi43OC02LjIxLDYuMi02LjIxaDIwLjc4di0yLjA3SDEyLjg4Yy00LjU2LDAtOC4yOCwzLjcxLTguMjgsOC4yOHYyMi4yNGMwLDQuNTYsMy43MSw4LjI4LDguMjgsOC4yOGgyMi4yNGM0LjU2LDAsOC4yOC0zLjcxLDguMjgtOC4yOHYtMy41MWgtMi4wN3YzLjUxWiIvPjwhLS0gNDVkMjM4NWQzYWFjYmI1OTMyNmEzODYxNDljNWE4NzggLS0+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPg== // @match https://trakt.tv/* // @run-at document-start // @grant unsafeWindow // @grant GM_addStyle // ==/UserScript== /* README > Inspired by sergeyhist's [Trakt.tv Hidden Items](https://github.com/sergeyhist/trakt-scripts/blob/main/Legacy/trakt-hidden.user.js) userscript. */ 'use strict'; const menuTemplates = { progressSorting: (hrefPrefix, [anchorIndex = 1, ...submenuAnchorIndexes] = []) => ({ hrefPrefix, entries: ((arr) => arr.with(anchorIndex, { ...arr.at(anchorIndex), anchor: true }))([ { text: 'SORT BY' }, { text: 'Watched Date', href: '/added' }, { text: 'Completion %', href: '/completed' }, { text: 'Episodes Left', href: '/episodes' }, { text: 'Time Left', href: '/time' }, { text: 'Plays', href: '/plays' }, { text: 'Release Date', href: '/released' }, { text: 'Premiere Date', href: '/premiered' }, { text: 'Title', href: '/title' }, { text: 'Popularity', href: '/popularity' }, { text: 'Episode Runtime', href: '/runtime' }, { text: 'Total Runtime', href: '/total-runtime' }, { text: 'Random', href: '/random' }, ]), }), ratingSelection: (hrefPrefix, [anchorIndex = 1, ...submenuAnchorIndexes] = []) => ({ hrefPrefix, entries: ((arr) => arr.with(anchorIndex, { ...arr.at(anchorIndex), anchor: true }))([ { text: 'RATING' }, { text: 'All Ratings', href: '/all', submenu: { hrefPrefix: hrefPrefix + '/all', entries: [ { text: 'SORT BY' }, { text: 'Rated Date', href: '/added', anchor: true }, { text: 'Rating', href: '/rating' }, ], }}, { text: '10 - Totally Ninja!', href: '/10' }, { text: '9 - Superb', href: '/9' }, { text: '8 - Great', href: '/8' }, { text: '7 - Good', href: '/7' }, { text: '6 - Fair', href: '/6' }, { text: '5 - Meh', href: '/5' }, { text: '4 - Poor', href: '/4' }, { text: '3 - Bad', href: '/3' }, { text: '2 - Terrible', href: '/2' }, { text: '1 - Weak Sauce :(', href: '/1' }, ]), }), listsViewSorting: (hrefPrefix, [anchorIndex = 1, ...submenuAnchorIndexes] = []) => ({ hrefPrefix, entries: ((arr) => arr.with(anchorIndex, { ...arr.at(anchorIndex), anchor: true }))([ { text: 'SORT BY' }, { text: 'Rank', href: 'rank' }, { text: 'Updated Date', href: 'updated' }, { text: 'Title', href: 'title' }, { text: 'Likes', href: 'likes' }, { text: 'Comments', href: 'comments' }, { text: 'Items', href: 'items' }, { text: 'Random', href: 'random' }, ]), }), listSorting: (hrefPrefix, [anchorIndex = 1, ...submenuAnchorIndexes] = []) => ({ hrefPrefix, entries: ((arr) => arr.with(anchorIndex, { ...arr.at(anchorIndex), anchor: true }))([ { text: 'SORT BY' }, { text: 'Rank', href: 'rank' }, { text: 'Added Date', href: 'added' }, { text: 'Title', href: 'title' }, { text: 'Release Date', href: 'released' }, { text: 'Runtime', href: 'runtime' }, { text: 'Popularity', href: 'popularity' }, { text: 'Random', href: 'random' }, {}, { text: 'Trakt Percentage', href: 'percentage' }, { text: 'Trakt Votes', href: 'votes' }, {}, { text: 'My Rating', href: 'my_rating' }, { text: 'Watched Date', href: 'watched' }, { text: 'Collected Date', href: 'collected' }, ]), }), commentType: (hrefPrefix, [anchorIndex = 1, ...submenuAnchorIndexes] = []) => ({ hrefPrefix, entries: ((arr) => arr.with(anchorIndex, { ...arr.at(anchorIndex), anchor: true }))([ { text: 'TYPE' }, { text: 'All Types', href: '/all', submenu: menuTemplates.commentSorting(hrefPrefix + '/all', submenuAnchorIndexes) }, { text: 'Movies', href: '/movies', submenu: menuTemplates.commentSorting(hrefPrefix + '/movies', submenuAnchorIndexes) }, { text: 'Shows', href: '/shows', submenu: menuTemplates.commentSorting(hrefPrefix + '/shows', submenuAnchorIndexes) }, { text: 'Seasons', href: '/seasons', submenu: menuTemplates.commentSorting(hrefPrefix + '/seasons', submenuAnchorIndexes) }, { text: 'Episodes', href: '/episodes', submenu: menuTemplates.commentSorting(hrefPrefix + '/episodes', submenuAnchorIndexes) }, { text: 'Lists', href: '/lists', submenu: menuTemplates.commentSorting(hrefPrefix + '/lists', submenuAnchorIndexes) }, ]), }), commentSorting: (hrefPrefix, [anchorIndex = 1, ...submenuAnchorIndexes] = []) => ({ hrefPrefix, entries: ((arr) => arr.with(anchorIndex, { ...arr.at(anchorIndex), anchor: true }))([ { text: 'SORT BY' }, { text: 'Added Date', href: '/added' }, { text: 'Likes <em>(30 Days)</em>', href: '/likes_30' }, { text: 'Likes <em>(All Time)</em>', href: '/likes' }, { text: 'Replies <em>(30 Days)</em>', href: '/replies_30' }, { text: 'Replies <em>(All Time)</em>', href: '/replies' }, { text: 'Plays', href: '/plays' }, { text: 'Rating', href: '/rating' }, ]), }), hiddenItemsSorting: (hrefPrefix, [anchorIndex = 1, ...submenuAnchorIndexes] = []) => ({ hrefPrefix, entries: ((arr) => arr.with(anchorIndex, { ...arr.at(anchorIndex), anchor: true }))([ { text: 'SORT BY' }, { text: 'Title', href: '/title' }, { text: 'Date', href: '/data' }, ]), }), showsMoviesCatTimePeriod: (hrefPrefix, [anchorIndex = 4, ...submenuAnchorIndexes] = []) => ({ hrefPrefix, entries: ((arr) => arr.with(anchorIndex, { ...arr.at(anchorIndex), anchor: true }))([ { text: 'PERIOD' }, { text: 'Day', href: '/daily' }, { text: 'Week', href: '/weekly' }, { text: 'Month', href: '/monthly' }, { text: 'All Time', href: '/all' }, ]), }), }; const menus = { '.btn-profile a[href$="/history"]': { hrefPrefix: '/users/me/history', entries: [ { text: 'TYPE' }, { text: 'All Types', href: '/all', anchor: true }, { text: 'Movies', href: '/movies' }, { text: 'Shows', href: '/shows' }, { text: 'Episodes', href: '/episodes' }, ], }, '.btn-profile a[href$="/library"]': { hrefPrefix: '/users/me/library', entries: [ { text: 'TYPE' }, { text: 'All Types', href: '/all', anchor: true }, { text: 'Movies', href: '/movies' }, { text: 'Shows', href: '/shows' }, { text: 'Episodes', href: '/episodes' }, ], }, '.btn-profile a[href$="/progress"]': { hrefPrefix: '/users/me/progress', entries: [ { text: 'SHOWS' }, { text: 'Watched', href: '/watched', anchor: true, submenu: menuTemplates.progressSorting('/users/me/progress/watched') }, { text: 'Dropped', href: '/dropped', submenu: menuTemplates.progressSorting('/users/me/progress/dropped') }, { text: 'Library', href: '/library', submenu: menuTemplates.progressSorting('/users/me/progress/library') }, ...(unsafeWindow.userscriptPlaybackProgressManager ? [ {}, { text: 'PLAYBACK' }, { text: 'All Types', href: '/playback' }, { text: 'Movies', href: '/playback/movies' }, { text: 'Episodes', href: '/playback/episodes' }, ] : []), ], }, '.btn-profile a[href$="/ratings"]': { hrefPrefix: '/users/me/ratings', entries: [ { text: 'TYPE' }, { text: 'All Types', href: '/all', anchor: true, submenu: menuTemplates.ratingSelection('/users/me/ratings/all') }, { text: 'Movies', href: '/movies', submenu: menuTemplates.ratingSelection('/users/me/ratings/movies') }, { text: 'Shows', href: '/shows', submenu: menuTemplates.ratingSelection('/users/me/ratings/shows') }, { text: 'Seasons', href: '/seasons', submenu: menuTemplates.ratingSelection('/users/me/ratings/seasons') }, { text: 'Episodes', href: '/episodes', submenu: menuTemplates.ratingSelection('/users/me/ratings/episodes') }, ], }, '.btn-profile a[href$="/lists"]': { hrefPrefix: '/users/me/lists', entries: [ { text: 'Watchlist', href: '/users/me/watchlist', useHrefPrefix: false, submenu: { hrefPrefix: '/users/me/watchlist?display=', entries: [ { text: 'TYPE' }, { text: 'All Types', href: '/users/me/watchlist', useHrefPrefix: false, anchor: true, submenu: menuTemplates.listSorting('/users/me/watchlist?sort=', [3]) }, { text: 'Movies', href: 'movie', submenu: menuTemplates.listSorting('/users/me/watchlist?display=movie&sort=', [3]) }, { text: 'Shows', href: 'show', submenu: menuTemplates.listSorting('/users/me/watchlist?display=show&sort=', [3]) }, { text: 'Seasons', href: 'season', submenu: menuTemplates.listSorting('/users/me/watchlist?display=season&sort=', [3]) }, { text: 'Episodes', href: 'episode', submenu: menuTemplates.listSorting('/users/me/watchlist?display=episode&sort=', [3]) }, ], }}, { text: 'Favorites', href: '/users/me/favorites', useHrefPrefix: false, submenu: { hrefPrefix: '/users/me/favorites?display=', entries: [ { text: 'TYPE' }, { text: 'All Types', href: '/users/me/favorites', useHrefPrefix: false, anchor: true, submenu: menuTemplates.listSorting('/users/me/favorites?sort=', [3]) }, { text: 'Movies', href: 'movie', submenu: menuTemplates.listSorting('/users/me/favorites?display=movie&sort=', [3]) }, { text: 'Shows', href: 'show', submenu: menuTemplates.listSorting('/users/me/favorites?display=show&sort=', [3]) }, ], }}, {}, { text: 'YOUR LISTS' }, { text: 'Personal Lists', href: '', submenu: menuTemplates.listsViewSorting('/users/me/lists?sort=') }, { text: 'Collaborations', href: '/collaborations', submenu: menuTemplates.listsViewSorting('/users/me/lists/collaborations?sort=') }, {}, { text: 'LIKED LISTS' }, { text: 'Personal Lists', href: '/liked', submenu: menuTemplates.listsViewSorting('/users/me/lists/liked?sort=') }, { text: 'Official Lists', href: '/liked/official', submenu: menuTemplates.listsViewSorting('/users/me/lists/liked/official?sort=') }, ], }, '.btn-profile a[href$="/comments"]': { hrefPrefix: '/users/me/comments', entries: [ { text: 'YOUR COMMENTS' }, { text: 'All Comments', href: '/all', anchor: true, submenu: menuTemplates.commentType('/users/me/comments/all') }, { text: 'Reviews', href: '/reviews', submenu: menuTemplates.commentType('/users/me/comments/reviews') }, { text: 'Shouts', href: '/shouts', submenu: menuTemplates.commentType('/users/me/comments/shouts') }, { text: 'Replies', href: '/replies', submenu: menuTemplates.commentType('/users/me/comments/replies') }, {}, { text: 'LIKED COMMENTS' }, { text: 'All Comments', href: '/liked/all', submenu: menuTemplates.commentType('/users/me/comments/liked/all', [-1, -1]) }, { text: 'Reviews', href: '/liked/reviews', submenu: menuTemplates.commentType('/users/me/comments/liked/reviews', [-1, -1]) }, { text: 'Shouts', href: '/liked/shouts', submenu: menuTemplates.commentType('/users/me/comments/liked/shouts', [-1, -1]) }, { text: 'Replies', href: '/liked/replies', submenu: menuTemplates.commentType('/users/me/comments/liked/replies', [-1, -1]) }, ], }, '.btn-profile a[href$="/notes"]': { hrefPrefix: '/users/me/notes', entries: [ { text: 'All Types', href: '/all' }, {}, { text: 'MEDIA ITEMS' }, { text: 'Movies', href: '/movies' }, { text: 'Shows', href: '/shows' }, { text: 'Seasons', href: '/seasons' }, { text: 'Episodes', href: '/episodes' }, { text: 'People', href: '/people' }, {}, { text: 'YOUR ACTIVITIES' }, { text: 'History', href: '/history' }, { text: 'Library', href: '/collection' }, // TODO switch to /library once /users/me/notes/library works { text: 'Ratings', href: '/ratings' }, ], }, '.btn-profile a[href$="/network"]': { hrefPrefix: '/users/me/network', entries: [ { text: 'Following', href: '/following/added' }, { text: 'Following <em>(Pending)</em>', href: '/following_pending/added' }, { text: 'Followers', href: '/followers/added' }, ], }, '.btn-profile a[href="/widgets"]': { hrefPrefix: '/widgets', entries: [ { text: 'Watched', href: '/watched' }, { text: 'Library', href: '/library' }, { text: 'Profile', href: '/profile' }, ], }, '.btn-profile a:contains("Quick Actions")': { entries: [ { text: '<span class="toggle-dark-mode">Toggle Dark Mode<span class="right fa-solid fa-moon"></span></span>', onclick: 'toggleDarkMode(); return false;' }, { text: 'Clear Search History', onclick: 'showLoading(); $.post(`/users/me/clear_search_history`).done(() => { toastr.success(`Your search history was cleared.`); cacheUserData(true); }).always(hideLoading); return false;' }, { text: 'Re-cache Progress Data', onclick: 'showLoading(); $.post(`/users/me/reset_progress_cache`).done(() => { toastr.success(`Your progress cache will be updated in a few minutes.`); }).always(hideLoading); return false;' }, { text: 'Re-cache Browser Data', onclick: 'window.reopenOverlays = [null]; window.afterLoadingBottomMessage = `Your browser data is reset!`; showLoading(`Please wait for the caching to fully complete.`); resetUserData(); return false;' }, ], }, '.btn-profile a[href="/settings"]': { hrefPrefix: '/settings', entries: [ { text: 'Advanced', href: '/advanced' }, { text: 'Your API Apps', href: '/oauth/applications', useHrefPrefix: false, submenu: { entries: [ { text: '<span class="hidden-xs left fa fa-book"></span>API Docs', href: '/b/api-docs' }, { text: '<span class="hidden-xs left fa fa-github"></span>Developer Forum', href: '/b/dev-forum' }, { text: '<span class="hidden-xs left fa trakt-icon-trakt"></span>Branding', href: '/branding' }, { text: '<span class="hidden-xs left fa fa-plus-circle"></span>New Application', href: '/oauth/applications/new' }, ], }}, { text: 'Connected Apps', href: '/oauth/authorized_applications', useHrefPrefix: false, submenu: { entries: [ { text: 'Activate Device', href: '/activate' }, ], }}, { text: 'Reports', href: '/reports', submenu: { hrefPrefix: '/reports', entries: [ { text: 'STATUS' }, { text: 'All Reports', href: '/all', anchor: true }, { text: 'Approved', href: '/approved' }, { text: 'Paused', href: '/paused' }, { text: 'Rejected', href: '/rejected' }, { text: 'Pending', href: '/pending' }, ], }}, { text: 'Hidden Items', href: '/hidden', submenu: { hrefPrefix: '/settings/hidden', entries: [ { text: 'Dropped Shows', href: '/dropped', submenu: menuTemplates.hiddenItemsSorting('/settings/hidden/dropped') }, {}, { text: 'Progress', href: '/watched', submenu: menuTemplates.hiddenItemsSorting('/settings/hidden/watched') }, { text: 'Library', href: '/collected', submenu: menuTemplates.hiddenItemsSorting('/settings/hidden/collected') }, // TODO switch to library once /settings/hidden/library works { text: 'Calendar', href: '/calendars', submenu: menuTemplates.hiddenItemsSorting('/settings/hidden/calendars') }, {}, { text: 'Rewatching', href: '/rewatching', submenu: menuTemplates.hiddenItemsSorting('/settings/hidden/rewatching') }, { text: 'Blocked Users', href: '/comments', submenu: menuTemplates.hiddenItemsSorting('/settings/hidden/comments') }, ], }}, { text: 'Plex Sync', href: '/plex' }, { text: 'Streaming Sync', href: '/scrobblers' }, { text: 'Notifications', href: '/notifications' }, { text: 'Sharing', href: '/sharing' }, { text: 'Data', href: '/data' }, { text: 'General', href: '', anchor: true, submenu: { hrefPrefix: '/settings', entries: [ { text: 'Change Password', href: '#password' }, { text: 'Appearance', href: '#appearance' }, { text: 'Search', href: '#search' }, { text: 'Progress', href: '#progress' }, { text: 'Profile', href: '#profile' }, { text: 'Year in Review', href: '#yir' }, { text: 'Calendars', href: '#calendars' }, { text: 'Dashboard', href: '#dashboard' }, { text: 'Spoilers', href: '#spoilers' }, { text: 'Watch Now', href: '#watchnow' }, { text: 'Rewatching', href: '#rewatching' }, { text: 'Global', href: '#global' }, { text: 'Date & Time', href: '#datetime' }, { text: 'Account', href: '#account', anchor: true }, ], }}, ], }, ':is(.btn-mobile-links, .links-wrapper) a[href^="/shows"]': { hrefPrefix: '/shows', entries: [ { text: 'Trending', href: '/trending' }, { text: 'Recommendations', href: '/recommendations' }, { text: 'Streaming Charts', href: '/streaming' }, { text: 'Anticipated', href: '/anticipated' }, { text: 'Popular', href: '/popular' }, { text: 'Favorited', href: '/favorited', submenu: menuTemplates.showsMoviesCatTimePeriod('/shows/favorited') }, { text: 'Watched', href: '/watched', submenu: menuTemplates.showsMoviesCatTimePeriod('/shows/watched') }, { text: 'Libraries', href: '/library', submenu: menuTemplates.showsMoviesCatTimePeriod('/shows/library') }, ], }, ':is(.btn-mobile-links, .links-wrapper) a[href^="/movies"]': { hrefPrefix: '/movies', entries: [ { text: 'Trending', href: '/trending' }, { text: 'Recommendations', href: '/recommendations' }, { text: 'Streaming Charts', href: '/streaming' }, { text: 'Anticipated', href: '/anticipated' }, { text: 'Popular', href: '/popular' }, { text: 'Favorited', href: '/favorited', submenu: menuTemplates.showsMoviesCatTimePeriod('/movies/favorited') }, { text: 'Watched', href: '/watched', submenu: menuTemplates.showsMoviesCatTimePeriod('/movies/watched') }, { text: 'Libraries', href: '/library', submenu: menuTemplates.showsMoviesCatTimePeriod('/movies/library') }, { text: 'Box Office', href: '/boxoffice' }, ], }, ':is(.btn-mobile-links, .links-wrapper) a[href="/calendars"]': { hrefPrefix: '/calendars', entries: [ { text: 'Personal', href: '/my/shows-movies', submenu: { hrefPrefix: '/calendars/my', entries: [ { text: 'Shows & Movies', href: '/shows-movies' }, { text: 'Shows', href: '/shows' }, { text: 'Premieres', href: '/premieres' }, { text: 'New Shows', href: '/new-shows' }, { text: 'Finales', href: '/finales' }, { text: 'Movies', href: '/movies' }, { text: 'Streaming', href: '/streaming' }, { text: 'DVD & Blu-ray', href: '/dvd' }, ], }}, { text: 'General', href: '/shows', submenu: { hrefPrefix: '/calendars', entries: [ { text: 'Shows', href: '/shows' }, { text: 'Premieres', href: '/premieres' }, { text: 'New Shows', href: '/new-shows' }, { text: 'Finales', href: '/finales' }, { text: 'Movies', href: '/movies' }, { text: 'Streaming', href: '/streaming' }, { text: 'DVD & Blu-ray', href: '/dvd' }, ], }}, ], }, ':is(.btn-mobile-links, .links-wrapper) a[href="/discover"]': { hrefPrefix: '/discover', entries: [ { text: 'Trends', href: '#trends' }, { text: 'Featured Lists', href: '#lists' }, { text: 'Summer TV Shows', href: '#featured-shows' }, { text: 'Comments', href: '#comments' }, ], }, ':is(.btn-mobile-links, .btn-tablet-links, .links-wrapper) a[href="/apps"]': { hrefPrefix: '/apps', entries: [ { text: 'Android <em>(official)</em>', href: "/a/trakt-android", useHrefPrefix: false, anchor: true }, { text: 'iOS <em>(official)</em>', href: "/a/trakt-ios", useHrefPrefix: false }, { text: 'Android & iOS <em>(3rd Party)</em>', href: "#community-apps" }, { text: 'Android TV <em>(official)</em>', href: "/a/trakt-android-tv", useHrefPrefix: false }, { text: 'tvOS <em>(official)</em>', href: "/a/trakt-tvos", useHrefPrefix: false }, {}, { text: 'INTEGRATIONS' }, { text: 'Media Centers', href: "#watching-wrapper" }, { text: 'Plex Sync', href: "#plex-scrobblers-wrapper" }, { text: 'Streaming Sync', href: "#streaming-scrobbler-wrapper" }, ], }, ':is(.btn-mobile-links, .btn-tablet-links, .links-wrapper) a[href="https://forums.trakt.tv"]': { hrefPrefix: 'https://forums.trakt.tv', entries: [ { text: 'Categories', href: '/categories', submenu: { hrefPrefix: 'https://forums.trakt.tv', entries: [ { text: 'Announcements', href: '/c/announcements' }, { text: 'Discussions', href: '/c/discussions', submenu: { hrefPrefix: 'https://forums.trakt.tv/c/discussions', entries: [ { text: 'General', href: '/general' }, { text: 'TV Shows', href: '/tv-shows' }, { text: 'Movies', href: '/movies' }, { text: 'Off Topic', href: '/off-topic' }, ], }}, { text: 'Trakt', href: '/c/trakt', submenu: { hrefPrefix: 'https://forums.trakt.tv/c/trakt', entries: [ { text: 'Questions & Help', href: '/questions' }, { text: 'Feature Requests', href: '/feature-requests' }, { text: 'Lite', href: '/trakt-lite' }, { text: 'Release Notes', href: '/release-notes' }, { text: 'VIP Beta Features', href: '/vip-beta-features' }, ], }}, { text: '3rd Party', href: '/c/3rd-party', submenu: { hrefPrefix: 'https://forums.trakt.tv/c/3rd-party', entries: [ { text: 'Media Centers', href: '/media-centers' }, { text: 'Mobile Apps', href: '/mobile-apps' }, { text: 'Other', href: '/other' }, ], }}, { text: 'Support', href: '/c/support', submenu: { hrefPrefix: 'https://forums.trakt.tv/c/support', entries: [ { text: 'Tutorials', href: '/tutorials' }, { text: 'VIP Features', href: '/vip-features' }, { text: 'Features', href: '/support-features' }, { text: 'FAQ', href: '/faq' }, ], }}, ], }}, { text: 'Latest', href: '/latest' }, { text: 'New', href: '/new' }, { text: 'Top', href: '/top', submenu: { hrefPrefix: 'https://forums.trakt.tv/top?period=', entries: [ { text: 'PERIOD' }, { text: 'Day', href: 'daily', anchor: true }, { text: 'Week', href: 'weekly' }, { text: 'Month', href: 'monthly' }, { text: 'Quarter', href: 'quarterly' }, { text: 'Year', href: 'yearly' }, { text: 'All Time', href: 'all' }, ], }}, {}, { text: 'EXTERNAL' }, { text: '<span class="left fa fa-reddit-alien"></span>r/trakt', href: 'https://reddit.com/r/trakt', useHrefPrefix: false, submenu: { hrefPrefix: 'https://reddit.com/r/trakt', entries: [ { text: 'SORT BY' }, { text: 'Best', href: '/best' }, { text: 'Hot', href: '/hot' }, { text: 'New', href: '/new' }, { text: 'Top', href: '/top', submenu: { hrefPrefix: 'https://reddit.com/r/trakt/top?t=', entries: [ { text: 'PERIOD' }, { text: 'Hour', href: 'hour' }, { text: 'Day', href: 'day' }, { text: 'Week', href: 'week' }, { text: 'Month', href: 'month' }, { text: 'Year', href: 'year' }, { text: 'All Time', href: 'all', anchor: true }, ], }}, { text: 'Rising', href: '/rising', anchor: true }, ], }}, { text: '<span class="left fa-brands fa-twitter"></span>Twitter', href: 'https://twitter.com/trakt', useHrefPrefix: false }, { text: '<span class="left fa-brands fa-mastodon"></span>Mastodon', href: 'https://ruby.social/@trakt', useHrefPrefix: false }, ], }, }; /////////////////////////////////////////////////////////////////////////////////////////////// const buildMenuHtml = ({ hrefPrefix, entries }) => entries.reduce((acc, { text, href, useHrefPrefix = true, onclick, submenu }, i) => acc + ( text !== undefined && (href !== undefined || onclick !== undefined) ? `<li${submenu ? ' class="with-ul-menu"' : ''}>` + `<a href="${useHrefPrefix && hrefPrefix ? hrefPrefix : ''}${href ?? '#'}"${onclick ? ` onclick="${onclick}"` : ''}>${text}</a>${submenu ? buildMenuHtml(submenu) : ''}` + `</li>` : text !== undefined ? `<li class="dropdown-header">${text}</li>` : '<li class="divider"></li>' ), `<ul class="menu" style="top: calc(-10px - ${Math.max(entries.findIndex((e) => e.anchor), 0) * 100}%)">`) + '</ul>'; const menuSelectorsAndHtml = Object.entries(menus).map(([targetSelector, menu]) => [targetSelector, buildMenuHtml(menu)]); addStyles(); window.addEventListener('turbo:load', () => { const $ = unsafeWindow.jQuery; if (!$) return; const $topNav = $('#top-nav'); $topNav.find('.links-wrapper > a').wrap('<div class="with-solid-bg"></div>'); $topNav.find('.links-wrapper a[href="/apps"]') .next().remove() .end().parent().removeClass('with-top-arrow').addClass('with-solid-bg'); $topNav.find('.btn-mobile-links li:has(> a[href="/apps"])') .next().remove() .end().unwrap(); $topNav.find('li.dark-knight') .removeClass('dark-knight').html('<a href="#" onclick="return false;">Quick Actions</a>') .before('<li class="divider"></li>').next().remove(); $topNav.find('.btn-profile li:has(> a.yir-loader)') .wrapAll('<li class="with-ul-menu"><ul class="menu" style="top: -10px;"></ul></li>') .parent().before('<a href="#" onclick="return false;">Stats</a>') $topNav.find('a[href="https://forums.trakt.tv"]').removeAttr('target'); menuSelectorsAndHtml.forEach(([targetSelector, menuHtml]) => $topNav.find(targetSelector).closest('li, div').addClass('with-ul-menu').append(menuHtml)); const $withUlMenu = $topNav.find(':is(.user-wrapper, .links-wrapper) .with-ul-menu'); $withUlMenu.off('click mouseover mouseout').on('touchend', function(evt) { // TODO :hover state gets set on second touchend evt.stopPropagation(); if ($(evt.originalEvent.target).closest($(this).children().first()).length) { if (!$(this).hasClass('selected')) { evt.preventDefault(); $withUlMenu.not($(this).parents()).removeClass('selected'); $(this).addClass('selected'); } else { $(this).removeClass('selected'); } } }); $('body').on('touchend', () => $withUlMenu.removeClass('selected')); }); /////////////////////////////////////////////////////////////////////////////////////////////// function addStyles() { GM_addStyle(` #top-nav :is(.user-wrapper, .links-wrapper) > .with-ul-menu { border-radius: 8px 8px 0 0 !important; } #top-nav :is(.user-wrapper, .links-wrapper) > .with-ul-menu > a { transition: color .2s !important; } #top-nav#top-nav :is(.user-wrapper, .links-wrapper) > .with-ul-menu > a:hover { color: var(--brand-primary-300) !important; } #top-nav :is(.user-wrapper, .links-wrapper) ul { height: max-content; width: max-content !important; overflow-y: revert !important; } #top-nav :is(.user-wrapper, .links-wrapper) > .with-ul-menu > ul { top: 100% !important; min-width: max(130px, 100%) !important; } #top-nav .links-wrapper > .with-ul-menu > ul { border-radius: 8px 0 8px 8px !important; } #top-nav :is(.user-wrapper, .links-wrapper) ul ul { min-width: 100px !important; border-radius: 8px !important; border-top: revert !important; } #top-nav :is(.user-wrapper, .links-wrapper) :is(ul a, .dropdown-header) { padding: 6px 16px !important; font-size: 14px !important; margin: revert !important; text-shadow: revert !important; } @media (width <= 767px) { #top-nav :is(.user-wrapper, .links-wrapper) :is(ul a, .dropdown-header) { padding: 6px 12px !important; } } #top-nav#top-nav :is(.user-wrapper, .links-wrapper) ul a, #top-nav .user-wrapper :is(.btn-mobile-links, .btn-tablet-links) > .icon { color: #fff !important; /* light mode override */ } #top-nav#top-nav :is(.user-wrapper, .links-wrapper) ul a:hover { background-color: rgb(from var(--brand-primary) r g b / 92%) !important; } #top-nav :is(.user-wrapper, .links-wrapper) .dropdown-header { font-weight: bold; text-transform: uppercase; } #top-nav :is(.user-wrapper, .links-wrapper) span.left { width: 18px; margin-right: 8px; text-align: center; } #top-nav :is(.user-wrapper, .links-wrapper) span.right { margin-left: 8px; } body.dark-knight #top-nav#top-nav .btn-profile a:has(> span.toggle-dark-mode):not(:hover) { color: var(--brand-secondary) !important; } #top-nav :is(.user-wrapper, .links-wrapper) li > a:has(+ ul)::after { content: "\\25B6"; display: inline-block; float: right; margin-left: 10px; transform: scale(0.75) rotate(0deg); transition: transform 0.2s; } #top-nav :is(.user-wrapper, .links-wrapper) :is(:hover, .selected) > a::after { transform: rotate(180deg) scale(1); } #top-nav :is(.user-wrapper, .links-wrapper) ul { display: none !important; } #top-nav :is(.user-wrapper, .links-wrapper) :is(:hover, .selected) > ul { display: block !important; } #top-nav :is(.user-wrapper, .links-wrapper) ul ul { --menu-columns: 5; --menu-overlap: min(97%, calc((100vw - 155px * var(--menu-columns)) / var(--menu-columns) + 100%)); right: var(--menu-overlap) !important; } @media (767px < width <= 991px) { #top-nav .links-wrapper ul ul { --menu-columns: 3; } #top-nav .links-wrapper ul ul ul ul { left: var(--menu-overlap) !important; right: revert !important; } #top-nav .links-wrapper ul ul ul a::after { transform: scale(0.75) rotate(180deg); } #top-nav .links-wrapper ul ul ul :is(:hover, .selected) > a::after { transform: rotate(0deg) scale(1); } } @media (width <= 767px) { #top-nav :is(.user-wrapper, .links-wrapper) ul ul { --menu-columns: 3; } #top-nav :is(.user-wrapper, .links-wrapper) ul ul ul ul { left: var(--menu-overlap) !important; right: revert !important; } #top-nav :is(.user-wrapper, .links-wrapper) ul ul ul a::after { transform: scale(0.75) rotate(180deg); } #top-nav :is(.user-wrapper, .links-wrapper) ul ul ul :is(:hover, .selected) > a::after { transform: rotate(0deg) scale(1); } } #top-nav :is(.user-wrapper, .links-wrapper) ul, #top-nav :is(.user-wrapper, .links-wrapper) > .with-ul-menu:is(:hover, .selected) { --nesting-depth: 0; z-index: var(--nesting-depth); background-color: hsl(0deg 0% calc(20% + var(--nesting-depth) * 2.5%) / 92%) !important; } #top-nav :is(.user-wrapper, .links-wrapper) .divider { background-color: hsl(0deg 0% calc(27% + var(--nesting-depth) * 2.5%)) !important; } #top-nav :is(.user-wrapper, .links-wrapper) :is(.dropdown-header, em) { color: hsl(0deg 0% calc(57% + var(--nesting-depth) * 2.5%)) !important; } #top-nav :is(.user-wrapper, .links-wrapper) ul ul { --nesting-depth: 1; } #top-nav :is(.user-wrapper, .links-wrapper) ul ul ul { --nesting-depth: 2; } #top-nav :is(.user-wrapper, .links-wrapper) ul ul ul ul { --nesting-depth: 3; } #top-nav :is(.user-wrapper, .links-wrapper) ul ul ul ul ul { --nesting-depth: 4; } `); }