From 7dd699370fae20c69119a4117468b1d999a2752a Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 6 May 2022 04:46:59 +0300 Subject: [PATCH 01/17] js code rewrite. Created _helpers.js with XHR and storage wrapper --- assets/js/_helpers.js | 218 +++++++++ assets/js/community.js | 86 ++-- assets/js/embed.js | 97 ++-- assets/js/handlers.js | 133 +++--- assets/js/notifications.js | 92 ++-- assets/js/player.js | 306 +++++------- assets/js/playlist_widget.js | 52 +- assets/js/subscribe_widget.js | 92 +--- assets/js/themes.js | 48 +- assets/js/watch.js | 446 +++++++----------- assets/js/watched_widget.js | 39 +- src/invidious/comments.cr | 2 +- src/invidious/views/add_playlist_items.ecr | 1 + src/invidious/views/community.ecr | 1 + src/invidious/views/components/player.ecr | 1 + .../views/components/subscribe_widget.ecr | 1 + src/invidious/views/embed.ecr | 1 + src/invidious/views/feeds/history.ecr | 1 + src/invidious/views/feeds/subscriptions.ecr | 1 + src/invidious/views/licenses.ecr | 14 + src/invidious/views/playlist.ecr | 1 + src/invidious/views/template.ecr | 1 + src/invidious/views/watch.ecr | 2 + 23 files changed, 735 insertions(+), 901 deletions(-) create mode 100644 assets/js/_helpers.js diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js new file mode 100644 index 00000000..04576348 --- /dev/null +++ b/assets/js/_helpers.js @@ -0,0 +1,218 @@ +'use strict'; +// Contains only auxiliary methods +// May be included and executed unlimited number of times without any consequences + +// Polyfills for IE11 +Array.prototype.find = Array.prototype.find || function (condition) { + return this.filter(condition)[0]; +}; +Array.from = Array.from || function (source) { + return Array.prototype.slice.call(source); +}; +NodeList.prototype.forEach = NodeList.prototype.forEach || function (callback) { + Array.from(this).forEach(callback); +}; +String.prototype.includes = String.prototype.includes || function (searchString) { + return this.indexOf(searchString) >= 0; +}; +String.prototype.startsWith = String.prototype.startsWith || function (prefix) { + return this.substr(0, prefix.length) === prefix; +}; +Math.sign = Math.sign || function(x) { + x = +x; + if (!x) return x; // 0 and NaN + return x > 0 ? 1 : -1; +}; + +// Monstrous global variable for handy code +helpers = helpers || { + /** + * https://en.wikipedia.org/wiki/Clamping_(graphics) + * @param {Number} num Source number + * @param {Number} min Low border + * @param {Number} max High border + * @returns {Number} Clamped value + */ + clamp: function (num, min, max) { + if (max < min) { + var t = max; max = min; min = t; // swap max and min + } + + if (max > num) + return max; + if (min < num) + return min; + return num; + }, + + /** @private */ + _xhr: function (method, url, options, callbacks) { + const xhr = new XMLHttpRequest(); + xhr.open(method, url); + + // Default options + xhr.responseType = 'json'; + xhr.timeout = 10000; + // Default options redefining + if (options.responseType) + xhr.responseType = options.responseType; + if (options.timeout) + xhr.timeout = options.timeout; + + if (method === 'POST') + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) + if (callbacks.on200) + callbacks.on200(xhr.response); + else + if (callbacks.onNon200) + callbacks.onNon200(xhr); + } + }; + + xhr.ontimeout = function () { + if (callbacks.onTimeout) + callbacks.onTimeout(xhr); + }; + + xhr.onerror = function () { + if (callbacks.onError) + callbacks.onError(xhr); + }; + + if (options.payload) + xhr.send(options.payload); + else + xhr.send(); + }, + /** @private */ + _xhrRetry(method, url, options, callbacks) { + if (options.retries <= 0) { + console.warn('Failed to pull', options.entity_name); + if (callbacks.onTotalFail) + callbacks.onTotalFail(); + return; + } + helpers.xhr(method, url, options, callbacks); + }, + /** + * @callback callbackXhrOn200 + * @param {Object} response - xhr.response + */ + /** + * @callback callbackXhrError + * @param {XMLHttpRequest} xhr + */ + /** + * @param {'GET'|'POST'} method - 'GET' or 'POST' + * @param {String} url - URL to send request to + * @param {Object} options - other XHR options + * @param {XMLHttpRequestBodyInit} [options.payload=null] - payload for POST-requests + * @param {'arraybuffer'|'blob'|'document'|'json'|'text'} [options.responseType=json] + * @param {Number} [options.timeout=10000] + * @param {Number} [options.retries=1] + * @param {String} [options.entity_name='unknown'] - string to log + * @param {Number} [options.retry_timeout=1000] + * @param {Object} callbacks - functions to execute on events fired + * @param {callbackXhrOn200} [callbacks.on200] + * @param {callbackXhrError} [callbacks.onNon200] + * @param {callbackXhrError} [callbacks.onTimeout] + * @param {callbackXhrError} [callbacks.onError] + * @param {callbackXhrError} [callbacks.onTotalFail] - if failed after all retries + */ + xhr(method, url, options, callbacks) { + if (options.retries > 1) { + helpers._xhr(method, url, options, callbacks); + return; + } + + if (!options.entity_name) options.entity_name = 'unknown'; + if (!options.retry_timeout) options.retry_timeout = 1; + const retries_total = options.retries; + + const retry = function () { + console.warn('Pulling ' + options.entity_name + ' failed... ' + options.retries + '/' + retries_total); + setTimeout(function () { + options.retries--; + helpers._xhrRetry(method, url, options, callbacks); + }, options.retry_timeout); + }; + + if (callbacks.onError) + callbacks._onError = callbacks.onError; + callbacks.onError = function (xhr) { + if (callbacks._onError) + callbacks._onError(); + retry(); + }; + + if (callbacks.onTimeout) + callbacks._onTimeout = callbacks.onTimeout; + callbacks.onTimeout = function (xhr) { + if (callbacks._onTimeout) + callbacks._onTimeout(); + retry(); + }; + helpers._xhrRetry(method, url, options, callbacks); + }, + + /** + * @typedef {Object} invidiousStorage + * @property {(key:String) => Object|null} get + * @property {(key:String, value:Object) => null} set + * @property {(key:String) => null} remove + */ + + /** + * Universal storage proxy. Uses inside localStorage or cookies + * @type {invidiousStorage} + */ + storage: (function () { + // access to localStorage throws exception in Tor Browser, so try is needed + let localStorageIsUsable = false; + try{localStorageIsUsable = !!localStorage.setItem;}catch(e){} + + if (localStorageIsUsable) { + return { + get: function (key) { return localStorage[key]; }, + set: function (key, value) { localStorage[key] = value; }, + remove: function (key) { localStorage.removeItem(key); } + }; + } + + console.info('Storage: localStorage is disabled or unaccessible trying cookies'); + return { + get: function (key) { + const cookiePrefix = key + '='; + function findCallback(cookie) {return cookie.startsWith(cookiePrefix);} + const matchedCookie = document.cookie.split(';').find(findCallback); + if (matchedCookie) + return matchedCookie.replace(cookiePrefix, ''); + return null; + }, + set: function (key, value) { + const cookie_data = encodeURIComponent(JSON.stringify(value)); + + // Set expiration in 2 year + const date = new Date(); + date.setTime(date.getTime() + 2*365.25*24*60*60); + + const ip_regex = /^((\d+\.){3}\d+|[A-Fa-f0-9]*:[A-Fa-f0-9:]*:[A-Fa-f0-9:]+)$/; + let domain_used = location.hostname; + + // Fix for a bug in FF where the leading dot in the FQDN is not ignored + if (domain_used.charAt(0) !== '.' && !ip_regex.test(domain_used) && domain_used !== 'localhost') + domain_used = '.' + location.hostname; + + document.cookie = key + '=' + cookie_data + '; SameSite=Strict; path=/; domain=' + + domain_used + '; expires=' + date.toGMTString() + ';'; + }, + remove: function (key) { + document.cookie = key + '=; Max-Age=0'; + } + }; + })() +}; diff --git a/assets/js/community.js b/assets/js/community.js index 44066a58..33e2e3ed 100644 --- a/assets/js/community.js +++ b/assets/js/community.js @@ -1,13 +1,6 @@ 'use strict'; var community_data = JSON.parse(document.getElementById('community_data').textContent); -String.prototype.supplant = function (o) { - return this.replace(/{([^{}]*)}/g, function (a, b) { - var r = o[b]; - return typeof r === 'string' || typeof r === 'number' ? r : a; - }); -}; - function hide_youtube_replies(event) { var target = event.target; @@ -38,13 +31,6 @@ function show_youtube_replies(event) { target.setAttribute('data-sub-text', sub_text); } -function number_with_separator(val) { - while (/(\d+)(\d{3})/.test(val.toString())) { - val = val.toString().replace(/(\d+)(\d{3})/, '$1' + ',' + '$2'); - } - return val; -} - function get_youtube_replies(target, load_more) { var continuation = target.getAttribute('data-continuation'); @@ -52,53 +38,45 @@ function get_youtube_replies(target, load_more) { var fallback = body.innerHTML; body.innerHTML = '

'; - + var url = '/api/v1/channels/comments/' + community_data.ucid + '?format=html' + '&hl=' + community_data.preferences.locale + '&thin_mode=' + community_data.preferences.thin_mode + '&continuation=' + continuation; - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('GET', url, true); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - if (load_more) { - body = body.parentNode.parentNode; - body.removeChild(body.lastElementChild); - body.innerHTML += xhr.response.contentHtml; - } else { - body.removeChild(body.lastElementChild); - - var p = document.createElement('p'); - var a = document.createElement('a'); - p.appendChild(a); - - a.href = 'javascript:void(0)'; - a.onclick = hide_youtube_replies; - a.setAttribute('data-sub-text', community_data.hide_replies_text); - a.setAttribute('data-inner-text', community_data.show_replies_text); - a.innerText = community_data.hide_replies_text; - - var div = document.createElement('div'); - div.innerHTML = xhr.response.contentHtml; - - body.appendChild(p); - body.appendChild(div); - } + helpers.xhr('GET', url, {}, { + on200: function (response) { + if (load_more) { + body = body.parentNode.parentNode; + body.removeChild(body.lastElementChild); + body.innerHTML += response.contentHtml; } else { - body.innerHTML = fallback; + body.removeChild(body.lastElementChild); + + var p = document.createElement('p'); + var a = document.createElement('a'); + p.appendChild(a); + + a.href = 'javascript:void(0)'; + a.onclick = hide_youtube_replies; + a.setAttribute('data-sub-text', community_data.hide_replies_text); + a.setAttribute('data-inner-text', community_data.show_replies_text); + a.innerText = community_data.hide_replies_text; + + var div = document.createElement('div'); + div.innerHTML = response.contentHtml; + + body.appendChild(p); + body.appendChild(div); } + }, + onNon200: function (xhr) { + body.innerHTML = fallback; + }, + onTimeout: function (xhr) { + console.warn('Pulling comments failed'); + body.innerHTML = fallback; } - }; - - xhr.ontimeout = function () { - console.warn('Pulling comments failed.'); - body.innerHTML = fallback; - }; - - xhr.send(); + }); } diff --git a/assets/js/embed.js b/assets/js/embed.js index 7e9ac605..b11b5e5a 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -1,14 +1,7 @@ 'use strict'; var video_data = JSON.parse(document.getElementById('video_data').textContent); -function get_playlist(plid, retries) { - if (retries === undefined) retries = 5; - - if (retries <= 0) { - console.warn('Failed to pull playlist'); - return; - } - +function get_playlist(plid) { var plid_url; if (plid.startsWith('RD')) { plid_url = '/api/v1/mixes/' + plid + @@ -21,85 +14,49 @@ function get_playlist(plid, retries) { '&format=html&hl=' + video_data.preferences.locale; } - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('GET', plid_url, true); + helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, { + on200: function (response) { + if (!response.nextVideo) + return; - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - if (xhr.response.nextVideo) { - player.on('ended', function () { - var url = new URL('https://example.com/embed/' + xhr.response.nextVideo); + player.on('ended', function () { + var url = new URL('https://example.com/embed/' + response.nextVideo); - url.searchParams.set('list', plid); - if (!plid.startsWith('RD')) { - url.searchParams.set('index', xhr.response.index); - } + url.searchParams.set('list', plid); + if (!plid.startsWith('RD')) + url.searchParams.set('index', response.index); + if (video_data.params.autoplay || video_data.params.continue_autoplay) + url.searchParams.set('autoplay', '1'); + if (video_data.params.listen !== video_data.preferences.listen) + url.searchParams.set('listen', video_data.params.listen); + if (video_data.params.speed !== video_data.preferences.speed) + url.searchParams.set('speed', video_data.params.speed); + if (video_data.params.local !== video_data.preferences.local) + url.searchParams.set('local', video_data.params.local); - if (video_data.params.autoplay || video_data.params.continue_autoplay) { - url.searchParams.set('autoplay', '1'); - } - - if (video_data.params.listen !== video_data.preferences.listen) { - url.searchParams.set('listen', video_data.params.listen); - } - - if (video_data.params.speed !== video_data.preferences.speed) { - url.searchParams.set('speed', video_data.params.speed); - } - - if (video_data.params.local !== video_data.preferences.local) { - url.searchParams.set('local', video_data.params.local); - } - - location.assign(url.pathname + url.search); - }); - } - } + location.assign(url.pathname + url.search); + }); } - }; - - xhr.onerror = function () { - console.warn('Pulling playlist failed... ' + retries + '/5'); - setTimeout(function () { get_playlist(plid, retries - 1); }, 1000); - }; - - xhr.ontimeout = function () { - console.warn('Pulling playlist failed... ' + retries + '/5'); - get_playlist(plid, retries - 1); - }; - - xhr.send(); + }); } -window.addEventListener('load', function (e) { +addEventListener('load', function (e) { if (video_data.plid) { get_playlist(video_data.plid); } else if (video_data.video_series) { player.on('ended', function () { var url = new URL('https://example.com/embed/' + video_data.video_series.shift()); - if (video_data.params.autoplay || video_data.params.continue_autoplay) { + if (video_data.params.autoplay || video_data.params.continue_autoplay) url.searchParams.set('autoplay', '1'); - } - - if (video_data.params.listen !== video_data.preferences.listen) { + if (video_data.params.listen !== video_data.preferences.listen) url.searchParams.set('listen', video_data.params.listen); - } - - if (video_data.params.speed !== video_data.preferences.speed) { + if (video_data.params.speed !== video_data.preferences.speed) url.searchParams.set('speed', video_data.params.speed); - } - - if (video_data.params.local !== video_data.preferences.local) { + if (video_data.params.local !== video_data.preferences.local) url.searchParams.set('local', video_data.params.local); - } - - if (video_data.video_series.length !== 0) { + if (video_data.video_series.length !== 0) url.searchParams.set('playlist', video_data.video_series.join(',')); - } location.assign(url.pathname + url.search); }); diff --git a/assets/js/handlers.js b/assets/js/handlers.js index f6617b60..438832b1 100644 --- a/assets/js/handlers.js +++ b/assets/js/handlers.js @@ -1,8 +1,6 @@ 'use strict'; (function () { - var n2a = function (n) { return Array.prototype.slice.call(n); }; - var video_player = document.getElementById('player_html5_api'); if (video_player) { video_player.onmouseenter = function () { video_player['data-title'] = video_player['title']; video_player['title'] = ''; }; @@ -11,8 +9,8 @@ } // For dynamically inserted elements - document.addEventListener('click', function (e) { - if (!e || !e.target) { return; } + addEventListener('click', function (e) { + if (!e || !e.target) return; var t = e.target; var handler_name = t.getAttribute('data-onclick'); @@ -29,6 +27,7 @@ get_youtube_replies(t, load_more, load_replies); break; case 'toggle_parent': + e.preventDefault(); toggle_parent(t); break; default: @@ -36,118 +35,98 @@ } }); - n2a(document.querySelectorAll('[data-mouse="switch_classes"]')).forEach(function (e) { - var classes = e.getAttribute('data-switch-classes').split(','); - var ec = classes[0]; - var lc = classes[1]; - var onoff = function (on, off) { - var cs = e.getAttribute('class'); - cs = cs.split(off).join(on); - e.setAttribute('class', cs); - }; - e.onmouseenter = function () { onoff(ec, lc); }; - e.onmouseleave = function () { onoff(lc, ec); }; + document.querySelectorAll('[data-mouse="switch_classes"]').forEach(function (el) { + var classes = el.getAttribute('data-switch-classes').split(','); + var classOnEnter = classes[0]; + var classOnLeave = classes[1]; + function toggle_classes(toAdd, toRemove) { + el.classList.add(toAdd); + el.classList.remove(toRemove); + } + el.onmouseenter = function () { toggle_classes(classOnEnter, classOnLeave); }; + el.onmouseleave = function () { toggle_classes(classOnLeave, classOnEnter); }; }); - n2a(document.querySelectorAll('[data-onsubmit="return_false"]')).forEach(function (e) { - e.onsubmit = function () { return false; }; + document.querySelectorAll('[data-onsubmit="return_false"]').forEach(function (el) { + el.onsubmit = function () { return false; }; }); - n2a(document.querySelectorAll('[data-onclick="mark_watched"]')).forEach(function (e) { - e.onclick = function () { mark_watched(e); }; + document.querySelectorAll('[data-onclick="mark_watched"]').forEach(function (el) { + el.onclick = function () { mark_watched(el); }; }); - n2a(document.querySelectorAll('[data-onclick="mark_unwatched"]')).forEach(function (e) { - e.onclick = function () { mark_unwatched(e); }; + document.querySelectorAll('[data-onclick="mark_unwatched"]').forEach(function (el) { + el.onclick = function () { mark_unwatched(el); }; }); - n2a(document.querySelectorAll('[data-onclick="add_playlist_video"]')).forEach(function (e) { - e.onclick = function () { add_playlist_video(e); }; + document.querySelectorAll('[data-onclick="add_playlist_video"]').forEach(function (el) { + el.onclick = function () { add_playlist_video(el); }; }); - n2a(document.querySelectorAll('[data-onclick="add_playlist_item"]')).forEach(function (e) { - e.onclick = function () { add_playlist_item(e); }; + document.querySelectorAll('[data-onclick="add_playlist_item"]').forEach(function (el) { + el.onclick = function () { add_playlist_item(el); }; }); - n2a(document.querySelectorAll('[data-onclick="remove_playlist_item"]')).forEach(function (e) { - e.onclick = function () { remove_playlist_item(e); }; + document.querySelectorAll('[data-onclick="remove_playlist_item"]').forEach(function (el) { + el.onclick = function () { remove_playlist_item(el); }; }); - n2a(document.querySelectorAll('[data-onclick="revoke_token"]')).forEach(function (e) { - e.onclick = function () { revoke_token(e); }; + document.querySelectorAll('[data-onclick="revoke_token"]').forEach(function (el) { + el.onclick = function () { revoke_token(el); }; }); - n2a(document.querySelectorAll('[data-onclick="remove_subscription"]')).forEach(function (e) { - e.onclick = function () { remove_subscription(e); }; + document.querySelectorAll('[data-onclick="remove_subscription"]').forEach(function (el) { + el.onclick = function () { remove_subscription(el); }; }); - n2a(document.querySelectorAll('[data-onclick="notification_requestPermission"]')).forEach(function (e) { - e.onclick = function () { Notification.requestPermission(); }; + document.querySelectorAll('[data-onclick="notification_requestPermission"]').forEach(function (el) { + el.onclick = function () { Notification.requestPermission(); }; }); - n2a(document.querySelectorAll('[data-onrange="update_volume_value"]')).forEach(function (e) { - var cb = function () { update_volume_value(e); }; - e.oninput = cb; - e.onchange = cb; + document.querySelectorAll('[data-onrange="update_volume_value"]').forEach(function (el) { + function update_volume_value() { + document.getElementById('volume-value').innerText = el.value; + } + el.oninput = update_volume_value; + el.onchange = update_volume_value; }); - function update_volume_value(element) { - document.getElementById('volume-value').innerText = element.value; - } function revoke_token(target) { var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; row.style.display = 'none'; var count = document.getElementById('count'); - count.innerText = count.innerText - 1; + count.innerText = parseInt(count.innerText) - 1; - var referer = window.encodeURIComponent(document.location.href); var url = '/token_ajax?action_revoke_token=1&redirect=false' + - '&referer=' + referer + + '&referer=' + encodeURIComponent(location.href) + '&session=' + target.getAttribute('data-session'); - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('POST', url, true); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status !== 200) { - count.innerText = parseInt(count.innerText) + 1; - row.style.display = ''; - } + var payload = 'csrf_token=' + target.parentNode.querySelector('input[name="csrf_token"]').value; + + helpers.xhr('POST', url, {payload: payload}, { + onNon200: function (xhr) { + count.innerText = parseInt(count.innerText) + 1; + row.style.display = ''; } - }; - - var csrf_token = target.parentNode.querySelector('input[name="csrf_token"]').value; - xhr.send('csrf_token=' + csrf_token); + }); } function remove_subscription(target) { var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; row.style.display = 'none'; var count = document.getElementById('count'); - count.innerText = count.innerText - 1; + count.innerText = parseInt(count.innerText) - 1; - var referer = window.encodeURIComponent(document.location.href); var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' + - '&referer=' + referer + + '&referer=' + encodeURIComponent(location.href) + '&c=' + target.getAttribute('data-ucid'); - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('POST', url, true); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status !== 200) { - count.innerText = parseInt(count.innerText) + 1; - row.style.display = ''; - } + var payload = 'csrf_token=' + target.parentNode.querySelector('input[name="csrf_token"]').value; + + helpers.xhr('POST', url, {payload: payload}, { + onNon200: function (xhr) { + count.innerText = parseInt(count.innerText) + 1; + row.style.display = ''; } - }; - - var csrf_token = target.parentNode.querySelector('input[name="csrf_token"]').value; - xhr.send('csrf_token=' + csrf_token); + }); } // Handle keypresses - window.addEventListener('keydown', function (event) { + addEventListener('keydown', function (event) { // Ignore modifier keys if (event.ctrlKey || event.metaKey) return; diff --git a/assets/js/notifications.js b/assets/js/notifications.js index ec5f6dd3..f8cc750b 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -2,42 +2,20 @@ var notification_data = JSON.parse(document.getElementById('notification_data').textContent); var notifications, delivered; +var notifications_substitution = { close: function () { } }; -function get_subscriptions(callback, retries) { - if (retries === undefined) retries = 5; - - if (retries <= 0) { - return; - } - - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('GET', '/api/v1/auth/subscriptions?fields=authorId', true); - - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - var subscriptions = xhr.response; - callback(subscriptions); - } - } - }; - - xhr.onerror = function () { - console.warn('Pulling subscriptions failed... ' + retries + '/5'); - setTimeout(function () { get_subscriptions(callback, retries - 1); }, 1000); - }; - - xhr.ontimeout = function () { - console.warn('Pulling subscriptions failed... ' + retries + '/5'); - get_subscriptions(callback, retries - 1); - }; - - xhr.send(); +function get_subscriptions() { + helpers.xhr('GET', '/api/v1/auth/subscriptions?fields=authorId', { + retries: 5, + entity_name: 'subscriptions' + }, { + on200: create_notification_stream + }); } function create_notification_stream(subscriptions) { + // sse.js can't be replaced to EventSource in place as it lack support of payload and headers + // see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource notifications = new SSE( '/api/v1/auth/notifications?fields=videoId,title,author,authorId,publishedText,published,authorThumbnails,liveNow', { withCredentials: true, @@ -49,9 +27,7 @@ function create_notification_stream(subscriptions) { var start_time = Math.round(new Date() / 1000); notifications.onmessage = function (event) { - if (!event.id) { - return; - } + if (!event.id) return; var notification = JSON.parse(event.data); console.info('Got notification:', notification); @@ -67,17 +43,17 @@ function create_notification_stream(subscriptions) { }); system_notification.onclick = function (event) { - window.open('/watch?v=' + event.currentTarget.tag, '_blank'); + open('/watch?v=' + event.currentTarget.tag, '_blank'); }; } delivered.push(notification.videoId); - localStorage.setItem('notification_count', parseInt(localStorage.getItem('notification_count') || '0') + 1); + helpers.storage.set('notification_count', parseInt(helpers.storage.get('notification_count') || '0') + 1); var notification_ticker = document.getElementById('notification_ticker'); - if (parseInt(localStorage.getItem('notification_count')) > 0) { + if (parseInt(helpers.storage.get('notification_count')) > 0) { notification_ticker.innerHTML = - '' + localStorage.getItem('notification_count') + ' '; + '' + helpers.storage.get('notification_count') + ' '; } else { notification_ticker.innerHTML = ''; @@ -91,35 +67,35 @@ function create_notification_stream(subscriptions) { function handle_notification_error(event) { console.warn('Something went wrong with notifications, trying to reconnect...'); - notifications = { close: function () { } }; - setTimeout(function () { get_subscriptions(create_notification_stream); }, 1000); + notifications = notifications_substitution; + setTimeout(get_subscriptions, 1000); } -window.addEventListener('load', function (e) { - localStorage.setItem('notification_count', document.getElementById('notification_count') ? document.getElementById('notification_count').innerText : '0'); +addEventListener('load', function (e) { + helpers.storage.set('notification_count', document.getElementById('notification_count') ? document.getElementById('notification_count').innerText : '0'); - if (localStorage.getItem('stream')) { - localStorage.removeItem('stream'); + if (helpers.storage.get('stream')) { + helpers.storage.remove('stream'); } else { setTimeout(function () { - if (!localStorage.getItem('stream')) { - notifications = { close: function () { } }; - localStorage.setItem('stream', true); - get_subscriptions(create_notification_stream); + if (!helpers.storage.get('stream')) { + notifications = notifications_substitution; + helpers.storage.set('stream', true); + get_subscriptions(); } }, Math.random() * 1000 + 50); } - window.addEventListener('storage', function (e) { + addEventListener('storage', function (e) { if (e.key === 'stream' && !e.newValue) { if (notifications) { - localStorage.setItem('stream', true); + helpers.storage.set('stream', true); } else { setTimeout(function () { - if (!localStorage.getItem('stream')) { - notifications = { close: function () { } }; - localStorage.setItem('stream', true); - get_subscriptions(create_notification_stream); + if (!helpers.storage.get('stream')) { + notifications = notifications_substitution; + helpers.storage.set('stream', true); + get_subscriptions(); } }, Math.random() * 1000 + 50); } @@ -137,8 +113,6 @@ window.addEventListener('load', function (e) { }); }); -window.addEventListener('unload', function (e) { - if (notifications) { - localStorage.removeItem('stream'); - } +addEventListener('unload', function (e) { + if (notifications) helpers.storage.remove('stream'); }); diff --git a/assets/js/player.js b/assets/js/player.js index 6ddb1158..07a5c128 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -42,7 +42,7 @@ embed_url = location.origin + '/embed/' + video_data.id + embed_url.search; var save_player_pos_key = 'save_player_pos'; videojs.Vhs.xhr.beforeRequest = function(options) { - if (options.uri.indexOf('videoplayback') === -1 && options.uri.indexOf('local=true') === -1) { + if (options.uri.includes('videoplayback') && options.uri.includes('local=true')) { options.uri = options.uri + '?local=true'; } return options; @@ -50,37 +50,38 @@ videojs.Vhs.xhr.beforeRequest = function(options) { var player = videojs('player', options); -player.on('error', () => { - if (video_data.params.quality !== 'dash') { - if (!player.currentSrc().includes("local=true") && !video_data.local_disabled) { - var currentSources = player.currentSources(); - for (var i = 0; i < currentSources.length; i++) { - currentSources[i]["src"] += "&local=true" - } - player.src(currentSources) - } - else if (player.error().code === 2 || player.error().code === 4) { - setTimeout(function (event) { - console.log('An error occurred in the player, reloading...'); +player.on('error', function () { + if (video_data.params.quality === 'dash') return; - var currentTime = player.currentTime(); - var playbackRate = player.playbackRate(); - var paused = player.paused(); - - player.load(); - - if (currentTime > 0.5) currentTime -= 0.5; - - player.currentTime(currentTime); - player.playbackRate(playbackRate); - - if (!paused) player.play(); - }, 10000); - } + var localNotDisabled = !player.currentSrc().includes('local=true') && !video_data.local_disabled; + var reloadMakesSense = player.error().code === MediaError.MEDIA_ERR_NETWORK || player.error().code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED; + + if (localNotDisabled) { + // add local=true to all current sources + player.src(player.currentSources().map(function (source) { + source.src += '&local=true'; + })); + } else if (reloadMakesSense) { + setTimeout(function (event) { + console.log('An error occurred in the player, reloading...'); + + var currentTime = player.currentTime(); + var playbackRate = player.playbackRate(); + var paused = player.paused(); + + player.load(); + + if (currentTime > 0.5) currentTime -= 0.5; + + player.currentTime(currentTime); + player.playbackRate(playbackRate); + + if (!paused) player.play(); + }, 10000); } }); -if (video_data.params.quality == 'dash') { +if (video_data.params.quality === 'dash') { player.reloadSourceOnError({ errorInterval: 10 }); @@ -89,7 +90,7 @@ if (video_data.params.quality == 'dash') { /** * Function for add time argument to url * @param {String} url - * @returns urlWithTimeArg + * @returns {URL} urlWithTimeArg */ function addCurrentTimeToURL(url) { var urlUsed = new URL(url); @@ -117,13 +118,6 @@ var shareOptions = { } }; -const storage = (function () { - try { if (localStorage.length !== -1) return localStorage; } - catch (e) { console.info('No storage available: ' + e); } - - return undefined; -})(); - if (location.pathname.startsWith('/embed/')) { var overlay_content = '

' + player_data.title + '

'; player.overlay({ @@ -162,7 +156,7 @@ if (isMobile()) { buttons.forEach(function (child) {primary_control_bar.removeChild(child);}); var operations_bar_element = operations_bar.el(); - operations_bar_element.className += ' mobile-operations-bar'; + operations_bar_element.classList.add('mobile-operations-bar'); player.addChild(operations_bar); // Playback menu doesn't work when it's initialized outside of the primary control bar @@ -175,8 +169,8 @@ if (isMobile()) { operations_bar_element.append(share_element); if (video_data.params.quality === 'dash') { - var http_source_selector = document.getElementsByClassName('vjs-http-source-selector vjs-menu-button')[0]; - operations_bar_element.append(http_source_selector); + var http_source_selector = document.getElementsByClassName('vjs-http-source-selector vjs-menu-button')[0]; + operations_bar_element.append(http_source_selector); } }); } @@ -220,14 +214,14 @@ player.playbackRate(video_data.params.speed); * Method for getting the contents of a cookie * * @param {String} name Name of cookie - * @returns cookieValue + * @returns {String|null} cookieValue */ function getCookieValue(name) { - var value = document.cookie.split(';').filter(function (item) {return item.includes(name + '=');}); - - return (value.length >= 1) - ? value[0].substring((name + '=').length, value[0].length) - : null; + var cookiePrefix = name + '='; + var matchedCookie = document.cookie.split(';').find(function (item) {return item.includes(cookiePrefix);}); + if (matchedCookie) + return matchedCookie.replace(cookiePrefix, ''); + return null; } /** @@ -257,11 +251,11 @@ function updateCookie(newVolume, newSpeed) { date.setTime(date.getTime() + 63115200); var ipRegex = /^((\d+\.){3}\d+|[A-Fa-f0-9]*:[A-Fa-f0-9:]*:[A-Fa-f0-9:]+)$/; - var domainUsed = window.location.hostname; + var domainUsed = location.hostname; // Fix for a bug in FF where the leading dot in the FQDN is not ignored if (domainUsed.charAt(0) !== '.' && !ipRegex.test(domainUsed) && domainUsed !== 'localhost') - domainUsed = '.' + window.location.hostname; + domainUsed = '.' + location.hostname; document.cookie = 'PREFS=' + cookieData + '; SameSite=Strict; path=/; domain=' + domainUsed + '; expires=' + date.toGMTString() + ';'; @@ -280,7 +274,7 @@ player.on('volumechange', function () { player.on('waiting', function () { if (player.playbackRate() > 1 && player.liveTracker.isLive() && player.liveTracker.atLiveEdge()) { - console.info('Player has caught up to source, resetting playbackRate.'); + console.info('Player has caught up to source, resetting playbackRate'); player.playbackRate(1); } }); @@ -292,12 +286,12 @@ if (video_data.premiere_timestamp && Math.round(new Date() / 1000) < video_data. if (video_data.params.save_player_pos) { const url = new URL(location); const hasTimeParam = url.searchParams.has('t'); - const remeberedTime = get_video_time(); + const rememberedTime = get_video_time(); let lastUpdated = 0; - if(!hasTimeParam) set_seconds_after_start(remeberedTime); + if(!hasTimeParam) set_seconds_after_start(rememberedTime); - const updateTime = function () { + player.on('timeupdate', function () { const raw = player.currentTime(); const time = Math.floor(raw); @@ -305,9 +299,7 @@ if (video_data.params.save_player_pos) { save_video_time(time); lastUpdated = time; } - }; - - player.on('timeupdate', updateTime); + }); } else remove_all_video_times(); @@ -347,53 +339,31 @@ if (!video_data.params.listen && video_data.params.quality === 'dash') { targetQualityLevel = 0; break; default: - const targetHeight = Number.parseInt(video_data.params.quality_dash, 10); + const targetHeight = parseInt(video_data.params.quality_dash, 10); for (let i = 0; i < qualityLevels.length; i++) { - if (qualityLevels[i].height <= targetHeight) { + if (qualityLevels[i].height <= targetHeight) targetQualityLevel = i; - } else { + else break; - } } } - for (let i = 0; i < qualityLevels.length; i++) { - qualityLevels[i].enabled = (i === targetQualityLevel); - } + qualityLevels.forEach(function (level, index) { + level.enabled = (index === targetQualityLevel); + }); }); }); } } player.vttThumbnails({ - src: location.origin + '/api/v1/storyboards/' + video_data.id + '?height=90', + src: '/api/v1/storyboards/' + video_data.id + '?height=90', showTimestamp: true }); // Enable annotations if (!video_data.params.listen && video_data.params.annotations) { - window.addEventListener('load', function (e) { - var video_container = document.getElementById('player'); - let xhr = new XMLHttpRequest(); - xhr.responseType = 'text'; - xhr.timeout = 60000; - xhr.open('GET', '/api/v1/annotations/' + video_data.id, true); - - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - videojs.registerPlugin('youtubeAnnotationsPlugin', youtubeAnnotationsPlugin); - if (!player.paused()) { - player.youtubeAnnotationsPlugin({ annotationXml: xhr.response, videoContainer: video_container }); - } else { - player.one('play', function (event) { - player.youtubeAnnotationsPlugin({ annotationXml: xhr.response, videoContainer: video_container }); - }); - } - } - } - }; - - window.addEventListener('__ar_annotation_click', function (e) { + addEventListener('load', function (e) { + addEventListener('__ar_annotation_click', function (e) { const url = e.detail.url, target = e.detail.target, seconds = e.detail.seconds; @@ -406,41 +376,48 @@ if (!video_data.params.listen && video_data.params.annotations) { path = path.pathname + path.search; if (target === 'current') { - window.location.href = path; + location.href = path; } else if (target === 'new') { - window.open(path, '_blank'); + open(path, '_blank'); + } + }); + + helpers.xhr('GET', '/api/v1/annotations/' + video_data.id, { + responseType: 'text', + timeout: 60000 + }, { + on200: function (response) { + var video_container = document.getElementById('player'); + videojs.registerPlugin('youtubeAnnotationsPlugin', youtubeAnnotationsPlugin); + if (player.paused()) { + player.one('play', function (event) { + player.youtubeAnnotationsPlugin({ annotationXml: response, videoContainer: video_container }); + }); + } else { + player.youtubeAnnotationsPlugin({ annotationXml: response, videoContainer: video_container }); + } } }); - xhr.send(); }); } function increase_volume(delta) { const curVolume = player.volume(); - let newVolume = curVolume + delta; - if (newVolume > 1) { - newVolume = 1; - } else if (newVolume < 0) { - newVolume = 0; - } + const newVolume = curVolume + delta; + helpers.clamp(newVolume, 0, 1); player.volume(newVolume); } function toggle_muted() { - const isMuted = player.muted(); - player.muted(!isMuted); + player.muted(!player.muted()); } function skip_seconds(delta) { const duration = player.duration(); const curTime = player.currentTime(); - let newTime = curTime + delta; - if (newTime > duration) { - newTime = duration; - } else if (newTime < 0) { - newTime = 0; - } + const newTime = curTime + delta; + helpers.clamp(newTime, 0, duration); player.currentTime(newTime); } @@ -455,52 +432,24 @@ function save_video_time(seconds) { all_video_times[videoId] = seconds; - set_all_video_times(all_video_times); + helpers.storage.set(save_player_pos_key, JSON.stringify(all_video_times)); } function get_video_time() { - try { - const videoId = video_data.id; - const all_video_times = get_all_video_times(); - const timestamp = all_video_times[videoId]; + const videoId = video_data.id; + const all_video_times = get_all_video_times(); + const timestamp = all_video_times[videoId]; - return timestamp || 0; - } - catch (e) { - return 0; - } -} - -function set_all_video_times(times) { - if (storage) { - if (times) { - try { - storage.setItem(save_player_pos_key, JSON.stringify(times)); - } catch (e) { - console.warn('set_all_video_times: ' + e); - } - } else { - storage.removeItem(save_player_pos_key); - } - } + return timestamp || 0; } function get_all_video_times() { - if (storage) { - const raw = storage.getItem(save_player_pos_key); - if (raw !== null) { - try { - return JSON.parse(raw); - } catch (e) { - console.warn('get_all_video_times: ' + e); - } - } - } - return {}; + const raw = helpers.storage.get(save_player_pos_key); + return raw ? JSON.parse(raw) : {}; } function remove_all_video_times() { - set_all_video_times(null); + helpers.storage.remove(save_player_pos_key); } function set_time_percent(percent) { @@ -516,21 +465,23 @@ function toggle_play() { player.paused() ? play() : pause(); } const toggle_captions = (function () { let toggledTrack = null; - const onChange = function (e) { - toggledTrack = null; - }; - const bindChange = function (onOrOff) { - player.textTracks()[onOrOff]('change', onChange); - }; + + function bindChange(onOrOff) { + player.textTracks()[onOrOff]('change', function (e) { + toggledTrack = null; + }); + } + // Wrapper function to ignore our own emitted events and only listen // to events emitted by Video.js on click on the captions menu items. - const setMode = function (track, mode) { + function setMode(track, mode) { bindChange('off'); track.mode = mode; - window.setTimeout(function () { + setTimeout(function () { bindChange('on'); }, 0); - }; + } + bindChange('on'); return function () { if (toggledTrack !== null) { @@ -577,16 +528,12 @@ function toggle_fullscreen() { function increase_playback_rate(steps) { const maxIndex = options.playbackRates.length - 1; const curIndex = options.playbackRates.indexOf(player.playbackRate()); - let newIndex = curIndex + steps; - if (newIndex > maxIndex) { - newIndex = maxIndex; - } else if (newIndex < 0) { - newIndex = 0; - } + const newIndex = curIndex + steps; + helpers.clamp(newIndex, 0, maxIndex); player.playbackRate(options.playbackRates[newIndex]); } -window.addEventListener('keydown', function (e) { +addEventListener('keydown', function (e) { if (e.target.tagName.toLowerCase() === 'input') { // Ignore input when focus is on certain elements, e.g. form fields. return; @@ -673,12 +620,11 @@ window.addEventListener('keydown', function (e) { // TODO: Add support to play back previous video. break; - case '.': - // TODO: Add support for next-frame-stepping. - break; - case ',': - // TODO: Add support for previous-frame-stepping. - break; + // TODO: More precise step. Now FPS is taken equal to 29.97 + // Common FPS: https://forum.videohelp.com/threads/81868#post323588 + // Possible solution is new HTMLVideoElement.requestVideoFrameCallback() https://wicg.github.io/video-rvfc/ + case '.': action = function () { pause(); skip_seconds(-1/29.97); }; break; + case ',': action = function () { pause(); skip_seconds( 1/29.97); }; break; case '>': action = increase_playback_rate.bind(this, 1); break; case '<': action = increase_playback_rate.bind(this, -1); break; @@ -697,10 +643,6 @@ window.addEventListener('keydown', function (e) { // Add support for controlling the player volume by scrolling over it. Adapted from // https://github.com/ctd1500/videojs-hotkeys/blob/bb4a158b2e214ccab87c2e7b95f42bc45c6bfd87/videojs.hotkeys.js#L292-L328 (function () { - const volumeStep = 0.05; - const enableVolumeScroll = true; - const enableHoverScroll = true; - const doc = document; const pEl = document.getElementById('player'); var volumeHover = false; @@ -710,39 +652,23 @@ window.addEventListener('keydown', function (e) { volumeSelector.onmouseout = function () { volumeHover = false; }; } - var mouseScroll = function mouseScroll(event) { - var activeEl = doc.activeElement; - if (enableHoverScroll) { - // If we leave this undefined then it can match non-existent elements below - activeEl = 0; - } - + function mouseScroll(event) { // When controls are disabled, hotkeys will be disabled as well - if (player.controls()) { - if (volumeHover) { - if (enableVolumeScroll) { - event = window.event || event; - var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail))); - event.preventDefault(); + if (!player.controls() || !volumeHover) return; - if (delta === 1) { - increase_volume(volumeStep); - } else if (delta === -1) { - increase_volume(-volumeStep); - } - } - } - } - }; + event.preventDefault(); + var wheelMove = event.wheelDelta || -event.detail; + var volumeSign = Math.sign(wheelMove); + + increase_volume(volumeSign * 0.05); // decrease/increase by 5% + } player.on('mousewheel', mouseScroll); player.on('DOMMouseScroll', mouseScroll); }()); // Since videojs-share can sometimes be blocked, we defer it until last -if (player.share) { - player.share(shareOptions); -} +if (player.share) player.share(shareOptions); // show the preferred caption by default if (player_data.preferred_caption_found) { @@ -763,7 +689,7 @@ if (navigator.vendor === 'Apple Computer, Inc.' && video_data.params.listen) { } // Watch on Invidious link -if (window.location.pathname.startsWith('/embed/')) { +if (location.pathname.startsWith('/embed/')) { const Button = videojs.getComponent('Button'); let watch_on_invidious_button = new Button(player); diff --git a/assets/js/playlist_widget.js b/assets/js/playlist_widget.js index c2565874..8f8da6d5 100644 --- a/assets/js/playlist_widget.js +++ b/assets/js/playlist_widget.js @@ -1,5 +1,6 @@ 'use strict'; var playlist_data = JSON.parse(document.getElementById('playlist_data').textContent); +var payload = 'csrf_token=' + playlist_data.csrf_token; function add_playlist_video(target) { var select = target.parentNode.children[0].children[1]; @@ -8,21 +9,12 @@ function add_playlist_video(target) { var url = '/playlist_ajax?action_add_video=1&redirect=false' + '&video_id=' + target.getAttribute('data-id') + '&playlist_id=' + option.getAttribute('data-plid'); - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('POST', url, true); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - option.innerText = '✓' + option.innerText; - } + helpers.xhr('POST', url, {payload: payload}, { + on200: function (response) { + option.innerText = '✓' + option.innerText; } - }; - - xhr.send('csrf_token=' + playlist_data.csrf_token); + }); } function add_playlist_item(target) { @@ -32,21 +24,12 @@ function add_playlist_item(target) { var url = '/playlist_ajax?action_add_video=1&redirect=false' + '&video_id=' + target.getAttribute('data-id') + '&playlist_id=' + target.getAttribute('data-plid'); - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('POST', url, true); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status !== 200) { - tile.style.display = ''; - } + helpers.xhr('POST', url, {payload: payload}, { + onNon200: function (xhr) { + tile.style.display = ''; } - }; - - xhr.send('csrf_token=' + playlist_data.csrf_token); + }); } function remove_playlist_item(target) { @@ -56,19 +39,10 @@ function remove_playlist_item(target) { var url = '/playlist_ajax?action_remove_video=1&redirect=false' + '&set_video_id=' + target.getAttribute('data-index') + '&playlist_id=' + target.getAttribute('data-plid'); - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('POST', url, true); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status !== 200) { - tile.style.display = ''; - } + helpers.xhr('POST', url, {payload: payload}, { + onNon200: function (xhr) { + tile.style.display = ''; } - }; - - xhr.send('csrf_token=' + playlist_data.csrf_token); + }); } diff --git a/assets/js/subscribe_widget.js b/assets/js/subscribe_widget.js index 45ff5706..7665a00b 100644 --- a/assets/js/subscribe_widget.js +++ b/assets/js/subscribe_widget.js @@ -1,8 +1,9 @@ 'use strict'; var subscribe_data = JSON.parse(document.getElementById('subscribe_data').textContent); +var payload = 'csrf_token=' + subscribe_data.csrf_token; var subscribe_button = document.getElementById('subscribe'); -subscribe_button.parentNode['action'] = 'javascript:void(0)'; +subscribe_button.parentNode.action = 'javascript:void(0)'; if (subscribe_button.getAttribute('data-type') === 'subscribe') { subscribe_button.onclick = subscribe; @@ -10,87 +11,34 @@ if (subscribe_button.getAttribute('data-type') === 'subscribe') { subscribe_button.onclick = unsubscribe; } -function subscribe(retries) { - if (retries === undefined) retries = 5; - - if (retries <= 0) { - console.warn('Failed to subscribe.'); - return; - } - - var url = '/subscription_ajax?action_create_subscription_to_channel=1&redirect=false' + - '&c=' + subscribe_data.ucid; - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('POST', url, true); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - +function subscribe() { var fallback = subscribe_button.innerHTML; subscribe_button.onclick = unsubscribe; subscribe_button.innerHTML = '' + subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text + ''; - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status !== 200) { - subscribe_button.onclick = subscribe; - subscribe_button.innerHTML = fallback; - } + var url = '/subscription_ajax?action_create_subscription_to_channel=1&redirect=false' + + '&c=' + subscribe_data.ucid; + + helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'subscribe request'}, { + onNon200: function (xhr) { + subscribe_button.onclick = subscribe; + subscribe_button.innerHTML = fallback; } - }; - - xhr.onerror = function () { - console.warn('Subscribing failed... ' + retries + '/5'); - setTimeout(function () { subscribe(retries - 1); }, 1000); - }; - - xhr.ontimeout = function () { - console.warn('Subscribing failed... ' + retries + '/5'); - subscribe(retries - 1); - }; - - xhr.send('csrf_token=' + subscribe_data.csrf_token); + }); } -function unsubscribe(retries) { - if (retries === undefined) - retries = 5; - - if (retries <= 0) { - console.warn('Failed to subscribe'); - return; - } - - var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' + - '&c=' + subscribe_data.ucid; - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('POST', url, true); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - +function unsubscribe() { var fallback = subscribe_button.innerHTML; subscribe_button.onclick = subscribe; subscribe_button.innerHTML = '' + subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text + ''; - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status !== 200) { - subscribe_button.onclick = unsubscribe; - subscribe_button.innerHTML = fallback; - } + var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' + + '&c=' + subscribe_data.ucid; + + helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'unsubscribe request'}, { + onNon200: function (xhr) { + subscribe_button.onclick = unsubscribe; + subscribe_button.innerHTML = fallback; } - }; - - xhr.onerror = function () { - console.warn('Unsubscribing failed... ' + retries + '/5'); - setTimeout(function () { unsubscribe(retries - 1); }, 1000); - }; - - xhr.ontimeout = function () { - console.warn('Unsubscribing failed... ' + retries + '/5'); - unsubscribe(retries - 1); - }; - - xhr.send('csrf_token=' + subscribe_data.csrf_token); + }); } diff --git a/assets/js/themes.js b/assets/js/themes.js index 3f503b38..7e86e9ac 100644 --- a/assets/js/themes.js +++ b/assets/js/themes.js @@ -1,60 +1,48 @@ 'use strict'; var toggle_theme = document.getElementById('toggle_theme'); -toggle_theme.href = 'javascript:void(0);'; +toggle_theme.href = 'javascript:void(0)'; toggle_theme.addEventListener('click', function () { var dark_mode = document.body.classList.contains('light-theme'); - var url = '/toggle_theme?redirect=false'; - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('GET', url, true); - set_mode(dark_mode); - try { - window.localStorage.setItem('dark_mode', dark_mode ? 'dark' : 'light'); - } catch (e) {} + helpers.storage.set('dark_mode', dark_mode ? 'dark' : 'light'); - xhr.send(); + helpers.xhr('GET', '/toggle_theme?redirect=false', {}, {}); }); -window.addEventListener('storage', function (e) { +// Handles theme change event caused by other tab +addEventListener('storage', function (e) { if (e.key === 'dark_mode') { update_mode(e.newValue); } }); -window.addEventListener('DOMContentLoaded', function () { +addEventListener('DOMContentLoaded', function () { const dark_mode = document.getElementById('dark_mode_pref').textContent; - try { - // Update localStorage if dark mode preference changed on preferences page - window.localStorage.setItem('dark_mode', dark_mode); - } catch (e) {} + // Update storage if dark mode preference changed on preferences page + helpers.storage.set('dark_mode', dark_mode); update_mode(dark_mode); }); -var darkScheme = window.matchMedia('(prefers-color-scheme: dark)'); -var lightScheme = window.matchMedia('(prefers-color-scheme: light)'); +var darkScheme = matchMedia('(prefers-color-scheme: dark)'); +var lightScheme = matchMedia('(prefers-color-scheme: light)'); darkScheme.addListener(scheme_switch); lightScheme.addListener(scheme_switch); function scheme_switch (e) { - // ignore this method if we have a preference set - try { - if (localStorage.getItem('dark_mode')) { - return; - } - } catch (exception) {} - if (e.matches) { + // ignore this method if we have a preference set + if (helpers.storage.get('dark_mode')) return; + + if (!e.matches) return; + if (e.media.includes('dark')) { - set_mode(true); + set_mode(true); } else if (e.media.includes('light')) { - set_mode(false); + set_mode(false); } - } } function set_mode (bool) { @@ -82,7 +70,7 @@ function update_mode (mode) { // If preference for light mode indicated set_mode(false); } - else if (document.getElementById('dark_mode_pref').textContent === '' && window.matchMedia('(prefers-color-scheme: dark)').matches) { + else if (document.getElementById('dark_mode_pref').textContent === '' && matchMedia('(prefers-color-scheme: dark)').matches) { // If no preference indicated here and no preference indicated on the preferences page (backend), but the browser tells us that the operating system has a dark theme set_mode(true); } diff --git a/assets/js/watch.js b/assets/js/watch.js index 29d58be5..ff0f7822 100644 --- a/assets/js/watch.js +++ b/assets/js/watch.js @@ -1,5 +1,7 @@ 'use strict'; var video_data = JSON.parse(document.getElementById('video_data').textContent); +var spinnerHTML = '

'; +var spinnerHTMLwithHR = spinnerHTML + '
'; String.prototype.supplant = function (o) { return this.replace(/{([^{}]*)}/g, function (a, b) { @@ -10,24 +12,24 @@ String.prototype.supplant = function (o) { function toggle_parent(target) { var body = target.parentNode.parentNode.children[1]; - if (body.style.display === null || body.style.display === '') { - target.textContent = '[ + ]'; - body.style.display = 'none'; - } else { + if (body.style.display === 'none') { target.textContent = '[ − ]'; body.style.display = ''; + } else { + target.textContent = '[ + ]'; + body.style.display = 'none'; } } function toggle_comments(event) { var target = event.target; var body = target.parentNode.parentNode.parentNode.children[1]; - if (body.style.display === null || body.style.display === '') { - target.textContent = '[ + ]'; - body.style.display = 'none'; - } else { + if (body.style.display === 'none') { target.textContent = '[ − ]'; body.style.display = ''; + } else { + target.textContent = '[ + ]'; + body.style.display = 'none'; } } @@ -79,31 +81,22 @@ if (continue_button) { function next_video() { var url = new URL('https://example.com/watch?v=' + video_data.next_video); - if (video_data.params.autoplay || video_data.params.continue_autoplay) { + if (video_data.params.autoplay || video_data.params.continue_autoplay) url.searchParams.set('autoplay', '1'); - } - - if (video_data.params.listen !== video_data.preferences.listen) { + if (video_data.params.listen !== video_data.preferences.listen) url.searchParams.set('listen', video_data.params.listen); - } - - if (video_data.params.speed !== video_data.preferences.speed) { + if (video_data.params.speed !== video_data.preferences.speed) url.searchParams.set('speed', video_data.params.speed); - } - - if (video_data.params.local !== video_data.preferences.local) { + if (video_data.params.local !== video_data.preferences.local) url.searchParams.set('local', video_data.params.local); - } - url.searchParams.set('continue', '1'); + location.assign(url.pathname + url.search); } function continue_autoplay(event) { if (event.target.checked) { - player.on('ended', function () { - next_video(); - }); + player.on('ended', next_video); } else { player.off('ended'); } @@ -116,19 +109,10 @@ function number_with_separator(val) { return val; } -function get_playlist(plid, retries) { - if (retries === undefined) retries = 5; +function get_playlist(plid) { var playlist = document.getElementById('playlist'); - if (retries <= 0) { - console.warn('Failed to pull playlist'); - playlist.innerHTML = ''; - return; - } - - playlist.innerHTML = ' \ -

\ -
'; + playlist.innerHTML = spinnerHTMLwithHR; var plid_url; if (plid.startsWith('RD')) { @@ -142,225 +126,144 @@ function get_playlist(plid, retries) { '&format=html&hl=' + video_data.preferences.locale; } - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('GET', plid_url, true); + helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, { + on200: function (response) { + playlist.innerHTML = response.playlistHtml; - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - playlist.innerHTML = xhr.response.playlistHtml; - var nextVideo = document.getElementById(xhr.response.nextVideo); - nextVideo.parentNode.parentNode.scrollTop = nextVideo.offsetTop; + if (!response.nextVideo) return; - if (xhr.response.nextVideo) { - player.on('ended', function () { - var url = new URL('https://example.com/watch?v=' + xhr.response.nextVideo); + var nextVideo = document.getElementById(response.nextVideo); + nextVideo.parentNode.parentNode.scrollTop = nextVideo.offsetTop; - url.searchParams.set('list', plid); - if (!plid.startsWith('RD')) { - url.searchParams.set('index', xhr.response.index); - } + player.on('ended', function () { + var url = new URL('https://example.com/watch?v=' + response.nextVideo); - if (video_data.params.autoplay || video_data.params.continue_autoplay) { - url.searchParams.set('autoplay', '1'); - } + url.searchParams.set('list', plid); + if (!plid.startsWith('RD')) + url.searchParams.set('index', response.index); + if (video_data.params.autoplay || video_data.params.continue_autoplay) + url.searchParams.set('autoplay', '1'); + if (video_data.params.listen !== video_data.preferences.listen) + url.searchParams.set('listen', video_data.params.listen); + if (video_data.params.speed !== video_data.preferences.speed) + url.searchParams.set('speed', video_data.params.speed); + if (video_data.params.local !== video_data.preferences.local) + url.searchParams.set('local', video_data.params.local); - if (video_data.params.listen !== video_data.preferences.listen) { - url.searchParams.set('listen', video_data.params.listen); - } - - if (video_data.params.speed !== video_data.preferences.speed) { - url.searchParams.set('speed', video_data.params.speed); - } - - if (video_data.params.local !== video_data.preferences.local) { - url.searchParams.set('local', video_data.params.local); - } - - location.assign(url.pathname + url.search); - }); - } - } else { - playlist.innerHTML = ''; - document.getElementById('continue').style.display = ''; - } + location.assign(url.pathname + url.search); + }); + }, + onNon200: function (xhr) { + playlist.innerHTML = ''; + document.getElementById('continue').style.display = ''; + }, + onError: function (xhr) { + playlist.innerHTML = spinnerHTMLwithHR; + }, + onTimeout: function (xhr) { + playlist.innerHTML = spinnerHTMLwithHR; } - }; - - xhr.onerror = function () { - playlist = document.getElementById('playlist'); - playlist.innerHTML = - '


'; - - console.warn('Pulling playlist timed out... ' + retries + '/5'); - setTimeout(function () { get_playlist(plid, retries - 1); }, 1000); - }; - - xhr.ontimeout = function () { - playlist = document.getElementById('playlist'); - playlist.innerHTML = - '


'; - - console.warn('Pulling playlist timed out... ' + retries + '/5'); - get_playlist(plid, retries - 1); - }; - - xhr.send(); + }); } -function get_reddit_comments(retries) { - if (retries === undefined) retries = 5; +function get_reddit_comments() { var comments = document.getElementById('comments'); - if (retries <= 0) { - console.warn('Failed to pull comments'); - comments.innerHTML = ''; - return; - } - var fallback = comments.innerHTML; - comments.innerHTML = - '

'; + comments.innerHTML = spinnerHTML; var url = '/api/v1/comments/' + video_data.id + '?source=reddit&format=html' + '&hl=' + video_data.preferences.locale; - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('GET', url, true); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - comments.innerHTML = ' \ -
\ -

\ - [ − ] \ - {title} \ -

\ -

\ - \ - \ - {youtubeCommentsText} \ - \ - \ -

\ + var onNon200 = function (xhr) { comments.innerHTML = fallback; }; + if (video_data.params.comments[1] === 'youtube') + onNon200 = function (xhr) {}; + + helpers.xhr('GET', url, {retries: 5, entity_name: ''}, { + on200: function (response) { + comments.innerHTML = ' \ +
\ +

\ + [ − ] \ + {title} \ +

\ +

\ \ - {redditPermalinkText} \ + \ + {youtubeCommentsText} \ + \ \ -

\ -
{contentHtml}
\ -
'.supplant({ - title: xhr.response.title, - youtubeCommentsText: video_data.youtube_comments_text, - redditPermalinkText: video_data.reddit_permalink_text, - permalink: xhr.response.permalink, - contentHtml: xhr.response.contentHtml - }); +

\ + \ + {redditPermalinkText} \ + \ +
\ +
{contentHtml}
\ +
'.supplant({ + title: response.title, + youtubeCommentsText: video_data.youtube_comments_text, + redditPermalinkText: video_data.reddit_permalink_text, + permalink: response.permalink, + contentHtml: response.contentHtml + }); - comments.children[0].children[0].children[0].onclick = toggle_comments; - comments.children[0].children[1].children[0].onclick = swap_comments; - } else { - if (video_data.params.comments[1] === 'youtube') { - console.warn('Pulling comments failed... ' + retries + '/5'); - setTimeout(function () { get_youtube_comments(retries - 1); }, 1000); - } else { - comments.innerHTML = fallback; - } - } - } - }; - - xhr.onerror = function () { - console.warn('Pulling comments failed... ' + retries + '/5'); - setTimeout(function () { get_reddit_comments(retries - 1); }, 1000); - }; - - xhr.ontimeout = function () { - console.warn('Pulling comments failed... ' + retries + '/5'); - get_reddit_comments(retries - 1); - }; - - xhr.send(); + comments.children[0].children[0].children[0].onclick = toggle_comments; + comments.children[0].children[1].children[0].onclick = swap_comments; + }, + onNon200: onNon200, // declared above + }); } -function get_youtube_comments(retries) { - if (retries === undefined) retries = 5; +function get_youtube_comments() { var comments = document.getElementById('comments'); - if (retries <= 0) { - console.warn('Failed to pull comments'); - comments.innerHTML = ''; - return; - } - var fallback = comments.innerHTML; - comments.innerHTML = - '

'; + comments.innerHTML = spinnerHTML; var url = '/api/v1/comments/' + video_data.id + '?format=html' + '&hl=' + video_data.preferences.locale + '&thin_mode=' + video_data.preferences.thin_mode; - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('GET', url, true); + + var onNon200 = function (xhr) { comments.innerHTML = fallback; }; + if (video_data.params.comments[1] === 'youtube') + onNon200 = function (xhr) {}; + + helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, { + on200: function (response) { + comments.innerHTML = ' \ +
\ +

\ + [ − ] \ + {commentsText} \ +

\ + \ + \ + {redditComments} \ + \ + \ +
\ +
{contentHtml}
\ +
'.supplant({ + contentHtml: response.contentHtml, + redditComments: video_data.reddit_comments_text, + commentsText: video_data.comments_text.supplant( + { commentCount: number_with_separator(response.commentCount) } + ) + }); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - comments.innerHTML = ' \ -
\ -

\ - [ − ] \ - {commentsText} \ -

\ - \ - \ - {redditComments} \ - \ - \ -
\ -
{contentHtml}
\ -
'.supplant({ - contentHtml: xhr.response.contentHtml, - redditComments: video_data.reddit_comments_text, - commentsText: video_data.comments_text.supplant( - { commentCount: number_with_separator(xhr.response.commentCount) } - ) - }); - - comments.children[0].children[0].children[0].onclick = toggle_comments; - comments.children[0].children[1].children[0].onclick = swap_comments; - } else { - if (video_data.params.comments[1] === 'youtube') { - setTimeout(function () { get_youtube_comments(retries - 1); }, 1000); - } else { - comments.innerHTML = ''; - } - } + comments.children[0].children[0].children[0].onclick = toggle_comments; + comments.children[0].children[1].children[0].onclick = swap_comments; + }, + onNon200: onNon200, // declared above + onError: function (xhr) { + comments.innerHTML = spinnerHTML; + }, + onTimeout: function (xhr) { + comments.innerHTML = spinnerHTML; } - }; - - xhr.onerror = function () { - comments.innerHTML = - '

'; - console.warn('Pulling comments failed... ' + retries + '/5'); - setTimeout(function () { get_youtube_comments(retries - 1); }, 1000); - }; - - xhr.ontimeout = function () { - comments.innerHTML = - '

'; - console.warn('Pulling comments failed... ' + retries + '/5'); - get_youtube_comments(retries - 1); - }; - - xhr.send(); + }); } function get_youtube_replies(target, load_more, load_replies) { @@ -368,91 +271,72 @@ function get_youtube_replies(target, load_more, load_replies) { var body = target.parentNode.parentNode; var fallback = body.innerHTML; - body.innerHTML = - '

'; + body.innerHTML = spinnerHTML; var url = '/api/v1/comments/' + video_data.id + '?format=html' + '&hl=' + video_data.preferences.locale + '&thin_mode=' + video_data.preferences.thin_mode + '&continuation=' + continuation; - if (load_replies) { - url += '&action=action_get_comment_replies'; - } - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('GET', url, true); + if (load_replies) url += '&action=action_get_comment_replies'; - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - if (load_more) { - body = body.parentNode.parentNode; - body.removeChild(body.lastElementChild); - body.innerHTML += xhr.response.contentHtml; - } else { - body.removeChild(body.lastElementChild); - - var p = document.createElement('p'); - var a = document.createElement('a'); - p.appendChild(a); - - a.href = 'javascript:void(0)'; - a.onclick = hide_youtube_replies; - a.setAttribute('data-sub-text', video_data.hide_replies_text); - a.setAttribute('data-inner-text', video_data.show_replies_text); - a.innerText = video_data.hide_replies_text; - - var div = document.createElement('div'); - div.innerHTML = xhr.response.contentHtml; - - body.appendChild(p); - body.appendChild(div); - } + helpers.xhr('GET', url, {}, { + on200: function (response) { + if (load_more) { + body = body.parentNode.parentNode; + body.removeChild(body.lastElementChild); + body.innerHTML += response.contentHtml; } else { - body.innerHTML = fallback; + body.removeChild(body.lastElementChild); + + var p = document.createElement('p'); + var a = document.createElement('a'); + p.appendChild(a); + + a.href = 'javascript:void(0)'; + a.onclick = hide_youtube_replies; + a.setAttribute('data-sub-text', video_data.hide_replies_text); + a.setAttribute('data-inner-text', video_data.show_replies_text); + a.innerText = video_data.hide_replies_text; + + var div = document.createElement('div'); + div.innerHTML = response.contentHtml; + + body.appendChild(p); + body.appendChild(div); } + }, + onNon200: function (xhr) { + body.innerHTML = fallback; + }, + onTimeout: function (xhr) { + console.warn('Pulling comments failed'); + body.innerHTML = fallback; } - }; - - xhr.ontimeout = function () { - console.warn('Pulling comments failed.'); - body.innerHTML = fallback; - }; - - xhr.send(); + }); } if (video_data.play_next) { player.on('ended', function () { var url = new URL('https://example.com/watch?v=' + video_data.next_video); - if (video_data.params.autoplay || video_data.params.continue_autoplay) { + if (video_data.params.autoplay || video_data.params.continue_autoplay) url.searchParams.set('autoplay', '1'); - } - - if (video_data.params.listen !== video_data.preferences.listen) { + if (video_data.params.listen !== video_data.preferences.listen) url.searchParams.set('listen', video_data.params.listen); - } - - if (video_data.params.speed !== video_data.preferences.speed) { + if (video_data.params.speed !== video_data.preferences.speed) url.searchParams.set('speed', video_data.params.speed); - } - - if (video_data.params.local !== video_data.preferences.local) { + if (video_data.params.local !== video_data.preferences.local) url.searchParams.set('local', video_data.params.local); - } - url.searchParams.set('continue', '1'); + location.assign(url.pathname + url.search); }); } -window.addEventListener('load', function (e) { - if (video_data.plid) { +addEventListener('load', function (e) { + if (video_data.plid) get_playlist(video_data.plid); - } if (video_data.params.comments[0] === 'youtube') { get_youtube_comments(); diff --git a/assets/js/watched_widget.js b/assets/js/watched_widget.js index 87989a79..497b1878 100644 --- a/assets/js/watched_widget.js +++ b/assets/js/watched_widget.js @@ -1,5 +1,6 @@ 'use strict'; var watched_data = JSON.parse(document.getElementById('watched_data').textContent); +var payload = 'csrf_token=' + watched_data.csrf_token; function mark_watched(target) { var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; @@ -7,45 +8,27 @@ function mark_watched(target) { var url = '/watch_ajax?action_mark_watched=1&redirect=false' + '&id=' + target.getAttribute('data-id'); - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('POST', url, true); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status !== 200) { - tile.style.display = ''; - } + helpers.xhr('POST', url, {payload: payload}, { + onNon200: function (xhr) { + tile.style.display = ''; } - }; - - xhr.send('csrf_token=' + watched_data.csrf_token); + }); } function mark_unwatched(target) { var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; tile.style.display = 'none'; var count = document.getElementById('count'); - count.innerText = count.innerText - 1; + count.innerText = parseInt(count.innerText) - 1; var url = '/watch_ajax?action_mark_unwatched=1&redirect=false' + '&id=' + target.getAttribute('data-id'); - var xhr = new XMLHttpRequest(); - xhr.responseType = 'json'; - xhr.timeout = 10000; - xhr.open('POST', url, true); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status !== 200) { - count.innerText = count.innerText - 1 + 2; - tile.style.display = ''; - } + helpers.xhr('POST', url, {payload: payload}, { + onNon200: function (xhr) { + count.innerText = parseInt(count.innerText) + 1; + tile.style.display = ''; } - }; - - xhr.send('csrf_token=' + watched_data.csrf_token); + }); } diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 1f8de657..f50b5907 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -481,7 +481,7 @@ def template_reddit_comments(root, locale) html << <<-END_HTML

- [ - ] + [ − ] #{child.author} #{translate_count(locale, "comments_points_count", child.score, NumberFormatting::Separator)} #{translate(locale, "`x` ago", recode_date(child.created_utc, locale))} diff --git a/src/invidious/views/add_playlist_items.ecr b/src/invidious/views/add_playlist_items.ecr index 22870317..758f3995 100644 --- a/src/invidious/views/add_playlist_items.ecr +++ b/src/invidious/views/add_playlist_items.ecr @@ -29,6 +29,7 @@ }.to_pretty_json %> +

diff --git a/src/invidious/views/community.ecr b/src/invidious/views/community.ecr index 3bc29e55..154c40b5 100644 --- a/src/invidious/views/community.ecr +++ b/src/invidious/views/community.ecr @@ -93,4 +93,5 @@ }.to_pretty_json %> + diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr index fffefc9a..483807d7 100644 --- a/src/invidious/views/components/player.ecr +++ b/src/invidious/views/components/player.ecr @@ -66,4 +66,5 @@ }.to_pretty_json %> + diff --git a/src/invidious/views/components/subscribe_widget.ecr b/src/invidious/views/components/subscribe_widget.ecr index b9d5f783..7a8c7fda 100644 --- a/src/invidious/views/components/subscribe_widget.ecr +++ b/src/invidious/views/components/subscribe_widget.ecr @@ -31,6 +31,7 @@ }.to_pretty_json %> + <% else %>

diff --git a/src/invidious/views/embed.ecr b/src/invidious/views/embed.ecr index ce5ff7f0..82f80f9d 100644 --- a/src/invidious/views/embed.ecr +++ b/src/invidious/views/embed.ecr @@ -31,6 +31,7 @@ <%= rendered "components/player" %> + diff --git a/src/invidious/views/feeds/history.ecr b/src/invidious/views/feeds/history.ecr index 6c1243c5..51dd78bd 100644 --- a/src/invidious/views/feeds/history.ecr +++ b/src/invidious/views/feeds/history.ecr @@ -25,6 +25,7 @@ }.to_pretty_json %> +

diff --git a/src/invidious/views/feeds/subscriptions.ecr b/src/invidious/views/feeds/subscriptions.ecr index 8d56ad14..957277fa 100644 --- a/src/invidious/views/feeds/subscriptions.ecr +++ b/src/invidious/views/feeds/subscriptions.ecr @@ -50,6 +50,7 @@ }.to_pretty_json %> +
diff --git a/src/invidious/views/licenses.ecr b/src/invidious/views/licenses.ecr index 861913d0..25b24ed4 100644 --- a/src/invidious/views/licenses.ecr +++ b/src/invidious/views/licenses.ecr @@ -9,6 +9,20 @@

<%= translate(locale, "JavaScript license information") %>

+ + + + + + + +
+ _helpers.js + + AGPL-3.0 + + <%= translate(locale, "source") %> +
community.js diff --git a/src/invidious/views/playlist.ecr b/src/invidious/views/playlist.ecr index df3112db..641cbe2c 100644 --- a/src/invidious/views/playlist.ecr +++ b/src/invidious/views/playlist.ecr @@ -97,6 +97,7 @@ }.to_pretty_json %> + <% end %> diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr index bd908dd6..79decbe6 100644 --- a/src/invidious/views/template.ecr +++ b/src/invidious/views/template.ecr @@ -157,6 +157,7 @@
+ <% if env.get? "user" %> diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 8b6eb903..e6a14d0f 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -165,6 +165,7 @@ we're going to need to do it here in order to allow for translations. }.to_pretty_json %> + <% end %> <% end %> @@ -303,4 +304,5 @@ we're going to need to do it here in order to allow for translations. <% end %> + From 835237382fd2e316a5e118dafc929f6ffb8e33fd Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 6 May 2022 06:16:41 +0300 Subject: [PATCH 02/17] fix helpers --- assets/js/_helpers.js | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 04576348..4583dbe3 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -25,7 +25,7 @@ Math.sign = Math.sign || function(x) { }; // Monstrous global variable for handy code -helpers = helpers || { +window.helpers = window.helpers || { /** * https://en.wikipedia.org/wiki/Clamping_(graphics) * @param {Number} num Source number @@ -38,9 +38,9 @@ helpers = helpers || { var t = max; max = min; min = t; // swap max and min } - if (max > num) + if (max < num) return max; - if (min < num) + if (min > num) return min; return num; }, @@ -62,14 +62,17 @@ helpers = helpers || { if (method === 'POST') xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.onreadystatechange = function () { - if (xhr.readyState === XMLHttpRequest.DONE) { - if (xhr.status === 200) - if (callbacks.on200) - callbacks.on200(xhr.response); - else - if (callbacks.onNon200) - callbacks.onNon200(xhr); + // better than onreadystatechange because of 404 codes https://stackoverflow.com/a/36182963 + xhr.onloadend = function () { + if (xhr.status === 200) { + if (callbacks.on200) + callbacks.on200(xhr.response); + } else { + // handled by onerror + if (xhr.status === 0) return; + + if (callbacks.onNon200) + callbacks.onNon200(xhr); } }; @@ -89,14 +92,14 @@ helpers = helpers || { xhr.send(); }, /** @private */ - _xhrRetry(method, url, options, callbacks) { + _xhrRetry: function(method, url, options, callbacks) { if (options.retries <= 0) { console.warn('Failed to pull', options.entity_name); if (callbacks.onTotalFail) callbacks.onTotalFail(); return; } - helpers.xhr(method, url, options, callbacks); + helpers._xhr(method, url, options, callbacks); }, /** * @callback callbackXhrOn200 @@ -123,18 +126,19 @@ helpers = helpers || { * @param {callbackXhrError} [callbacks.onError] * @param {callbackXhrError} [callbacks.onTotalFail] - if failed after all retries */ - xhr(method, url, options, callbacks) { - if (options.retries > 1) { + xhr: function(method, url, options, callbacks) { + if (!options.retries || options.retries <= 1) { helpers._xhr(method, url, options, callbacks); return; } if (!options.entity_name) options.entity_name = 'unknown'; - if (!options.retry_timeout) options.retry_timeout = 1; + if (!options.retry_timeout) options.retry_timeout = 1000; const retries_total = options.retries; + let currentTry = 1; const retry = function () { - console.warn('Pulling ' + options.entity_name + ' failed... ' + options.retries + '/' + retries_total); + console.warn('Pulling ' + options.entity_name + ' failed... ' + (currentTry++) + '/' + retries_total); setTimeout(function () { options.retries--; helpers._xhrRetry(method, url, options, callbacks); From fd890f9c0a78635a3ea1ab56ec5a735fef27c1c4 Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 6 May 2022 07:21:19 +0300 Subject: [PATCH 03/17] fix helpers storage --- assets/js/_helpers.js | 28 +++++++++++----------------- assets/js/notifications.js | 9 +++++++-- assets/js/player.js | 5 ++--- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 4583dbe3..838a4612 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -171,7 +171,7 @@ window.helpers = window.helpers || { */ /** - * Universal storage proxy. Uses inside localStorage or cookies + * Universal storage, stores and returns JS objects. Uses inside localStorage or cookies * @type {invidiousStorage} */ storage: (function () { @@ -181,8 +181,8 @@ window.helpers = window.helpers || { if (localStorageIsUsable) { return { - get: function (key) { return localStorage[key]; }, - set: function (key, value) { localStorage[key] = value; }, + get: function (key) { if (localStorage[key]) return JSON.parse(decodeURIComponent(localStorage[key])); }, + set: function (key, value) { localStorage[key] = encodeURIComponent(JSON.stringify(value)); }, remove: function (key) { localStorage.removeItem(key); } }; } @@ -192,27 +192,21 @@ window.helpers = window.helpers || { get: function (key) { const cookiePrefix = key + '='; function findCallback(cookie) {return cookie.startsWith(cookiePrefix);} - const matchedCookie = document.cookie.split(';').find(findCallback); - if (matchedCookie) - return matchedCookie.replace(cookiePrefix, ''); - return null; + const matchedCookie = document.cookie.split('; ').find(findCallback); + if (matchedCookie) { + const cookieBody = matchedCookie.replace(cookiePrefix, ''); + if (cookieBody.length === 0) return; + return JSON.parse(decodeURIComponent(cookieBody)); + } }, set: function (key, value) { const cookie_data = encodeURIComponent(JSON.stringify(value)); // Set expiration in 2 year const date = new Date(); - date.setTime(date.getTime() + 2*365.25*24*60*60); + date.setFullYear(date.getFullYear()+2); - const ip_regex = /^((\d+\.){3}\d+|[A-Fa-f0-9]*:[A-Fa-f0-9:]*:[A-Fa-f0-9:]+)$/; - let domain_used = location.hostname; - - // Fix for a bug in FF where the leading dot in the FQDN is not ignored - if (domain_used.charAt(0) !== '.' && !ip_regex.test(domain_used) && domain_used !== 'localhost') - domain_used = '.' + location.hostname; - - document.cookie = key + '=' + cookie_data + '; SameSite=Strict; path=/; domain=' + - domain_used + '; expires=' + date.toGMTString() + ';'; + document.cookie = key + '=' + cookie_data + '; expires=' + date.toGMTString(); }, remove: function (key) { document.cookie = key + '=; Max-Age=0'; diff --git a/assets/js/notifications.js b/assets/js/notifications.js index f8cc750b..568f5ff6 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -48,7 +48,7 @@ function create_notification_stream(subscriptions) { } delivered.push(notification.videoId); - helpers.storage.set('notification_count', parseInt(helpers.storage.get('notification_count') || '0') + 1); + helpers.storage.set('notification_count', (helpers.storage.get('notification_count') || 0) + 1); var notification_ticker = document.getElementById('notification_ticker'); if (parseInt(helpers.storage.get('notification_count')) > 0) { @@ -72,7 +72,12 @@ function handle_notification_error(event) { } addEventListener('load', function (e) { - helpers.storage.set('notification_count', document.getElementById('notification_count') ? document.getElementById('notification_count').innerText : '0'); + var notification_count = document.getElementById('notification_count'); + if (notification_count) { + helpers.storage.set('notification_count', parseInt(notification_count.innerText)); + } else { + helpers.storage.set('notification_count', 0); + } if (helpers.storage.get('stream')) { helpers.storage.remove('stream'); diff --git a/assets/js/player.js b/assets/js/player.js index 07a5c128..5bff7ee5 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -432,7 +432,7 @@ function save_video_time(seconds) { all_video_times[videoId] = seconds; - helpers.storage.set(save_player_pos_key, JSON.stringify(all_video_times)); + helpers.storage.set(save_player_pos_key, all_video_times); } function get_video_time() { @@ -444,8 +444,7 @@ function get_video_time() { } function get_all_video_times() { - const raw = helpers.storage.get(save_player_pos_key); - return raw ? JSON.parse(raw) : {}; + return helpers.storage.get(save_player_pos_key) || {}; } function remove_all_video_times() { From f06d5b973b1bc5aa1c00ff330ae5efeaf83bc5d1 Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 6 May 2022 07:42:15 +0300 Subject: [PATCH 04/17] jsdoc type fix --- assets/js/_helpers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 838a4612..ad7dcb91 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -165,9 +165,9 @@ window.helpers = window.helpers || { /** * @typedef {Object} invidiousStorage - * @property {(key:String) => Object|null} get - * @property {(key:String, value:Object) => null} set - * @property {(key:String) => null} remove + * @property {(key:String) => Object} get + * @property {(key:String, value:Object)} set + * @property {(key:String)} remove */ /** From fd66084388399319993e9aaa0a15ba3ed5498404 Mon Sep 17 00:00:00 2001 From: meow Date: Sun, 15 May 2022 08:38:46 +0300 Subject: [PATCH 05/17] js code rewrite. Themes rewritten, bugs fixed --- assets/js/_helpers.js | 6 ++-- assets/js/player.js | 15 ++++---- assets/js/themes.js | 82 +++++++++++++++---------------------------- assets/js/watch.js | 17 ++++----- 4 files changed, 46 insertions(+), 74 deletions(-) diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index ad7dcb91..3f79bf54 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -145,16 +145,14 @@ window.helpers = window.helpers || { }, options.retry_timeout); }; - if (callbacks.onError) - callbacks._onError = callbacks.onError; + callbacks._onError = callbacks.onError; callbacks.onError = function (xhr) { if (callbacks._onError) callbacks._onError(); retry(); }; - if (callbacks.onTimeout) - callbacks._onTimeout = callbacks.onTimeout; + callbacks._onTimeout = callbacks.onTimeout; callbacks.onTimeout = function (xhr) { if (callbacks._onTimeout) callbacks._onTimeout(); diff --git a/assets/js/player.js b/assets/js/player.js index 5bff7ee5..832c7d0e 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -42,9 +42,10 @@ embed_url = location.origin + '/embed/' + video_data.id + embed_url.search; var save_player_pos_key = 'save_player_pos'; videojs.Vhs.xhr.beforeRequest = function(options) { - if (options.uri.includes('videoplayback') && options.uri.includes('local=true')) { - options.uri = options.uri + '?local=true'; - } + // set local if requested not videoplayback + if (!options.uri.includes('videoplayback')) + if (!options.uri.includes('local=true')) + options.uri += '?local=true'; return options; }; @@ -402,7 +403,7 @@ if (!video_data.params.listen && video_data.params.annotations) { }); } -function increase_volume(delta) { +function change_volume(delta) { const curVolume = player.volume(); const newVolume = curVolume + delta; helpers.clamp(newVolume, 0, 1); @@ -565,10 +566,10 @@ addEventListener('keydown', function (e) { case 'MediaStop': action = stop; break; case 'ArrowUp': - if (isPlayerFocused) action = increase_volume.bind(this, 0.1); + if (isPlayerFocused) action = change_volume.bind(this, 0.1); break; case 'ArrowDown': - if (isPlayerFocused) action = increase_volume.bind(this, -0.1); + if (isPlayerFocused) action = change_volume.bind(this, -0.1); break; case 'm': @@ -659,7 +660,7 @@ addEventListener('keydown', function (e) { var wheelMove = event.wheelDelta || -event.detail; var volumeSign = Math.sign(wheelMove); - increase_volume(volumeSign * 0.05); // decrease/increase by 5% + change_volume(volumeSign * 0.05); // decrease/increase by 5% } player.on('mousewheel', mouseScroll); diff --git a/assets/js/themes.js b/assets/js/themes.js index 7e86e9ac..eedf63a4 100644 --- a/assets/js/themes.js +++ b/assets/js/themes.js @@ -2,58 +2,39 @@ var toggle_theme = document.getElementById('toggle_theme'); toggle_theme.href = 'javascript:void(0)'; +const STORAGE_KEY_THEME = 'dark_mode'; +const THEME_DARK = 'dark'; +const THEME_LIGHT = 'light'; +const THEME_SYSTEM = ''; + +// TODO: theme state controlled by system toggle_theme.addEventListener('click', function () { - var dark_mode = document.body.classList.contains('light-theme'); - - set_mode(dark_mode); - helpers.storage.set('dark_mode', dark_mode ? 'dark' : 'light'); - + const isDarkTheme = helpers.storage.get(STORAGE_KEY_THEME) === THEME_DARK; + setTheme(isDarkTheme ? THEME_LIGHT : THEME_DARK); helpers.xhr('GET', '/toggle_theme?redirect=false', {}, {}); }); -// Handles theme change event caused by other tab -addEventListener('storage', function (e) { - if (e.key === 'dark_mode') { - update_mode(e.newValue); - } -}); -addEventListener('DOMContentLoaded', function () { - const dark_mode = document.getElementById('dark_mode_pref').textContent; - // Update storage if dark mode preference changed on preferences page - helpers.storage.set('dark_mode', dark_mode); - update_mode(dark_mode); +// Ask system about dark theme +var systemDarkTheme = matchMedia('(prefers-color-scheme: dark)'); +systemDarkTheme.addListener(function () { + // Ignore system events if theme set manually + if (!helpers.storage.get(STORAGE_KEY_THEME)) + setTheme(THEME_SYSTEM); }); -var darkScheme = matchMedia('(prefers-color-scheme: dark)'); -var lightScheme = matchMedia('(prefers-color-scheme: light)'); +/** @param {THEME_DARK|THEME_LIGHT|THEME_SYSTEM} theme */ +function setTheme(theme) { + if (theme !== THEME_SYSTEM) + helpers.storage.set(STORAGE_KEY_THEME, theme); -darkScheme.addListener(scheme_switch); -lightScheme.addListener(scheme_switch); - -function scheme_switch (e) { - // ignore this method if we have a preference set - if (helpers.storage.get('dark_mode')) return; - - if (!e.matches) return; - - if (e.media.includes('dark')) { - set_mode(true); - } else if (e.media.includes('light')) { - set_mode(false); - } -} - -function set_mode (bool) { - if (bool) { - // dark + if (theme === THEME_DARK || (theme === THEME_SYSTEM && systemDarkTheme.matches)) { toggle_theme.children[0].setAttribute('class', 'icon ion-ios-sunny'); document.body.classList.remove('no-theme'); document.body.classList.remove('light-theme'); document.body.classList.add('dark-theme'); } else { - // light toggle_theme.children[0].setAttribute('class', 'icon ion-ios-moon'); document.body.classList.remove('no-theme'); document.body.classList.remove('dark-theme'); @@ -61,18 +42,13 @@ function set_mode (bool) { } } -function update_mode (mode) { - if (mode === 'true' /* for backwards compatibility */ || mode === 'dark') { - // If preference for dark mode indicated - set_mode(true); - } - else if (mode === 'false' /* for backwards compatibility */ || mode === 'light') { - // If preference for light mode indicated - set_mode(false); - } - else if (document.getElementById('dark_mode_pref').textContent === '' && matchMedia('(prefers-color-scheme: dark)').matches) { - // If no preference indicated here and no preference indicated on the preferences page (backend), but the browser tells us that the operating system has a dark theme - set_mode(true); - } - // else do nothing, falling back to the mode defined by the `dark_mode` preference on the preferences page (backend) -} +// Handles theme change event caused by other tab +addEventListener('storage', function (e) { + if (e.key === STORAGE_KEY_THEME) setTheme(e.newValue); +}); + +// Set theme from preferences on page load +addEventListener('DOMContentLoaded', function () { + const prefTheme = document.getElementById('dark_mode_pref').textContent; + setTheme(prefTheme); +}); diff --git a/assets/js/watch.js b/assets/js/watch.js index ff0f7822..45492241 100644 --- a/assets/js/watch.js +++ b/assets/js/watch.js @@ -102,13 +102,6 @@ function continue_autoplay(event) { } } -function number_with_separator(val) { - while (/(\d+)(\d{3})/.test(val.toString())) { - val = val.toString().replace(/(\d+)(\d{3})/, '$1' + ',' + '$2'); - } - return val; -} - function get_playlist(plid) { var playlist = document.getElementById('playlist'); @@ -248,9 +241,13 @@ function get_youtube_comments() {
'.supplant({ contentHtml: response.contentHtml, redditComments: video_data.reddit_comments_text, - commentsText: video_data.comments_text.supplant( - { commentCount: number_with_separator(response.commentCount) } - ) + commentsText: video_data.comments_text.supplant({ + // toLocaleString correctly splits number with local thousands separator. e.g.: + // '1,234,567.89' for user with English locale + // '1 234 567,89' for user with Russian locale + // '1.234.567,89' for user with Portuguese locale + commentCount: response.commentCount.toLocaleString() + }) }); comments.children[0].children[0].children[0].onclick = toggle_comments; From e18b10297b259460a3219edfeb9ccd0fabc34270 Mon Sep 17 00:00:00 2001 From: meow Date: Mon, 16 May 2022 13:13:00 +0300 Subject: [PATCH 06/17] JS fixes: recursion in themes, keys for frame walking, JSON XHR and details-summary in IE11 --- assets/js/_helpers.js | 23 +++++++++++++++++++++-- assets/js/player.js | 4 ++-- assets/js/themes.js | 11 ++++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 3f79bf54..448e95d1 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -23,6 +23,20 @@ Math.sign = Math.sign || function(x) { if (!x) return x; // 0 and NaN return x > 0 ? 1 : -1; }; +if (!window.hasOwnProperty('HTMLDetailsElement') && !window.hasOwnProperty('mockHTMLDetailsElement')) { + window.mockHTMLDetailsElement = true; + const style = 'details:not([open]) > :not(summary) {display: none}'; + document.head.appendChild(document.createElement('style')).textContent = style; + + addEventListener('click', function (e) { + if (e.target.nodeName !== 'SUMMARY') return; + const details = e.target.parentElement; + if (details.hasAttribute('open')) + details.removeAttribute('open'); + else + details.setAttribute('open', ''); + }); +} // Monstrous global variable for handy code window.helpers = window.helpers || { @@ -65,8 +79,13 @@ window.helpers = window.helpers || { // better than onreadystatechange because of 404 codes https://stackoverflow.com/a/36182963 xhr.onloadend = function () { if (xhr.status === 200) { - if (callbacks.on200) - callbacks.on200(xhr.response); + if (callbacks.on200) { + // fix for IE11. It doesn't convert response to JSON + if (xhr.responseType === '' && typeof(xhr.response) === 'string') + callbacks.on200(JSON.parse(xhr.response)); + else + callbacks.on200(xhr.response); + } } else { // handled by onerror if (xhr.status === 0) return; diff --git a/assets/js/player.js b/assets/js/player.js index 832c7d0e..e2bd2df1 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -623,8 +623,8 @@ addEventListener('keydown', function (e) { // TODO: More precise step. Now FPS is taken equal to 29.97 // Common FPS: https://forum.videohelp.com/threads/81868#post323588 // Possible solution is new HTMLVideoElement.requestVideoFrameCallback() https://wicg.github.io/video-rvfc/ - case '.': action = function () { pause(); skip_seconds(-1/29.97); }; break; - case ',': action = function () { pause(); skip_seconds( 1/29.97); }; break; + case ',': action = function () { pause(); skip_seconds(-1/29.97); }; break; + case '.': action = function () { pause(); skip_seconds( 1/29.97); }; break; case '>': action = increase_playback_rate.bind(this, 1); break; case '<': action = increase_playback_rate.bind(this, -1); break; diff --git a/assets/js/themes.js b/assets/js/themes.js index eedf63a4..029d7c5d 100644 --- a/assets/js/themes.js +++ b/assets/js/themes.js @@ -10,7 +10,9 @@ const THEME_SYSTEM = ''; // TODO: theme state controlled by system toggle_theme.addEventListener('click', function () { const isDarkTheme = helpers.storage.get(STORAGE_KEY_THEME) === THEME_DARK; - setTheme(isDarkTheme ? THEME_LIGHT : THEME_DARK); + const newTheme = isDarkTheme ? THEME_LIGHT : THEME_DARK; + setTheme(newTheme); + helpers.storage.set(STORAGE_KEY_THEME, newTheme); helpers.xhr('GET', '/toggle_theme?redirect=false', {}, {}); }); @@ -26,9 +28,6 @@ systemDarkTheme.addListener(function () { /** @param {THEME_DARK|THEME_LIGHT|THEME_SYSTEM} theme */ function setTheme(theme) { - if (theme !== THEME_SYSTEM) - helpers.storage.set(STORAGE_KEY_THEME, theme); - if (theme === THEME_DARK || (theme === THEME_SYSTEM && systemDarkTheme.matches)) { toggle_theme.children[0].setAttribute('class', 'icon ion-ios-sunny'); document.body.classList.remove('no-theme'); @@ -44,11 +43,13 @@ function setTheme(theme) { // Handles theme change event caused by other tab addEventListener('storage', function (e) { - if (e.key === STORAGE_KEY_THEME) setTheme(e.newValue); + if (e.key === STORAGE_KEY_THEME) + setTheme(helpers.storage.get(STORAGE_KEY_THEME)); }); // Set theme from preferences on page load addEventListener('DOMContentLoaded', function () { const prefTheme = document.getElementById('dark_mode_pref').textContent; setTheme(prefTheme); + helpers.storage.set(STORAGE_KEY_THEME, prefTheme); }); From 2dead1a19b650155371ab3fa5296d60650868d90 Mon Sep 17 00:00:00 2001 From: meow Date: Mon, 16 May 2022 13:51:28 +0300 Subject: [PATCH 07/17] JS theme switching simplified --- assets/js/themes.js | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/assets/js/themes.js b/assets/js/themes.js index 029d7c5d..76767d5f 100644 --- a/assets/js/themes.js +++ b/assets/js/themes.js @@ -5,7 +5,6 @@ toggle_theme.href = 'javascript:void(0)'; const STORAGE_KEY_THEME = 'dark_mode'; const THEME_DARK = 'dark'; const THEME_LIGHT = 'light'; -const THEME_SYSTEM = ''; // TODO: theme state controlled by system toggle_theme.addEventListener('click', function () { @@ -16,28 +15,16 @@ toggle_theme.addEventListener('click', function () { helpers.xhr('GET', '/toggle_theme?redirect=false', {}, {}); }); - -// Ask system about dark theme -var systemDarkTheme = matchMedia('(prefers-color-scheme: dark)'); -systemDarkTheme.addListener(function () { - // Ignore system events if theme set manually - if (!helpers.storage.get(STORAGE_KEY_THEME)) - setTheme(THEME_SYSTEM); -}); - - -/** @param {THEME_DARK|THEME_LIGHT|THEME_SYSTEM} theme */ +/** @param {THEME_DARK|THEME_LIGHT} theme */ function setTheme(theme) { - if (theme === THEME_DARK || (theme === THEME_SYSTEM && systemDarkTheme.matches)) { - toggle_theme.children[0].setAttribute('class', 'icon ion-ios-sunny'); - document.body.classList.remove('no-theme'); - document.body.classList.remove('light-theme'); - document.body.classList.add('dark-theme'); + // By default body element has .no-theme class that uses OS theme via CSS @media rules + // It rewrites using hard className below + if (theme === THEME_DARK) { + toggle_theme.children[0].className = 'icon ion-ios-sunny'; + document.body.className = 'dark-theme'; } else { - toggle_theme.children[0].setAttribute('class', 'icon ion-ios-moon'); - document.body.classList.remove('no-theme'); - document.body.classList.remove('dark-theme'); - document.body.classList.add('light-theme'); + toggle_theme.children[0].className = 'icon ion-ios-moon'; + document.body.className = 'light-theme'; } } @@ -50,6 +37,8 @@ addEventListener('storage', function (e) { // Set theme from preferences on page load addEventListener('DOMContentLoaded', function () { const prefTheme = document.getElementById('dark_mode_pref').textContent; - setTheme(prefTheme); - helpers.storage.set(STORAGE_KEY_THEME, prefTheme); + if (prefTheme) { + setTheme(prefTheme); + helpers.storage.set(STORAGE_KEY_THEME, prefTheme); + } }); From 2ea423032e977145f9dbdd2f566616312b8af6b5 Mon Sep 17 00:00:00 2001 From: meow Date: Tue, 17 May 2022 09:43:05 +0300 Subject: [PATCH 08/17] Share video regression. Single quotes are required --- assets/js/player.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/js/player.js b/assets/js/player.js index e2bd2df1..38746787 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -114,8 +114,9 @@ var shareOptions = { description: player_data.description, image: player_data.thumbnail, get embedCode() { - return ''; + // Single quotes inside here required. HTML inserted as is into value attribute of input + return ""; } }; From 17e62134487aaa0fa5ede651f43616dd0fe20a52 Mon Sep 17 00:00:00 2001 From: meow Date: Tue, 17 May 2022 10:03:07 +0300 Subject: [PATCH 09/17] Less player reload timeout --- assets/js/player.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/js/player.js b/assets/js/player.js index 38746787..e8c18e21 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -63,9 +63,10 @@ player.on('error', function () { source.src += '&local=true'; })); } else if (reloadMakesSense) { - setTimeout(function (event) { - console.log('An error occurred in the player, reloading...'); + setTimeout(function () { + console.warn('An error occurred in the player, reloading...'); + // After load() all parameters are reset. Save them var currentTime = player.currentTime(); var playbackRate = player.playbackRate(); var paused = player.paused(); @@ -76,9 +77,8 @@ player.on('error', function () { player.currentTime(currentTime); player.playbackRate(playbackRate); - if (!paused) player.play(); - }, 10000); + }, 5000); } }); From 1097648f0a675215594f52c0e1b1f97975a07f39 Mon Sep 17 00:00:00 2001 From: meow Date: Tue, 17 May 2022 10:09:01 +0300 Subject: [PATCH 10/17] Fix HTML validation. This is how browser really split tags --- src/invidious/views/watch.ecr | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index e6a14d0f..f2d8ba03 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -279,24 +279,24 @@ we're going to need to do it here in order to allow for translations. <% end %>

<%= rv["title"] %>

-
-
- <% if rv["ucid"]? %> - "><%= rv["author"]? %><% if rv["author_verified"]? == "true" %> <% end %> - <% else %> - <%= rv["author"]? %><% if rv["author_verified"]? == "true" %> <% end %> - <% end %> -
- -
- <%= - views = rv["view_count"]?.try &.to_i? - views ||= rv["view_count_short"]?.try { |x| short_text_to_number(x) } - translate_count(locale, "generic_views_count", views || 0, NumberFormatting::Short) - %> -
-
+
+
+ <% if rv["ucid"]? %> + "><%= rv["author"]? %><% if rv["author_verified"]? == "true" %> <% end %> + <% else %> + <%= rv["author"]? %><% if rv["author_verified"]? == "true" %> <% end %> + <% end %> +
+ +
+ <%= + views = rv["view_count"]?.try &.to_i? + views ||= rv["view_count_short"]?.try { |x| short_text_to_number(x) } + translate_count(locale, "generic_views_count", views || 0, NumberFormatting::Short) + %> +
+
<% end %> <% end %> From 319bbd2f8113a775990895fff952a0228fb8f9e1 Mon Sep 17 00:00:00 2001 From: AHOHNMYC <24810600+AHOHNMYC@users.noreply.github.com> Date: Thu, 19 May 2022 07:15:17 +0300 Subject: [PATCH 11/17] JS code minor formatting Co-authored-by: Samantaz Fox --- assets/js/player.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/assets/js/player.js b/assets/js/player.js index e8c18e21..d09892cb 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -54,8 +54,13 @@ var player = videojs('player', options); player.on('error', function () { if (video_data.params.quality === 'dash') return; - var localNotDisabled = !player.currentSrc().includes('local=true') && !video_data.local_disabled; - var reloadMakesSense = player.error().code === MediaError.MEDIA_ERR_NETWORK || player.error().code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED; + var localNotDisabled = ( + !player.currentSrc().includes('local=true') && !video_data.local_disabled + ); + var reloadMakesSense = ( + player.error().code === MediaError.MEDIA_ERR_NETWORK || + player.error().code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED + ); if (localNotDisabled) { // add local=true to all current sources From b72b917af239c46dbe3d4592fc8b215e63703459 Mon Sep 17 00:00:00 2001 From: meow Date: Sat, 21 May 2022 13:35:41 +0300 Subject: [PATCH 12/17] handled invalid values in storage partial rewrite notifications.js innerText to textContent fixed bug with clamping --- assets/js/_helpers.js | 30 +++++-- assets/js/community.js | 2 +- assets/js/handlers.js | 10 +-- assets/js/notifications.js | 159 ++++++++++++++++++----------------- assets/js/player.js | 28 +++--- assets/js/playlist_widget.js | 2 +- assets/js/watch.js | 2 +- assets/js/watched_widget.js | 4 +- 8 files changed, 128 insertions(+), 109 deletions(-) diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 448e95d1..0bd99a8c 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -39,6 +39,7 @@ if (!window.hasOwnProperty('HTMLDetailsElement') && !window.hasOwnProperty('mock } // Monstrous global variable for handy code +// Includes: clamp, xhr, storage.{get,set,remove} window.helpers = window.helpers || { /** * https://en.wikipedia.org/wiki/Clamping_(graphics) @@ -164,19 +165,20 @@ window.helpers = window.helpers || { }, options.retry_timeout); }; + // Pack retry() call into error handlers callbacks._onError = callbacks.onError; callbacks.onError = function (xhr) { if (callbacks._onError) - callbacks._onError(); + callbacks._onError(xhr); retry(); }; - callbacks._onTimeout = callbacks.onTimeout; callbacks.onTimeout = function (xhr) { if (callbacks._onTimeout) - callbacks._onTimeout(); + callbacks._onTimeout(xhr); retry(); - }; + }; + helpers._xhrRetry(method, url, options, callbacks); }, @@ -198,13 +200,22 @@ window.helpers = window.helpers || { if (localStorageIsUsable) { return { - get: function (key) { if (localStorage[key]) return JSON.parse(decodeURIComponent(localStorage[key])); }, + get: function (key) { + if (!localStorage[key]) return; + try { + return JSON.parse(decodeURIComponent(localStorage[key])); + } catch(e) { + // Erase non parsable value + helpers.storage.remove(key); + } + }, set: function (key, value) { localStorage[key] = encodeURIComponent(JSON.stringify(value)); }, remove: function (key) { localStorage.removeItem(key); } }; } - console.info('Storage: localStorage is disabled or unaccessible trying cookies'); + // TODO: fire 'storage' event for cookies + console.info('Storage: localStorage is disabled or unaccessible. Cookies used as fallback'); return { get: function (key) { const cookiePrefix = key + '='; @@ -213,7 +224,12 @@ window.helpers = window.helpers || { if (matchedCookie) { const cookieBody = matchedCookie.replace(cookiePrefix, ''); if (cookieBody.length === 0) return; - return JSON.parse(decodeURIComponent(cookieBody)); + try { + return JSON.parse(decodeURIComponent(cookieBody)); + } catch(e) { + // Erase non parsable value + helpers.storage.remove(key); + } } }, set: function (key, value) { diff --git a/assets/js/community.js b/assets/js/community.js index 33e2e3ed..608dc971 100644 --- a/assets/js/community.js +++ b/assets/js/community.js @@ -62,7 +62,7 @@ function get_youtube_replies(target, load_more) { a.onclick = hide_youtube_replies; a.setAttribute('data-sub-text', community_data.hide_replies_text); a.setAttribute('data-inner-text', community_data.show_replies_text); - a.innerText = community_data.hide_replies_text; + a.textContent = community_data.hide_replies_text; var div = document.createElement('div'); div.innerHTML = response.contentHtml; diff --git a/assets/js/handlers.js b/assets/js/handlers.js index 438832b1..29810e72 100644 --- a/assets/js/handlers.js +++ b/assets/js/handlers.js @@ -78,7 +78,7 @@ document.querySelectorAll('[data-onrange="update_volume_value"]').forEach(function (el) { function update_volume_value() { - document.getElementById('volume-value').innerText = el.value; + document.getElementById('volume-value').textContent = el.value; } el.oninput = update_volume_value; el.onchange = update_volume_value; @@ -89,7 +89,7 @@ var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; row.style.display = 'none'; var count = document.getElementById('count'); - count.innerText = parseInt(count.innerText) - 1; + count.textContent--; var url = '/token_ajax?action_revoke_token=1&redirect=false' + '&referer=' + encodeURIComponent(location.href) + @@ -99,7 +99,7 @@ helpers.xhr('POST', url, {payload: payload}, { onNon200: function (xhr) { - count.innerText = parseInt(count.innerText) + 1; + count.textContent++; row.style.display = ''; } }); @@ -109,7 +109,7 @@ var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; row.style.display = 'none'; var count = document.getElementById('count'); - count.innerText = parseInt(count.innerText) - 1; + count.textContent--; var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' + '&referer=' + encodeURIComponent(location.href) + @@ -119,7 +119,7 @@ helpers.xhr('POST', url, {payload: payload}, { onNon200: function (xhr) { - count.innerText = parseInt(count.innerText) + 1; + count.textContent++; row.style.display = ''; } }); diff --git a/assets/js/notifications.js b/assets/js/notifications.js index 568f5ff6..7a30375d 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -1,8 +1,13 @@ 'use strict'; var notification_data = JSON.parse(document.getElementById('notification_data').textContent); +/** Boolean meaning 'some tab have stream' */ +const STORAGE_KEY_STREAM = 'stream'; +/** Number of notifications. May be increased or reset */ +const STORAGE_KEY_NOTIF_COUNT = 'notification_count'; + var notifications, delivered; -var notifications_substitution = { close: function () { } }; +var notifications_mock = { close: function () { } }; function get_subscriptions() { helpers.xhr('GET', '/api/v1/auth/subscriptions?fields=authorId', { @@ -32,92 +37,96 @@ function create_notification_stream(subscriptions) { var notification = JSON.parse(event.data); console.info('Got notification:', notification); - if (start_time < notification.published && !delivered.includes(notification.videoId)) { - if (Notification.permission === 'granted') { - var system_notification = - new Notification((notification.liveNow ? notification_data.live_now_text : notification_data.upload_text).replace('`x`', notification.author), { - body: notification.title, - icon: '/ggpht' + new URL(notification.authorThumbnails[2].url).pathname, - img: '/ggpht' + new URL(notification.authorThumbnails[4].url).pathname, - tag: notification.videoId - }); + // Ignore not actual and delivered notifications + if (start_time > notification.published || delivered.includes(notification.videoId)) return; - system_notification.onclick = function (event) { - open('/watch?v=' + event.currentTarget.tag, '_blank'); - }; - } + delivered.push(notification.videoId); - delivered.push(notification.videoId); - helpers.storage.set('notification_count', (helpers.storage.get('notification_count') || 0) + 1); - var notification_ticker = document.getElementById('notification_ticker'); + let notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT) || 0; + notification_count++; + helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count); - if (parseInt(helpers.storage.get('notification_count')) > 0) { - notification_ticker.innerHTML = - '' + helpers.storage.get('notification_count') + ' '; - } else { - notification_ticker.innerHTML = - ''; - } + update_ticker_count(); + + // TODO: ask permission to show notifications via Notification.requestPermission + // https://developer.mozilla.org/en-US/docs/Web/API/notification + if (window.Notification && Notification.permission === 'granted') { + var notification_text = notification.liveNow ? notification_data.live_now_text : notification_data.upload_text; + notification_text = notification_text.replace('`x`', notification.author); + + var system_notification = new Notification(notification_text, { + body: notification.title, + icon: '/ggpht' + new URL(notification.authorThumbnails[2].url).pathname, + img: '/ggpht' + new URL(notification.authorThumbnails[4].url).pathname + }); + + system_notification.onclick = function (e) { + open('/watch?v=' + notification.videoId, '_blank'); + }; } }; - notifications.addEventListener('error', handle_notification_error); + notifications.addEventListener('error', function (e) { + console.warn('Something went wrong with notifications, trying to reconnect...'); + notifications = notifications_mock; + setTimeout(get_subscriptions, 1000); + }); + notifications.stream(); } -function handle_notification_error(event) { - console.warn('Something went wrong with notifications, trying to reconnect...'); - notifications = notifications_substitution; - setTimeout(get_subscriptions, 1000); +function update_ticker_count() { + var notification_ticker = document.getElementById('notification_ticker'); + + const notification_count = helpers.storage.get(STORAGE_KEY_STREAM); + if (notification_count > 0) { + notification_ticker.innerHTML = + '' + notification_count + ' '; + } else { + notification_ticker.innerHTML = + ''; + } } -addEventListener('load', function (e) { - var notification_count = document.getElementById('notification_count'); - if (notification_count) { - helpers.storage.set('notification_count', parseInt(notification_count.innerText)); - } else { - helpers.storage.set('notification_count', 0); - } - - if (helpers.storage.get('stream')) { - helpers.storage.remove('stream'); - } else { - setTimeout(function () { - if (!helpers.storage.get('stream')) { - notifications = notifications_substitution; - helpers.storage.set('stream', true); - get_subscriptions(); - } - }, Math.random() * 1000 + 50); - } - - addEventListener('storage', function (e) { - if (e.key === 'stream' && !e.newValue) { - if (notifications) { - helpers.storage.set('stream', true); - } else { - setTimeout(function () { - if (!helpers.storage.get('stream')) { - notifications = notifications_substitution; - helpers.storage.set('stream', true); - get_subscriptions(); - } - }, Math.random() * 1000 + 50); - } - } else if (e.key === 'notification_count') { - var notification_ticker = document.getElementById('notification_ticker'); - - if (parseInt(e.newValue) > 0) { - notification_ticker.innerHTML = - '' + e.newValue + ' '; - } else { - notification_ticker.innerHTML = - ''; - } +function start_stream_if_needed() { + // random wait for other tabs set 'stream' flag + setTimeout(function () { + if (!helpers.storage.get(STORAGE_KEY_STREAM)) { + // if no one set 'stream', set it by yourself and start stream + helpers.storage.set(STORAGE_KEY_STREAM, true); + notifications = notifications_mock; + get_subscriptions(); } - }); + }, Math.random() * 1000 + 50); // [0.050 .. 1.050) second +} + + +addEventListener('storage', function (e) { + if (e.key === STORAGE_KEY_NOTIF_COUNT) + update_ticker_count(); + + // if 'stream' key was removed + if (e.key === STORAGE_KEY_STREAM && !helpers.storage.get(STORAGE_KEY_STREAM)) { + if (notifications) { + // restore it if we have active stream + helpers.storage.set(STORAGE_KEY_STREAM, true); + } else { + start_stream_if_needed(); + } + } }); -addEventListener('unload', function (e) { - if (notifications) helpers.storage.remove('stream'); +addEventListener('load', function () { + var notification_count_el = document.getElementById('notification_count'); + var notification_count = notification_count_el ? parseInt(notification_count_el.textContent) : 0; + helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count); + + if (helpers.storage.get(STORAGE_KEY_STREAM)) + helpers.storage.remove(STORAGE_KEY_STREAM); + start_stream_if_needed(); +}); + +addEventListener('unload', function () { + // let chance to other tabs to be a streamer via firing 'storage' event + if (notifications) helpers.storage.remove(STORAGE_KEY_STREAM); }); diff --git a/assets/js/player.js b/assets/js/player.js index d09892cb..ff9302b7 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -43,9 +43,10 @@ var save_player_pos_key = 'save_player_pos'; videojs.Vhs.xhr.beforeRequest = function(options) { // set local if requested not videoplayback - if (!options.uri.includes('videoplayback')) + if (!options.uri.includes('videoplayback')) { if (!options.uri.includes('local=true')) options.uri += '?local=true'; + } return options; }; @@ -346,7 +347,7 @@ if (!video_data.params.listen && video_data.params.quality === 'dash') { targetQualityLevel = 0; break; default: - const targetHeight = parseInt(video_data.params.quality_dash, 10); + const targetHeight = parseInt(video_data.params.quality_dash); for (let i = 0; i < qualityLevels.length; i++) { if (qualityLevels[i].height <= targetHeight) targetQualityLevel = i; @@ -411,8 +412,8 @@ if (!video_data.params.listen && video_data.params.annotations) { function change_volume(delta) { const curVolume = player.volume(); - const newVolume = curVolume + delta; - helpers.clamp(newVolume, 0, 1); + let newVolume = curVolume + delta; + newVolume = helpers.clamp(newVolume, 0, 1); player.volume(newVolume); } @@ -423,8 +424,8 @@ function toggle_muted() { function skip_seconds(delta) { const duration = player.duration(); const curTime = player.currentTime(); - const newTime = curTime + delta; - helpers.clamp(newTime, 0, duration); + let newTime = curTime + delta; + newTime = helpers.clamp(newTime, 0, duration); player.currentTime(newTime); } @@ -434,20 +435,13 @@ function set_seconds_after_start(delta) { } function save_video_time(seconds) { - const videoId = video_data.id; const all_video_times = get_all_video_times(); - - all_video_times[videoId] = seconds; - + all_video_times[video_data.id] = seconds; helpers.storage.set(save_player_pos_key, all_video_times); } function get_video_time() { - const videoId = video_data.id; - const all_video_times = get_all_video_times(); - const timestamp = all_video_times[videoId]; - - return timestamp || 0; + return get_all_video_times()[video_data.id] || 0; } function get_all_video_times() { @@ -534,8 +528,8 @@ function toggle_fullscreen() { function increase_playback_rate(steps) { const maxIndex = options.playbackRates.length - 1; const curIndex = options.playbackRates.indexOf(player.playbackRate()); - const newIndex = curIndex + steps; - helpers.clamp(newIndex, 0, maxIndex); + let newIndex = curIndex + steps; + newIndex = helpers.clamp(newIndex, 0, maxIndex); player.playbackRate(options.playbackRates[newIndex]); } diff --git a/assets/js/playlist_widget.js b/assets/js/playlist_widget.js index 8f8da6d5..c92592ac 100644 --- a/assets/js/playlist_widget.js +++ b/assets/js/playlist_widget.js @@ -12,7 +12,7 @@ function add_playlist_video(target) { helpers.xhr('POST', url, {payload: payload}, { on200: function (response) { - option.innerText = '✓' + option.innerText; + option.textContent = '✓' + option.textContent; } }); } diff --git a/assets/js/watch.js b/assets/js/watch.js index 45492241..f78b9242 100644 --- a/assets/js/watch.js +++ b/assets/js/watch.js @@ -294,7 +294,7 @@ function get_youtube_replies(target, load_more, load_replies) { a.onclick = hide_youtube_replies; a.setAttribute('data-sub-text', video_data.hide_replies_text); a.setAttribute('data-inner-text', video_data.show_replies_text); - a.innerText = video_data.hide_replies_text; + a.textContent = video_data.hide_replies_text; var div = document.createElement('div'); div.innerHTML = response.contentHtml; diff --git a/assets/js/watched_widget.js b/assets/js/watched_widget.js index 497b1878..f1ac9cb4 100644 --- a/assets/js/watched_widget.js +++ b/assets/js/watched_widget.js @@ -20,14 +20,14 @@ function mark_unwatched(target) { var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; tile.style.display = 'none'; var count = document.getElementById('count'); - count.innerText = parseInt(count.innerText) - 1; + count.textContent--; var url = '/watch_ajax?action_mark_unwatched=1&redirect=false' + '&id=' + target.getAttribute('data-id'); helpers.xhr('POST', url, {payload: payload}, { onNon200: function (xhr) { - count.innerText = parseInt(count.innerText) + 1; + count.textContent++; tile.style.display = ''; } }); From b7295977284f060b4c294332d9029ed46aa8f35d Mon Sep 17 00:00:00 2001 From: meow Date: Sat, 21 May 2022 19:30:51 +0300 Subject: [PATCH 13/17] comment changed extra spaces removed --- assets/js/notifications.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/assets/js/notifications.js b/assets/js/notifications.js index 7a30375d..51ff1f98 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -48,8 +48,7 @@ function create_notification_stream(subscriptions) { update_ticker_count(); - // TODO: ask permission to show notifications via Notification.requestPermission - // https://developer.mozilla.org/en-US/docs/Web/API/notification + // permission for notifications handled on settings page. JS handler is in handlers.js if (window.Notification && Notification.permission === 'granted') { var notification_text = notification.liveNow ? notification_data.live_now_text : notification_data.upload_text; notification_text = notification_text.replace('`x`', notification.author); @@ -62,7 +61,7 @@ function create_notification_stream(subscriptions) { system_notification.onclick = function (e) { open('/watch?v=' + notification.videoId, '_blank'); - }; + }; } }; From b12149bafd7ed20daa757163e84381f6650e9d2e Mon Sep 17 00:00:00 2001 From: meow Date: Tue, 31 May 2022 11:58:12 +0300 Subject: [PATCH 14/17] Save time during redirection on another instance --- assets/js/player.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/assets/js/player.js b/assets/js/player.js index ff9302b7..4244f2e2 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -704,3 +704,9 @@ if (location.pathname.startsWith('/embed/')) { var cb = player.getChild('ControlBar'); cb.addChild(watch_on_invidious_button); } + +// Save time during redirection on another instance +const changeInstanceLink = document.querySelector('#watch-on-another-invidious-instance > a'); +if (changeInstanceLink) changeInstanceLink.addEventListener('click', function () { + changeInstanceLink.href = addCurrentTimeToURL(changeInstanceLink.href); +}); From f2f3f045e5482618c6dcc18c410556a3b4045f99 Mon Sep 17 00:00:00 2001 From: meow Date: Tue, 31 May 2022 12:18:42 +0300 Subject: [PATCH 15/17] fix time adding dirung redirection --- assets/js/player.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/assets/js/player.js b/assets/js/player.js index 4244f2e2..48533b3e 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -705,8 +705,10 @@ if (location.pathname.startsWith('/embed/')) { cb.addChild(watch_on_invidious_button); } -// Save time during redirection on another instance -const changeInstanceLink = document.querySelector('#watch-on-another-invidious-instance > a'); -if (changeInstanceLink) changeInstanceLink.addEventListener('click', function () { - changeInstanceLink.href = addCurrentTimeToURL(changeInstanceLink.href); +addEventListener('DOMContentLoaded', function () { + // Save time during redirection on another instance + const changeInstanceLink = document.querySelector('#watch-on-another-invidious-instance > a'); + if (changeInstanceLink) changeInstanceLink.addEventListener('click', function () { + changeInstanceLink.href = addCurrentTimeToURL(changeInstanceLink.href); + }); }); From d3ab4a51457ee2f0596db8c2a735ef220105dea8 Mon Sep 17 00:00:00 2001 From: meow Date: Sun, 5 Jun 2022 20:54:48 +0300 Subject: [PATCH 16/17] JS. Trailing spaces removed --- assets/js/_helpers.js | 2 +- assets/js/community.js | 2 +- assets/js/notifications.js | 4 ++-- assets/js/player.js | 8 ++++---- assets/js/watch.js | 10 +++++----- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 0bd99a8c..7c50670e 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -164,7 +164,7 @@ window.helpers = window.helpers || { helpers._xhrRetry(method, url, options, callbacks); }, options.retry_timeout); }; - + // Pack retry() call into error handlers callbacks._onError = callbacks.onError; callbacks.onError = function (xhr) { diff --git a/assets/js/community.js b/assets/js/community.js index 608dc971..32fe4ebc 100644 --- a/assets/js/community.js +++ b/assets/js/community.js @@ -38,7 +38,7 @@ function get_youtube_replies(target, load_more) { var fallback = body.innerHTML; body.innerHTML = '

'; - + var url = '/api/v1/channels/comments/' + community_data.ucid + '?format=html' + '&hl=' + community_data.preferences.locale + diff --git a/assets/js/notifications.js b/assets/js/notifications.js index 51ff1f98..058553d9 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -52,13 +52,13 @@ function create_notification_stream(subscriptions) { if (window.Notification && Notification.permission === 'granted') { var notification_text = notification.liveNow ? notification_data.live_now_text : notification_data.upload_text; notification_text = notification_text.replace('`x`', notification.author); - + var system_notification = new Notification(notification_text, { body: notification.title, icon: '/ggpht' + new URL(notification.authorThumbnails[2].url).pathname, img: '/ggpht' + new URL(notification.authorThumbnails[4].url).pathname }); - + system_notification.onclick = function (e) { open('/watch?v=' + notification.videoId, '_blank'); }; diff --git a/assets/js/player.js b/assets/js/player.js index 48533b3e..7d099e66 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -54,12 +54,12 @@ var player = videojs('player', options); player.on('error', function () { if (video_data.params.quality === 'dash') return; - + var localNotDisabled = ( !player.currentSrc().includes('local=true') && !video_data.local_disabled ); var reloadMakesSense = ( - player.error().code === MediaError.MEDIA_ERR_NETWORK || + player.error().code === MediaError.MEDIA_ERR_NETWORK || player.error().code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED ); @@ -465,7 +465,7 @@ function toggle_play() { player.paused() ? play() : pause(); } const toggle_captions = (function () { let toggledTrack = null; - + function bindChange(onOrOff) { player.textTracks()[onOrOff]('change', function (e) { toggledTrack = null; @@ -481,7 +481,7 @@ const toggle_captions = (function () { bindChange('on'); }, 0); } - + bindChange('on'); return function () { if (toggledTrack !== null) { diff --git a/assets/js/watch.js b/assets/js/watch.js index f78b9242..cff84e4d 100644 --- a/assets/js/watch.js +++ b/assets/js/watch.js @@ -172,7 +172,7 @@ function get_reddit_comments() { var onNon200 = function (xhr) { comments.innerHTML = fallback; }; if (video_data.params.comments[1] === 'youtube') onNon200 = function (xhr) {}; - + helpers.xhr('GET', url, {retries: 5, entity_name: ''}, { on200: function (response) { comments.innerHTML = ' \ @@ -218,11 +218,11 @@ function get_youtube_comments() { '?format=html' + '&hl=' + video_data.preferences.locale + '&thin_mode=' + video_data.preferences.thin_mode; - + var onNon200 = function (xhr) { comments.innerHTML = fallback; }; if (video_data.params.comments[1] === 'youtube') onNon200 = function (xhr) {}; - + helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, { on200: function (response) { comments.innerHTML = ' \ @@ -304,11 +304,11 @@ function get_youtube_replies(target, load_more, load_replies) { } }, onNon200: function (xhr) { - body.innerHTML = fallback; + body.innerHTML = fallback; }, onTimeout: function (xhr) { console.warn('Pulling comments failed'); - body.innerHTML = fallback; + body.innerHTML = fallback; } }); } From a402128a7d4a2d3dccbeeb553e5363447f501a37 Mon Sep 17 00:00:00 2001 From: meow Date: Sun, 5 Jun 2022 21:19:59 +0300 Subject: [PATCH 17/17] Move `_helpers.js` include from various .ecr's into `template.ecr` `head` tag --- src/invidious/views/add_playlist_items.ecr | 1 - src/invidious/views/community.ecr | 1 - src/invidious/views/components/player.ecr | 1 - src/invidious/views/components/subscribe_widget.ecr | 1 - src/invidious/views/embed.ecr | 1 - src/invidious/views/feeds/history.ecr | 1 - src/invidious/views/feeds/subscriptions.ecr | 1 - src/invidious/views/playlist.ecr | 1 - src/invidious/views/template.ecr | 2 +- src/invidious/views/watch.ecr | 2 -- 10 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/invidious/views/add_playlist_items.ecr b/src/invidious/views/add_playlist_items.ecr index 758f3995..22870317 100644 --- a/src/invidious/views/add_playlist_items.ecr +++ b/src/invidious/views/add_playlist_items.ecr @@ -29,7 +29,6 @@ }.to_pretty_json %> -
diff --git a/src/invidious/views/community.ecr b/src/invidious/views/community.ecr index 154c40b5..3bc29e55 100644 --- a/src/invidious/views/community.ecr +++ b/src/invidious/views/community.ecr @@ -93,5 +93,4 @@ }.to_pretty_json %> - diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr index 483807d7..fffefc9a 100644 --- a/src/invidious/views/components/player.ecr +++ b/src/invidious/views/components/player.ecr @@ -66,5 +66,4 @@ }.to_pretty_json %> - diff --git a/src/invidious/views/components/subscribe_widget.ecr b/src/invidious/views/components/subscribe_widget.ecr index 7a8c7fda..b9d5f783 100644 --- a/src/invidious/views/components/subscribe_widget.ecr +++ b/src/invidious/views/components/subscribe_widget.ecr @@ -31,7 +31,6 @@ }.to_pretty_json %> - <% else %>

diff --git a/src/invidious/views/embed.ecr b/src/invidious/views/embed.ecr index 82f80f9d..ce5ff7f0 100644 --- a/src/invidious/views/embed.ecr +++ b/src/invidious/views/embed.ecr @@ -31,7 +31,6 @@ <%= rendered "components/player" %> - diff --git a/src/invidious/views/feeds/history.ecr b/src/invidious/views/feeds/history.ecr index 51dd78bd..6c1243c5 100644 --- a/src/invidious/views/feeds/history.ecr +++ b/src/invidious/views/feeds/history.ecr @@ -25,7 +25,6 @@ }.to_pretty_json %> -

diff --git a/src/invidious/views/feeds/subscriptions.ecr b/src/invidious/views/feeds/subscriptions.ecr index 957277fa..8d56ad14 100644 --- a/src/invidious/views/feeds/subscriptions.ecr +++ b/src/invidious/views/feeds/subscriptions.ecr @@ -50,7 +50,6 @@ }.to_pretty_json %> -
diff --git a/src/invidious/views/playlist.ecr b/src/invidious/views/playlist.ecr index 641cbe2c..df3112db 100644 --- a/src/invidious/views/playlist.ecr +++ b/src/invidious/views/playlist.ecr @@ -97,7 +97,6 @@ }.to_pretty_json %> - <% end %> diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr index 79decbe6..4e2b29f0 100644 --- a/src/invidious/views/template.ecr +++ b/src/invidious/views/template.ecr @@ -17,6 +17,7 @@ + <% @@ -157,7 +158,6 @@
- <% if env.get? "user" %> diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index f2d8ba03..861b2048 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -165,7 +165,6 @@ we're going to need to do it here in order to allow for translations. }.to_pretty_json %> - <% end %> <% end %> @@ -304,5 +303,4 @@ we're going to need to do it here in order to allow for translations.
<% end %> -