YouTube - Playlist Drag Without Refresh

Prevents YouTube from refreshing when reorganizing/dragging videos in a playlist

Size

5.3 KB

Version

1.0.1

Created

Mar 6, 2026

Updated

about 1 month ago

1// ==UserScript==
2// @name		YouTube - Playlist Drag Without Refresh
3// @description		Prevents YouTube from refreshing when reorganizing/dragging videos in a playlist
4// @version		1.0.1
5// @match		https://*.youtube.com/*
6// @icon		https://www.youtube.com/s/desktop/1b43e753/img/favicon_32x32.png
7// @grant		GM.xmlhttpRequest
8// ==/UserScript==
9(function() {
10    'use strict';
11
12    console.log('YouTube Playlist Drag Without Refresh - Extension loaded');
13
14    // Prevent page navigation during drag operations
15    let isDragging = false;
16    let originalPushState = null;
17    let originalReplaceState = null;
18
19    function init() {
20        // Store original history methods
21        originalPushState = history.pushState;
22        originalReplaceState = history.replaceState;
23
24        // Override history.pushState to prevent navigation during drag
25        history.pushState = function(...args) {
26            if (isDragging) {
27                console.log('Blocked pushState during drag operation');
28                return;
29            }
30            return originalPushState.apply(this, args);
31        };
32
33        // Override history.replaceState to prevent navigation during drag
34        history.replaceState = function(...args) {
35            if (isDragging) {
36                console.log('Blocked replaceState during drag operation');
37                return;
38            }
39            return originalReplaceState.apply(this, args);
40        };
41
42        // Monitor for drag operations on playlist items
43        observePlaylistDragOperations();
44        
45        console.log('YouTube Playlist Drag Without Refresh - Initialized');
46    }
47
48    function observePlaylistDragOperations() {
49        // Use event delegation to catch drag events on playlist items
50        document.addEventListener('dragstart', function(e) {
51            const playlistItem = e.target.closest('ytd-playlist-video-renderer');
52            if (playlistItem) {
53                isDragging = true;
54                console.log('Drag started on playlist item');
55            }
56        }, true);
57
58        document.addEventListener('dragend', function(e) {
59            const playlistItem = e.target.closest('ytd-playlist-video-renderer');
60            if (playlistItem) {
61                console.log('Drag ended on playlist item');
62                // Delay resetting to ensure all navigation attempts are blocked
63                setTimeout(() => {
64                    isDragging = false;
65                    console.log('Drag operation complete');
66                }, 1000);
67            }
68        }, true);
69
70        document.addEventListener('drop', function(e) {
71            const playlistContainer = e.target.closest('ytd-playlist-video-list-renderer');
72            if (playlistContainer) {
73                console.log('Drop detected in playlist');
74                // Keep blocking navigation for a bit longer after drop
75                setTimeout(() => {
76                    isDragging = false;
77                    console.log('Drop operation complete');
78                }, 1500);
79            }
80        }, true);
81
82        // Also monitor for mousedown on drag handles
83        document.addEventListener('mousedown', function(e) {
84            const dragHandle = e.target.closest('.playlist-drag-handle');
85            if (dragHandle) {
86                isDragging = true;
87                console.log('Drag handle grabbed');
88            }
89        }, true);
90
91        document.addEventListener('mouseup', function(e) {
92            if (isDragging) {
93                console.log('Mouse released');
94                setTimeout(() => {
95                    isDragging = false;
96                    console.log('Drag interaction complete');
97                }, 1000);
98            }
99        }, true);
100    }
101
102    // Intercept YouTube's navigation system
103    function interceptYouTubeNavigation() {
104        // Intercept yt-navigate events that YouTube uses for navigation
105        document.addEventListener('yt-navigate', function(e) {
106            if (isDragging) {
107                console.log('Blocked yt-navigate event during drag');
108                e.stopPropagation();
109                e.stopImmediatePropagation();
110                e.preventDefault();
111            }
112        }, true);
113
114        document.addEventListener('yt-navigate-start', function(e) {
115            if (isDragging) {
116                console.log('Blocked yt-navigate-start event during drag');
117                e.stopPropagation();
118                e.stopImmediatePropagation();
119                e.preventDefault();
120            }
121        }, true);
122
123        document.addEventListener('yt-navigate-finish', function(e) {
124            if (isDragging) {
125                console.log('Blocked yt-navigate-finish event during drag');
126                e.stopPropagation();
127                e.stopImmediatePropagation();
128                e.preventDefault();
129            }
130        }, true);
131    }
132
133    // Wait for page to be ready
134    if (document.readyState === 'loading') {
135        document.addEventListener('DOMContentLoaded', function() {
136            init();
137            interceptYouTubeNavigation();
138        });
139    } else {
140        init();
141        interceptYouTubeNavigation();
142    }
143
144    // Also handle YouTube's SPA navigation
145    document.addEventListener('yt-page-data-updated', function() {
146        console.log('YouTube page updated, re-initializing if needed');
147    });
148
149})();