Skip to content

Commit 5885492

Browse files
committed
Add hide watched videos setting
1 parent cbe42b6 commit 5885492

6 files changed

Lines changed: 181 additions & 1 deletion

File tree

_locales/en/messages.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,9 @@
789789
"hideWatchedVideos": {
790790
"message": "Hide watched videos"
791791
},
792+
"hideWatchedVideosThreshold": {
793+
"message": "Hide watched videos at"
794+
},
792795
"high": {
793796
"message": "High"
794797
},

js&css/extension/init.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ window.addEventListener('yt-navigate-finish', function () {
1111
extension.features.trackWatchedVideos();
1212
extension.features.thumbnailsQuality();
1313
extension.features.stickyNavigation();
14+
extension.features.hideWatchedVideos();
1415
extension.features.hideSponsoredVideosOnHome?.();
1516
});
1617

@@ -46,6 +47,7 @@ extension.events.on('init', function () {
4647
extension.features.disableThumbnailPlayback();
4748
extension.features.muteThumbnailPreviews();
4849
extension.features.markWatchedVideos();
50+
extension.features.hideWatchedVideos();
4951
extension.features.relatedVideos();
5052
extension.features.sidePanels();
5153
extension.features.stickyNavigation();

js&css/extension/www.youtube.com/general/general.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ html[it-cursorLighting=true] ytd-mini-guide-renderer {opacity:.4; transition:2
5252
html[it-cursorLighting=true]:not([data-page-type=video]) #guide-content:hover,
5353
html[it-cursorLighting=true] ytd-mini-guide-renderer:hover {opacity:1; transition:.3s}
5454

55+
/*--------------------------------------------------------------
56+
# HIDE WATCHED VIDEOS
57+
--------------------------------------------------------------*/
58+
ytd-compact-video-renderer[it-hide-watched-video],
59+
ytd-rich-item-renderer[it-hide-watched-video],
60+
ytd-video-renderer[it-hide-watched-video],
61+
ytd-grid-video-renderer[it-hide-watched-video],
62+
ytd-playlist-video-renderer[it-hide-watched-video],
63+
ytd-reel-item-renderer[it-hide-watched-video] {
64+
display: none !important;
65+
}
66+
5567
/*--------------------------------------------------------------
5668
# SQUARED THUMBNAILS
5769
--------------------------------------------------------------*/

js&css/extension/www.youtube.com/general/general.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,114 @@ extension.features.trackWatchedVideos = function () {
703703
}
704704
};
705705

706+
/*--------------------------------------------------------------
707+
# HIDE WATCHED VIDEOS
708+
--------------------------------------------------------------*/
709+
710+
extension.features.hideWatchedVideos = function (anything) {
711+
var enabled = anything === true || extension.storage.get('hide_watched_videos') === true;
712+
var selector = [
713+
'ytd-compact-video-renderer',
714+
'ytd-rich-item-renderer',
715+
'ytd-video-renderer',
716+
'ytd-grid-video-renderer',
717+
'ytd-playlist-video-renderer',
718+
'ytd-reel-item-renderer'
719+
].join(',');
720+
721+
function getVideoId(renderer) {
722+
var link = renderer.querySelector('a[href*="/watch"], a[href*="/shorts/"]');
723+
var href = link && link.href;
724+
725+
if (!href) {
726+
return '';
727+
}
728+
729+
var queryId = extension.functions.getUrlParameter(href, 'v');
730+
731+
if (queryId) {
732+
return queryId;
733+
}
734+
735+
var shortsMatch = href.match(/\/shorts\/([^?&/]+)/);
736+
737+
return shortsMatch ? shortsMatch[1] : '';
738+
}
739+
740+
function getWatchedPercent(renderer) {
741+
if (renderer.querySelector('ytd-thumbnail-overlay-watched-status-renderer')) {
742+
return 100;
743+
}
744+
745+
var progress = renderer.querySelector('ytd-thumbnail-overlay-resume-playback-renderer #progress, #progress.ytd-thumbnail-overlay-resume-playback-renderer');
746+
747+
if (progress && progress.style && progress.style.width) {
748+
var width = parseFloat(progress.style.width);
749+
750+
if (!Number.isNaN(width)) {
751+
return width;
752+
}
753+
}
754+
755+
var videoId = getVideoId(renderer);
756+
var watched = extension.storage.get('watched');
757+
758+
if (videoId && watched && watched[videoId]) {
759+
return 100;
760+
}
761+
762+
return 0;
763+
}
764+
765+
function apply() {
766+
var threshold = parseInt(extension.storage.get('hide_watched_videos_threshold'), 10);
767+
768+
if (Number.isNaN(threshold)) {
769+
threshold = 90;
770+
}
771+
772+
threshold = Math.max(1, Math.min(100, threshold));
773+
774+
document.querySelectorAll(selector).forEach(function (renderer) {
775+
var hide = enabled && getWatchedPercent(renderer) >= threshold;
776+
777+
renderer.toggleAttribute('it-hide-watched-video', hide);
778+
});
779+
}
780+
781+
if (extension.features.hideWatchedVideos.observer) {
782+
extension.features.hideWatchedVideos.observer.disconnect();
783+
extension.features.hideWatchedVideos.observer = null;
784+
}
785+
786+
apply();
787+
788+
if (enabled) {
789+
var scheduled = false;
790+
var schedule = function () {
791+
if (scheduled) {
792+
return;
793+
}
794+
795+
scheduled = true;
796+
requestAnimationFrame(function () {
797+
scheduled = false;
798+
apply();
799+
});
800+
};
801+
802+
extension.features.hideWatchedVideos.observer = new MutationObserver(schedule);
803+
extension.features.hideWatchedVideos.observer.observe(document.documentElement, {
804+
childList: true,
805+
subtree: true,
806+
attributes: true,
807+
attributeFilter: ['style']
808+
});
809+
}
810+
};
811+
812+
extension.features.hideWatchedVideosThreshold = extension.features.hideWatchedVideos;
813+
706814
/*--------------------------------------------------------------
707815
# THUMBNAILS QUALITY
708816
--------------------------------------------------------------*/

menu/skeleton-parts/general.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,25 @@ extension.skeleton.main.layers.section.general = {
468468
},
469469
hide_watched_videos: {
470470
component: 'switch',
471-
text: 'hideWatchedVideos'
471+
text: 'hideWatchedVideos',
472+
on: {
473+
click: function () {
474+
setTimeout(() => {
475+
if (satus.storage.get('hide_watched_videos') && !satus.storage.get('track_watched_videos')) {
476+
satus.storage.set('track_watched_videos', true);
477+
}
478+
}, 250);
479+
}
480+
}
481+
},
482+
hide_watched_videos_threshold: {
483+
component: 'slider',
484+
variant: 'row',
485+
text: 'hideWatchedVideosThreshold',
486+
min: 1,
487+
max: 100,
488+
step: 1,
489+
value: 90
472490
},
473491
hide_watch_later: {
474492
component: 'switch',
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
describe('Hide watched videos (#4128)', () => {
5+
let initContent;
6+
let generalContent;
7+
let generalCssContent;
8+
let menuContent;
9+
10+
beforeAll(() => {
11+
initContent = fs.readFileSync(path.join(__dirname, '../../js&css/extension/init.js'), 'utf8');
12+
generalContent = fs.readFileSync(path.join(__dirname, '../../js&css/extension/www.youtube.com/general/general.js'), 'utf8');
13+
generalCssContent = fs.readFileSync(path.join(__dirname, '../../js&css/extension/www.youtube.com/general/general.css'), 'utf8');
14+
menuContent = fs.readFileSync(path.join(__dirname, '../../menu/skeleton-parts/general.js'), 'utf8');
15+
});
16+
17+
test('initializes the hide watched videos feature', () => {
18+
expect(initContent).toContain('extension.features.hideWatchedVideos();');
19+
});
20+
21+
test('hides watched thumbnail renderers by threshold', () => {
22+
expect(generalContent).toContain('extension.features.hideWatchedVideos = function');
23+
expect(generalContent).toContain('ytd-thumbnail-overlay-watched-status-renderer');
24+
expect(generalContent).toContain('ytd-thumbnail-overlay-resume-playback-renderer #progress');
25+
expect(generalContent).toContain('hide_watched_videos_threshold');
26+
expect(generalContent).toContain('it-hide-watched-video');
27+
expect(generalCssContent).toContain('[it-hide-watched-video]');
28+
expect(generalCssContent).toContain('display: none !important');
29+
});
30+
31+
test('exposes a settings threshold control and enables tracking explicitly', () => {
32+
expect(menuContent).toContain('hide_watched_videos_threshold');
33+
expect(menuContent).toContain("component: 'slider'");
34+
expect(menuContent).toContain("text: 'hideWatchedVideosThreshold'");
35+
expect(menuContent).toContain("satus.storage.set('track_watched_videos', true)");
36+
});
37+
});

0 commit comments

Comments
 (0)