From 7dd699370fae20c69119a4117468b1d999a2752a Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 6 May 2022 04:46:59 +0300 Subject: js code rewrite. Created _helpers.js with XHR and storage wrapper --- assets/js/_helpers.js | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 assets/js/_helpers.js (limited to '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'; + } + }; + })() +}; -- cgit v1.2.3 From 835237382fd2e316a5e118dafc929f6ffb8e33fd Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 6 May 2022 06:16:41 +0300 Subject: fix helpers --- assets/js/_helpers.js | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) (limited to 'assets/js/_helpers.js') 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); -- cgit v1.2.3 From fd890f9c0a78635a3ea1ab56ec5a735fef27c1c4 Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 6 May 2022 07:21:19 +0300 Subject: 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(-) (limited to 'assets/js/_helpers.js') 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() { -- cgit v1.2.3 From f06d5b973b1bc5aa1c00ff330ae5efeaf83bc5d1 Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 6 May 2022 07:42:15 +0300 Subject: jsdoc type fix --- assets/js/_helpers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'assets/js/_helpers.js') 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 */ /** -- cgit v1.2.3 From fd66084388399319993e9aaa0a15ba3ed5498404 Mon Sep 17 00:00:00 2001 From: meow Date: Sun, 15 May 2022 08:38:46 +0300 Subject: 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(-) (limited to 'assets/js/_helpers.js') 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)'; -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 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 () { + 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)'); - -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); - } -} +/** @param {THEME_DARK|THEME_LIGHT|THEME_SYSTEM} theme */ +function setTheme(theme) { + if (theme !== THEME_SYSTEM) + helpers.storage.set(STORAGE_KEY_THEME, theme); -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; -- cgit v1.2.3 From e18b10297b259460a3219edfeb9ccd0fabc34270 Mon Sep 17 00:00:00 2001 From: meow Date: Mon, 16 May 2022 13:13:00 +0300 Subject: 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(-) (limited to 'assets/js/_helpers.js') 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); }); -- cgit v1.2.3 From b72b917af239c46dbe3d4592fc8b215e63703459 Mon Sep 17 00:00:00 2001 From: meow Date: Sat, 21 May 2022 13:35:41 +0300 Subject: 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(-) (limited to 'assets/js/_helpers.js') 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 - }); - - system_notification.onclick = function (event) { - open('/watch?v=' + event.currentTarget.tag, '_blank'); - }; - } - - delivered.push(notification.videoId); - 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) { - notification_ticker.innerHTML = - '' + helpers.storage.get('notification_count') + ' '; - } else { - notification_ticker.innerHTML = - ''; - } + // Ignore not actual and delivered notifications + if (start_time > notification.published || delivered.includes(notification.videoId)) return; + + delivered.push(notification.videoId); + + let notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT) || 0; + notification_count++; + helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count); + + 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'); -addEventListener('load', function (e) { - var notification_count = document.getElementById('notification_count'); - if (notification_count) { - helpers.storage.set('notification_count', parseInt(notification_count.innerText)); + const notification_count = helpers.storage.get(STORAGE_KEY_STREAM); + if (notification_count > 0) { + notification_ticker.innerHTML = + '' + notification_count + ' '; } else { - helpers.storage.set('notification_count', 0); + 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 +} - 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 = - ''; - } +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('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 (e) { - if (notifications) helpers.storage.remove('stream'); +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 = ''; } }); -- cgit v1.2.3 From d3ab4a51457ee2f0596db8c2a735ef220105dea8 Mon Sep 17 00:00:00 2001 From: meow Date: Sun, 5 Jun 2022 20:54:48 +0300 Subject: 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(-) (limited to 'assets/js/_helpers.js') 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; } }); } -- cgit v1.2.3 From a3da03bee91eab5c602882c4b43b959362ee441d Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Thu, 23 Mar 2023 18:10:53 -0400 Subject: improve accessibility --- assets/css/default.css | 29 ++++++++++++++++++------- assets/css/embed.css | 3 ++- assets/js/_helpers.js | 8 ++++--- assets/js/handlers.js | 2 +- assets/js/player.js | 2 +- src/invidious/comments.cr | 6 ++--- src/invidious/mixes.cr | 2 +- src/invidious/playlists.cr | 2 +- src/invidious/views/components/channel_info.ecr | 4 ++-- src/invidious/views/components/item.ecr | 10 ++++----- src/invidious/views/feeds/history.ecr | 2 +- src/invidious/views/watch.ecr | 4 ++-- 12 files changed, 45 insertions(+), 29 deletions(-) (limited to 'assets/js/_helpers.js') diff --git a/assets/css/default.css b/assets/css/default.css index f8b1c9f7..65d03be1 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -119,13 +119,16 @@ body a.pure-button { button.pure-button-primary, body a.pure-button-primary, -.channel-owner:hover { +.channel-owner:hover, +.channel-owner:focus { background-color: #a0a0a0; color: rgba(35, 35, 35, 1); } button.pure-button-primary:hover, -body a.pure-button-primary:hover { +body a.pure-button-primary:hover, +button.pure-button-primary:focus, +body a.pure-button-primary:focus { background-color: rgba(0, 182, 240, 1); color: #fff; } @@ -227,6 +230,7 @@ div.watched-indicator { border-radius: 0; box-shadow: none; + appearance: none; -webkit-appearance: none; } @@ -365,11 +369,14 @@ span > select { .light-theme a:hover, .light-theme a:active, -.light-theme summary:hover { +.light-theme summary:hover, +.light-theme a:focus, +.light-theme summary:focus { color: #075A9E !important; } -.light-theme a.pure-button-primary:hover { +.light-theme a.pure-button-primary:hover, +.light-theme a.pure-button-primary:focus { color: #fff !important; } @@ -392,11 +399,14 @@ span > select { @media (prefers-color-scheme: light) { .no-theme a:hover, .no-theme a:active, - .no-theme summary:hover { + .no-theme summary:hover, + .no-theme a:focus, + .no-theme summary:focus { color: #075A9E !important; } - .no-theme a.pure-button-primary:hover { + .no-theme a.pure-button-primary:hover, + .no-theme a.pure-button-primary:focus { color: #fff !important; } @@ -423,7 +433,9 @@ span > select { .dark-theme a:hover, .dark-theme a:active, -.dark-theme summary:hover { +.dark-theme summary:hover, +.dark-theme a:focus, +.dark-theme summary:focus { color: rgb(0, 182, 240); } @@ -462,7 +474,8 @@ body.dark-theme { @media (prefers-color-scheme: dark) { .no-theme a:hover, - .no-theme a:active { + .no-theme a:active, + .no-theme a:focus { color: rgb(0, 182, 240); } diff --git a/assets/css/embed.css b/assets/css/embed.css index 466a284a..cbafcfea 100644 --- a/assets/css/embed.css +++ b/assets/css/embed.css @@ -21,6 +21,7 @@ color: white; } -.watch-on-invidious > a:hover { +.watch-on-invidious > a:hover, +.watch-on-invidious > a:focus { color: rgba(0, 182, 240, 1);; } diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 7c50670e..3960cf2c 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -6,6 +6,7 @@ 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); }; @@ -201,15 +202,16 @@ window.helpers = window.helpers || { if (localStorageIsUsable) { return { get: function (key) { - if (!localStorage[key]) return; + let storageItem = localStorage.getItem(key) + if (!storageItem) return; try { - return JSON.parse(decodeURIComponent(localStorage[key])); + return JSON.parse(decodeURIComponent(storageItem)); } catch(e) { // Erase non parsable value helpers.storage.remove(key); } }, - set: function (key, value) { localStorage[key] = encodeURIComponent(JSON.stringify(value)); }, + set: function (key, value) { localStorage.setItem(key, encodeURIComponent(JSON.stringify(value))); }, remove: function (key) { localStorage.removeItem(key); } }; } diff --git a/assets/js/handlers.js b/assets/js/handlers.js index 29810e72..539974fb 100644 --- a/assets/js/handlers.js +++ b/assets/js/handlers.js @@ -137,7 +137,7 @@ if (focused_tag === 'textarea') return; if (focused_tag === 'input') { let focused_type = document.activeElement.type.toLowerCase(); - if (!focused_type.match(allowed)) return; + if (!allowed.test(focused_type)) return; } // Focus search bar on '/' diff --git a/assets/js/player.js b/assets/js/player.js index ee678663..bb53ac24 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -261,7 +261,7 @@ function updateCookie(newVolume, newSpeed) { var date = new Date(); date.setFullYear(date.getFullYear() + 2); - var ipRegex = /^((\d+\.){3}\d+|[A-Fa-f0-9]*:[A-Fa-f0-9:]*:[A-Fa-f0-9:]+)$/; + var ipRegex = /^((\d+\.){3}\d+|[\dA-Fa-f]*:[\d:A-Fa-f]*:[\d:A-Fa-f]+)$/; var domainUsed = location.hostname; // Fix for a bug in FF where the leading dot in the FQDN is not ignored diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index b15d63d4..2d62580d 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -346,7 +346,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML
- +

@@ -367,7 +367,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML

- +
END_HTML @@ -428,7 +428,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML
- +
diff --git a/src/invidious/mixes.cr b/src/invidious/mixes.cr index 3f342b92..defbbc84 100644 --- a/src/invidious/mixes.cr +++ b/src/invidious/mixes.cr @@ -97,7 +97,7 @@ def template_mix(mix)
  • - +

    #{recode_length_seconds(video["lengthSeconds"].as_i)}

    #{video["title"]}

    diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 57f1f53e..40bb244b 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -507,7 +507,7 @@ def template_playlist(playlist)
  • - +

    #{recode_length_seconds(video["lengthSeconds"].as_i)}

    #{video["title"]}

    diff --git a/src/invidious/views/components/channel_info.ecr b/src/invidious/views/components/channel_info.ecr index f216359f..d94ecdad 100644 --- a/src/invidious/views/components/channel_info.ecr +++ b/src/invidious/views/components/channel_info.ecr @@ -1,6 +1,6 @@ <% if channel.banner %>
    - "> + " alt="">
    @@ -11,7 +11,7 @@
    - + <%= author %><% if !channel.verified.nil? && channel.verified %> <% end %>
    diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index fa12374f..36e9d45b 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -7,7 +7,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - "/> + " alt=""/>
    <% end %>

    <%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %> <% end %>

    @@ -25,7 +25,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - "/> + " alt="" />

    <%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %>

    <% end %> @@ -38,7 +38,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + <% if item.length_seconds != 0 %>

    <%= recode_length_seconds(item.length_seconds) %>

    <% end %> @@ -58,7 +58,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + <% if plid_form = env.get?("remove_playlist_items") %>
    " method="post"> @@ -112,7 +112,7 @@ <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + <% if env.get? "show_watched" %> " method="post"> "> diff --git a/src/invidious/views/feeds/history.ecr b/src/invidious/views/feeds/history.ecr index 471d21db..be1b521d 100644 --- a/src/invidious/views/feeds/history.ecr +++ b/src/invidious/views/feeds/history.ecr @@ -34,7 +34,7 @@ <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + " method="post"> ">

    diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index a3ec94e8..d2082557 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -208,7 +208,7 @@ we're going to need to do it here in order to allow for translations.

    @@ -298,7 +298,7 @@ we're going to need to do it here in order to allow for translations. &listen=<%= params.listen %>"> <% if !env.get("preferences").as(Preferences).thin_mode %>
    - /mqdefault.jpg"> + /mqdefault.jpg" alt="">

    <%= recode_length_seconds(rv["length_seconds"]?.try &.to_i? || 0) %>

    <% end %> -- cgit v1.2.3 From 1da00bade3d370711c670afb38dcd0f97e9dd965 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Sun, 2 Apr 2023 16:31:59 -0400 Subject: implement code suggestions Co-Authored-By: Samantaz Fox --- assets/js/_helpers.js | 5 ++++- src/invidious/comments.cr | 8 ++++---- src/invidious/mixes.cr | 2 +- src/invidious/playlists.cr | 2 +- src/invidious/views/components/channel_info.ecr | 4 ++-- src/invidious/views/components/item.ecr | 8 ++++---- src/invidious/views/feeds/history.ecr | 2 +- src/invidious/views/watch.ecr | 4 ++-- 8 files changed, 19 insertions(+), 16 deletions(-) (limited to 'assets/js/_helpers.js') diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 3960cf2c..8e18169e 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -211,7 +211,10 @@ window.helpers = window.helpers || { helpers.storage.remove(key); } }, - set: function (key, value) { localStorage.setItem(key, encodeURIComponent(JSON.stringify(value))); }, + set: function (key, value) { + let encoded_value = encodeURIComponent(JSON.stringify(value)) + localStorage.setItem(key, encoded_value); + }, remove: function (key) { localStorage.removeItem(key); } }; } diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 2d62580d..fd2be73d 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -346,7 +346,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML
    - +

    @@ -367,7 +367,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML

    - +
    END_HTML @@ -428,7 +428,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML
    - +
    @@ -702,7 +702,7 @@ def content_to_comment_html(content, video_id : String? = "") str << %(title=") << emojiAlt << "\" " str << %(width=") << emojiThumb["width"] << "\" " str << %(height=") << emojiThumb["height"] << "\" " - str << %(class="channel-emoji"/>) + str << %(class="channel-emoji" />) end else # Hide deleted channel emoji diff --git a/src/invidious/mixes.cr b/src/invidious/mixes.cr index defbbc84..823ca85b 100644 --- a/src/invidious/mixes.cr +++ b/src/invidious/mixes.cr @@ -97,7 +97,7 @@ def template_mix(mix)
  • - +

    #{recode_length_seconds(video["lengthSeconds"].as_i)}

    #{video["title"]}

    diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 40bb244b..013be268 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -507,7 +507,7 @@ def template_playlist(playlist)
  • - +

    #{recode_length_seconds(video["lengthSeconds"].as_i)}

    #{video["title"]}

    diff --git a/src/invidious/views/components/channel_info.ecr b/src/invidious/views/components/channel_info.ecr index d94ecdad..59888760 100644 --- a/src/invidious/views/components/channel_info.ecr +++ b/src/invidious/views/components/channel_info.ecr @@ -1,6 +1,6 @@ <% if channel.banner %>
    - " alt=""> + " alt="" />
    @@ -11,7 +11,7 @@
    - + <%= author %><% if !channel.verified.nil? && channel.verified %> <% end %>
    diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index 36e9d45b..7cfd38db 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -7,7 +7,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - " alt=""/> + " alt="" />
    <% end %>

    <%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %> <% end %>

    @@ -38,7 +38,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>