diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js
index 8e18169e..30904e8e 100644
--- a/assets/js/_helpers.js
+++ b/assets/js/_helpers.js
@@ -1,254 +1,273 @@
-'use strict';
+"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) {
+Array.prototype.find =
+ Array.prototype.find ||
+ function (condition) {
return this.filter(condition)[0];
-};
+ };
-Array.from = Array.from || function (source) {
+Array.from =
+ Array.from ||
+ function (source) {
return Array.prototype.slice.call(source);
-};
-NodeList.prototype.forEach = NodeList.prototype.forEach || function (callback) {
+ };
+NodeList.prototype.forEach =
+ NodeList.prototype.forEach ||
+ function (callback) {
Array.from(this).forEach(callback);
-};
-String.prototype.includes = String.prototype.includes || function (searchString) {
+ };
+String.prototype.includes =
+ String.prototype.includes ||
+ function (searchString) {
return this.indexOf(searchString) >= 0;
-};
-String.prototype.startsWith = String.prototype.startsWith || function (prefix) {
+ };
+String.prototype.startsWith =
+ String.prototype.startsWith ||
+ function (prefix) {
return this.substr(0, prefix.length) === prefix;
-};
-Math.sign = Math.sign || function(x) {
+ };
+Math.sign =
+ Math.sign ||
+ function (x) {
x = +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;
+ };
+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', '');
- });
+ 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
// Includes: clamp, xhr, storage.{get,set,remove}
window.helpers = window.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
+ /**
+ * 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");
+
+ // better than onreadystatechange because of 404 codes https://stackoverflow.com/a/36182963
+ xhr.onloadend = function () {
+ if (xhr.status === 200) {
+ 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;
- if (max < num)
- return max;
- if (min > num)
- return min;
- return num;
- },
+ if (callbacks.onNon200) callbacks.onNon200(xhr);
+ }
+ };
- /** @private */
- _xhr: function (method, url, options, callbacks) {
- const xhr = new XMLHttpRequest();
- xhr.open(method, url);
+ xhr.ontimeout = function () {
+ if (callbacks.onTimeout) callbacks.onTimeout(xhr);
+ };
- // 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;
+ xhr.onerror = function () {
+ if (callbacks.onError) callbacks.onError(xhr);
+ };
- if (method === 'POST')
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ if (options.payload) xhr.send(options.payload);
+ else xhr.send();
+ },
+ /** @private */
+ _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);
+ },
+ /**
+ * @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: function (method, url, options, callbacks) {
+ if (!options.retries || options.retries <= 1) {
+ helpers._xhr(method, url, options, callbacks);
+ return;
+ }
- // better than onreadystatechange because of 404 codes https://stackoverflow.com/a/36182963
- xhr.onloadend = function () {
- if (xhr.status === 200) {
- 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;
-
- 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: 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);
- },
- /**
- * @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: 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 = 1000;
- const retries_total = options.retries;
- let currentTry = 1;
-
- const retry = function () {
- console.warn('Pulling ' + options.entity_name + ' failed... ' + (currentTry++) + '/' + retries_total);
- setTimeout(function () {
- options.retries--;
- helpers._xhrRetry(method, url, options, callbacks);
- }, options.retry_timeout);
- };
-
- // Pack retry() call into error handlers
- callbacks._onError = callbacks.onError;
- callbacks.onError = function (xhr) {
- if (callbacks._onError)
- callbacks._onError(xhr);
- retry();
- };
- callbacks._onTimeout = callbacks.onTimeout;
- callbacks.onTimeout = function (xhr) {
- if (callbacks._onTimeout)
- callbacks._onTimeout(xhr);
- retry();
- };
+ if (!options.entity_name) options.entity_name = "unknown";
+ 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... " +
+ currentTry++ +
+ "/" +
+ retries_total,
+ );
+ setTimeout(function () {
+ options.retries--;
helpers._xhrRetry(method, url, options, callbacks);
- },
+ }, options.retry_timeout);
+ };
- /**
- * @typedef {Object} invidiousStorage
- * @property {(key:String) => Object} get
- * @property {(key:String, value:Object)} set
- * @property {(key:String)} remove
- */
+ // Pack retry() call into error handlers
+ callbacks._onError = callbacks.onError;
+ callbacks.onError = function (xhr) {
+ if (callbacks._onError) callbacks._onError(xhr);
+ retry();
+ };
+ callbacks._onTimeout = callbacks.onTimeout;
+ callbacks.onTimeout = function (xhr) {
+ if (callbacks._onTimeout) callbacks._onTimeout(xhr);
+ retry();
+ };
- /**
- * Universal storage, stores and returns JS objects. 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){}
+ helpers._xhrRetry(method, url, options, callbacks);
+ },
- if (localStorageIsUsable) {
- return {
- get: function (key) {
- let storageItem = localStorage.getItem(key)
- if (!storageItem) return;
- try {
- return JSON.parse(decodeURIComponent(storageItem));
- } catch(e) {
- // Erase non parsable value
- helpers.storage.remove(key);
- }
- },
- set: function (key, value) {
- let encoded_value = encodeURIComponent(JSON.stringify(value))
- localStorage.setItem(key, encoded_value);
- },
- remove: function (key) { localStorage.removeItem(key); }
- };
+ /**
+ * @typedef {Object} invidiousStorage
+ * @property {(key:String) => Object} get
+ * @property {(key:String, value:Object)} set
+ * @property {(key:String)} remove
+ */
+
+ /**
+ * Universal storage, stores and returns JS objects. 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) {
+ let storageItem = localStorage.getItem(key);
+ if (!storageItem) return;
+ try {
+ return JSON.parse(decodeURIComponent(storageItem));
+ } catch (e) {
+ // Erase non parsable value
+ helpers.storage.remove(key);
+ }
+ },
+ set: function (key, value) {
+ let encoded_value = encodeURIComponent(JSON.stringify(value));
+ localStorage.setItem(key, encoded_value);
+ },
+ remove: function (key) {
+ localStorage.removeItem(key);
+ },
+ };
+ }
+
+ // 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 + "=";
+ function findCallback(cookie) {
+ return cookie.startsWith(cookiePrefix);
}
+ const matchedCookie = document.cookie.split("; ").find(findCallback);
+ if (matchedCookie) {
+ const cookieBody = matchedCookie.replace(cookiePrefix, "");
+ if (cookieBody.length === 0) return;
+ try {
+ return JSON.parse(decodeURIComponent(cookieBody));
+ } catch (e) {
+ // Erase non parsable value
+ helpers.storage.remove(key);
+ }
+ }
+ },
+ set: function (key, value) {
+ const cookie_data = encodeURIComponent(JSON.stringify(value));
- // 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 + '=';
- function findCallback(cookie) {return cookie.startsWith(cookiePrefix);}
- const matchedCookie = document.cookie.split('; ').find(findCallback);
- if (matchedCookie) {
- const cookieBody = matchedCookie.replace(cookiePrefix, '');
- if (cookieBody.length === 0) return;
- try {
- return JSON.parse(decodeURIComponent(cookieBody));
- } catch(e) {
- // Erase non parsable value
- helpers.storage.remove(key);
- }
- }
- },
- set: function (key, value) {
- const cookie_data = encodeURIComponent(JSON.stringify(value));
+ // Set expiration in 2 year
+ const date = new Date();
+ date.setFullYear(date.getFullYear() + 2);
- // Set expiration in 2 year
- const date = new Date();
- date.setFullYear(date.getFullYear()+2);
-
- document.cookie = key + '=' + cookie_data + '; expires=' + date.toGMTString();
- },
- remove: function (key) {
- document.cookie = key + '=; Max-Age=0';
- }
- };
- })()
+ document.cookie =
+ key + "=" + cookie_data + "; expires=" + date.toGMTString();
+ },
+ remove: function (key) {
+ document.cookie = key + "=; Max-Age=0";
+ },
+ };
+ })(),
};
diff --git a/assets/js/comments.js b/assets/js/comments.js
index 57c99f52..7c68ae54 100644
--- a/assets/js/comments.js
+++ b/assets/js/comments.js
@@ -1,81 +1,91 @@
-var video_data = JSON.parse(document.getElementById('video_data').textContent);
+var video_data = JSON.parse(document.getElementById("video_data").textContent);
-var spinnerHTML = '
';
-var spinnerHTMLwithHR = spinnerHTML + '
';
+var spinnerHTML =
+ '
';
+var spinnerHTMLwithHR = spinnerHTML + "
";
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;
- });
+ return this.replace(/{([^{}]*)}/g, function (a, b) {
+ var r = o[b];
+ return typeof r === "string" || typeof r === "number" ? r : a;
+ });
};
function toggle_comments(event) {
- const target = event.target;
- const comments = document.querySelector(".comments");
- if (comments.style.display === 'none') {
- target.textContent = '−';
- comments.style.display = '';
- } else {
- target.textContent = '+';
- comments.style.display = 'none';
- }
+ const target = event.target;
+ const comments = document.querySelector(".comments");
+ if (comments.style.display === "none") {
+ target.textContent = "−";
+ comments.style.display = "";
+ } else {
+ target.textContent = "+";
+ comments.style.display = "none";
+ }
}
function hide_youtube_replies(event) {
- var target = event.target;
+ var target = event.target;
- var sub_text = target.getAttribute('data-inner-text');
- var inner_text = target.getAttribute('data-sub-text');
+ var sub_text = target.getAttribute("data-inner-text");
+ var inner_text = target.getAttribute("data-sub-text");
- var body = target.parentNode.parentNode.children[1];
- body.style.display = 'none';
+ var body = target.parentNode.parentNode.children[1];
+ body.style.display = "none";
- target.textContent = sub_text;
- target.onclick = show_youtube_replies;
- target.setAttribute('data-inner-text', inner_text);
- target.setAttribute('data-sub-text', sub_text);
+ target.textContent = sub_text;
+ target.onclick = show_youtube_replies;
+ target.setAttribute("data-inner-text", inner_text);
+ target.setAttribute("data-sub-text", sub_text);
}
function show_youtube_replies(event) {
- var target = event.target;
- console.log(target);
+ var target = event.target;
+ console.log(target);
- var sub_text = target.getAttribute('data-inner-text');
- var inner_text = target.getAttribute('data-sub-text');
+ var sub_text = target.getAttribute("data-inner-text");
+ var inner_text = target.getAttribute("data-sub-text");
- var body = target.parentNode.parentNode.children[1];
- body.style.display = '';
+ var body = target.parentNode.parentNode.children[1];
+ body.style.display = "";
- target.textContent = sub_text;
- target.onclick = hide_youtube_replies;
- target.setAttribute('data-inner-text', inner_text);
- target.setAttribute('data-sub-text', sub_text);
+ target.textContent = sub_text;
+ target.onclick = hide_youtube_replies;
+ target.setAttribute("data-inner-text", inner_text);
+ target.setAttribute("data-sub-text", sub_text);
}
function get_youtube_comments() {
- var comments = document.getElementById('comments');
+ var comments = document.getElementById("comments");
- var fallback = comments.innerHTML;
- comments.innerHTML = spinnerHTML;
+ var fallback = comments.innerHTML;
+ comments.innerHTML = spinnerHTML;
- var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id
- var url = baseUrl +
- '?format=html' +
- '&hl=' + video_data.preferences.locale +
- '&thin_mode=' + video_data.preferences.thin_mode;
+ var baseUrl = video_data.base_url || "/api/v1/comments/" + video_data.id;
+ var url =
+ baseUrl +
+ "?format=html" +
+ "&hl=" +
+ video_data.preferences.locale +
+ "&thin_mode=" +
+ video_data.preferences.thin_mode;
- if (video_data.ucid) {
- url += '&ucid=' + video_data.ucid
- }
+ if (video_data.ucid) {
+ url += "&ucid=" + video_data.ucid;
+ }
- var onNon200 = function (xhr) { comments.innerHTML = fallback; };
- if (video_data.params.comments[1] === 'youtube')
- onNon200 = function (xhr) {};
+ 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) {
- var commentInnerHtml = ' \
+ helpers.xhr(
+ "GET",
+ url,
+ { retries: 5, entity_name: "comments" },
+ {
+ on200: function (response) {
+ var commentInnerHtml =
+ ' \
\
- '
- commentInnerHtml = commentInnerHtml.supplant({
- contentHtml: response.contentHtml,
- redditComments: video_data.reddit_comments_text,
- 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.innerHTML = commentInnerHtml;
- document.getElementById("toggle-comments").onclick = toggle_comments;
- if (video_data.support_reddit) {
- comments.children[1].children[1].onclick = swap_comments;
- }
- },
- onNon200: onNon200, // declared above
- onError: function (xhr) {
- comments.innerHTML = spinnerHTML;
- },
- onTimeout: function (xhr) {
- comments.innerHTML = spinnerHTML;
+ ';
+ commentInnerHtml = commentInnerHtml.supplant({
+ contentHtml: response.contentHtml,
+ redditComments: video_data.reddit_comments_text,
+ 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.innerHTML = commentInnerHtml;
+ document.getElementById("toggle-comments").onclick = toggle_comments;
+ if (video_data.support_reddit) {
+ comments.children[1].children[1].onclick = swap_comments;
}
- });
+ },
+ onNon200: onNon200, // declared above
+ onError: function (xhr) {
+ comments.innerHTML = spinnerHTML;
+ },
+ onTimeout: function (xhr) {
+ comments.innerHTML = spinnerHTML;
+ },
+ },
+ );
}
function get_youtube_replies(target, load_more, load_replies) {
- var continuation = target.getAttribute('data-continuation');
+ var continuation = target.getAttribute("data-continuation");
- var body = target.parentNode;
- var fallback = body.innerHTML;
- body.innerHTML = spinnerHTML;
- var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id
- var url = baseUrl +
- '?format=html' +
- '&hl=' + video_data.preferences.locale +
- '&thin_mode=' + video_data.preferences.thin_mode +
- '&continuation=' + continuation;
+ var body = target.parentNode;
+ var fallback = body.innerHTML;
+ body.innerHTML = spinnerHTML;
+ var baseUrl = video_data.base_url || "/api/v1/comments/" + video_data.id;
+ var url =
+ baseUrl +
+ "?format=html" +
+ "&hl=" +
+ video_data.preferences.locale +
+ "&thin_mode=" +
+ video_data.preferences.thin_mode +
+ "&continuation=" +
+ continuation;
- if (video_data.ucid) {
- url += '&ucid=' + video_data.ucid
- }
- if (load_replies) url += '&action=action_get_comment_replies';
+ if (video_data.ucid) {
+ url += "&ucid=" + video_data.ucid;
+ }
+ if (load_replies) url += "&action=action_get_comment_replies";
- helpers.xhr('GET', url, {}, {
- on200: function (response) {
- if (load_more) {
- body = body.parentNode;
- body.removeChild(body.lastElementChild);
- body.insertAdjacentHTML('beforeend', response.contentHtml);
- } else {
- body.removeChild(body.lastElementChild);
+ helpers.xhr(
+ "GET",
+ url,
+ {},
+ {
+ on200: function (response) {
+ if (load_more) {
+ body = body.parentNode;
+ body.removeChild(body.lastElementChild);
+ body.insertAdjacentHTML("beforeend", response.contentHtml);
+ } else {
+ body.removeChild(body.lastElementChild);
- var div = document.createElement('div');
- var button = document.createElement('button');
- div.appendChild(button);
+ var div = document.createElement("div");
+ var button = document.createElement("button");
+ div.appendChild(button);
- button.onclick = hide_youtube_replies;
- button.setAttribute('data-sub-text', video_data.hide_replies_text);
- button.setAttribute('data-inner-text', video_data.show_replies_text);
- button.textContent = video_data.hide_replies_text;
+ button.onclick = hide_youtube_replies;
+ button.setAttribute("data-sub-text", video_data.hide_replies_text);
+ button.setAttribute("data-inner-text", video_data.show_replies_text);
+ button.textContent = video_data.hide_replies_text;
- var div = document.createElement('div');
- div.innerHTML = response.contentHtml;
+ var div = document.createElement("div");
+ div.innerHTML = response.contentHtml;
- body.appendChild(div);
- }
- },
- onNon200: function (xhr) {
- body.innerHTML = fallback;
- },
- onTimeout: function (xhr) {
- console.warn('Pulling comments failed');
- body.innerHTML = fallback;
+ body.appendChild(div);
}
- });
+ },
+ onNon200: function (xhr) {
+ body.innerHTML = fallback;
+ },
+ onTimeout: function (xhr) {
+ console.warn("Pulling comments failed");
+ body.innerHTML = fallback;
+ },
+ },
+ );
}
diff --git a/assets/js/community.js b/assets/js/community.js
index 32fe4ebc..ef8be78b 100644
--- a/assets/js/community.js
+++ b/assets/js/community.js
@@ -1,82 +1,94 @@
-'use strict';
-var community_data = JSON.parse(document.getElementById('community_data').textContent);
+"use strict";
+var community_data = JSON.parse(
+ document.getElementById("community_data").textContent,
+);
function hide_youtube_replies(event) {
- var target = event.target;
+ var target = event.target;
- var sub_text = target.getAttribute('data-inner-text');
- var inner_text = target.getAttribute('data-sub-text');
+ var sub_text = target.getAttribute("data-inner-text");
+ var inner_text = target.getAttribute("data-sub-text");
- var body = target.parentNode.parentNode.children[1];
- body.style.display = 'none';
+ var body = target.parentNode.parentNode.children[1];
+ body.style.display = "none";
- target.innerHTML = sub_text;
- target.onclick = show_youtube_replies;
- target.setAttribute('data-inner-text', inner_text);
- target.setAttribute('data-sub-text', sub_text);
+ target.innerHTML = sub_text;
+ target.onclick = show_youtube_replies;
+ target.setAttribute("data-inner-text", inner_text);
+ target.setAttribute("data-sub-text", sub_text);
}
function show_youtube_replies(event) {
- var target = event.target;
+ var target = event.target;
- var sub_text = target.getAttribute('data-inner-text');
- var inner_text = target.getAttribute('data-sub-text');
+ var sub_text = target.getAttribute("data-inner-text");
+ var inner_text = target.getAttribute("data-sub-text");
- var body = target.parentNode.parentNode.children[1];
- body.style.display = '';
+ var body = target.parentNode.parentNode.children[1];
+ body.style.display = "";
- target.innerHTML = sub_text;
- target.onclick = hide_youtube_replies;
- target.setAttribute('data-inner-text', inner_text);
- target.setAttribute('data-sub-text', sub_text);
+ target.innerHTML = sub_text;
+ target.onclick = hide_youtube_replies;
+ target.setAttribute("data-inner-text", inner_text);
+ target.setAttribute("data-sub-text", sub_text);
}
function get_youtube_replies(target, load_more) {
- var continuation = target.getAttribute('data-continuation');
+ var continuation = target.getAttribute("data-continuation");
- var body = target.parentNode.parentNode;
- var fallback = body.innerHTML;
- body.innerHTML =
- '
';
+ var body = target.parentNode.parentNode;
+ 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 url =
+ "/api/v1/channels/comments/" +
+ community_data.ucid +
+ "?format=html" +
+ "&hl=" +
+ community_data.preferences.locale +
+ "&thin_mode=" +
+ community_data.preferences.thin_mode +
+ "&continuation=" +
+ continuation;
- helpers.xhr('GET', url, {}, {
- on200: function (response) {
- if (load_more) {
- body = body.parentNode.parentNode;
- body.removeChild(body.lastElementChild);
- body.innerHTML += response.contentHtml;
- } else {
- body.removeChild(body.lastElementChild);
+ helpers.xhr(
+ "GET",
+ url,
+ {},
+ {
+ on200: function (response) {
+ if (load_more) {
+ body = body.parentNode.parentNode;
+ body.removeChild(body.lastElementChild);
+ body.innerHTML += response.contentHtml;
+ } else {
+ body.removeChild(body.lastElementChild);
- var p = document.createElement('p');
- var a = document.createElement('a');
- p.appendChild(a);
+ 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.textContent = community_data.hide_replies_text;
+ 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.textContent = community_data.hide_replies_text;
- var div = document.createElement('div');
- div.innerHTML = response.contentHtml;
+ 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;
+ body.appendChild(p);
+ body.appendChild(div);
}
- });
+ },
+ onNon200: function (xhr) {
+ body.innerHTML = fallback;
+ },
+ onTimeout: function (xhr) {
+ console.warn("Pulling comments failed");
+ body.innerHTML = fallback;
+ },
+ },
+ );
}
diff --git a/assets/js/embed.js b/assets/js/embed.js
index b11b5e5a..763f0ca8 100644
--- a/assets/js/embed.js
+++ b/assets/js/embed.js
@@ -1,64 +1,79 @@
-'use strict';
-var video_data = JSON.parse(document.getElementById('video_data').textContent);
+"use strict";
+var video_data = JSON.parse(document.getElementById("video_data").textContent);
function get_playlist(plid) {
- var plid_url;
- if (plid.startsWith('RD')) {
- plid_url = '/api/v1/mixes/' + plid +
- '?continuation=' + video_data.id +
- '&format=html&hl=' + video_data.preferences.locale;
- } else {
- plid_url = '/api/v1/playlists/' + plid +
- '?index=' + video_data.index +
- '&continuation' + video_data.id +
- '&format=html&hl=' + video_data.preferences.locale;
- }
+ var plid_url;
+ if (plid.startsWith("RD")) {
+ plid_url =
+ "/api/v1/mixes/" +
+ plid +
+ "?continuation=" +
+ video_data.id +
+ "&format=html&hl=" +
+ video_data.preferences.locale;
+ } else {
+ plid_url =
+ "/api/v1/playlists/" +
+ plid +
+ "?index=" +
+ video_data.index +
+ "&continuation" +
+ video_data.id +
+ "&format=html&hl=" +
+ video_data.preferences.locale;
+ }
- helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, {
- on200: function (response) {
- if (!response.nextVideo)
- return;
+ helpers.xhr(
+ "GET",
+ plid_url,
+ { retries: 5, entity_name: "playlist" },
+ {
+ on200: function (response) {
+ if (!response.nextVideo) return;
- player.on('ended', function () {
- var url = new URL('https://example.com/embed/' + 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', 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);
+ 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);
- location.assign(url.pathname + url.search);
- });
- }
- });
+ location.assign(url.pathname + url.search);
+ });
+ },
+ },
+ );
}
-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());
+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)
- 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.video_series.length !== 0)
- url.searchParams.set('playlist', video_data.video_series.join(','));
+ 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.video_series.length !== 0)
+ url.searchParams.set("playlist", video_data.video_series.join(","));
- location.assign(url.pathname + url.search);
- });
- }
+ location.assign(url.pathname + url.search);
+ });
+ }
});
diff --git a/assets/js/handlers.js b/assets/js/handlers.js
index 51de08f6..efd0e1cf 100644
--- a/assets/js/handlers.js
+++ b/assets/js/handlers.js
@@ -1,149 +1,220 @@
-'use strict';
+"use strict";
(function () {
- 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'] = ''; };
- video_player.onmouseleave = function () { video_player['title'] = video_player['data-title']; video_player['data-title'] = ''; };
- video_player.oncontextmenu = function () { video_player['title'] = video_player['data-title']; };
+ 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"] = "";
+ };
+ video_player.onmouseleave = function () {
+ video_player["title"] = video_player["data-title"];
+ video_player["data-title"] = "";
+ };
+ video_player.oncontextmenu = function () {
+ video_player["title"] = video_player["data-title"];
+ };
+ }
+
+ // For dynamically inserted elements
+ addEventListener("click", function (e) {
+ if (!e || !e.target) return;
+
+ var t = e.target;
+ var handler_name = t.getAttribute("data-onclick");
+
+ switch (handler_name) {
+ case "jump_to_time":
+ e.preventDefault();
+ var time = t.getAttribute("data-jump-time");
+ player.currentTime(time);
+ break;
+ case "get_youtube_replies":
+ var load_more = t.getAttribute("data-load-more") !== null;
+ var load_replies = t.getAttribute("data-load-replies") !== null;
+ get_youtube_replies(t, load_more, load_replies);
+ break;
+ case "toggle_parent":
+ e.preventDefault();
+ toggle_parent(t);
+ break;
+ default:
+ break;
+ }
+ });
+
+ 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);
+ };
+ });
+
+ document
+ .querySelectorAll('[data-onsubmit="return_false"]')
+ .forEach(function (el) {
+ el.onsubmit = function () {
+ return false;
+ };
+ });
+
+ document
+ .querySelectorAll('[data-onclick="mark_watched"]')
+ .forEach(function (el) {
+ el.onclick = function () {
+ mark_watched(el);
+ };
+ });
+ document
+ .querySelectorAll('[data-onclick="mark_unwatched"]')
+ .forEach(function (el) {
+ el.onclick = function () {
+ mark_unwatched(el);
+ };
+ });
+ document
+ .querySelectorAll('[data-onclick="add_playlist_video"]')
+ .forEach(function (el) {
+ el.onclick = function (e) {
+ add_playlist_video(e);
+ };
+ });
+ document
+ .querySelectorAll('[data-onclick="add_playlist_item"]')
+ .forEach(function (el) {
+ el.onclick = function (e) {
+ add_playlist_item(e);
+ };
+ });
+ document
+ .querySelectorAll('[data-onclick="remove_playlist_item"]')
+ .forEach(function (el) {
+ el.onclick = function (e) {
+ remove_playlist_item(e);
+ };
+ });
+ document
+ .querySelectorAll('[data-onclick="revoke_token"]')
+ .forEach(function (el) {
+ el.onclick = function () {
+ revoke_token(el);
+ };
+ });
+ document
+ .querySelectorAll('[data-onclick="remove_subscription"]')
+ .forEach(function (el) {
+ el.onclick = function () {
+ remove_subscription(el);
+ };
+ });
+ document
+ .querySelectorAll('[data-onclick="notification_requestPermission"]')
+ .forEach(function (el) {
+ el.onclick = function () {
+ Notification.requestPermission();
+ };
+ });
+
+ document
+ .querySelectorAll('[data-onrange="update_volume_value"]')
+ .forEach(function (el) {
+ function update_volume_value() {
+ document.getElementById("volume-value").textContent = el.value;
+ }
+ el.oninput = update_volume_value;
+ el.onchange = update_volume_value;
+ });
+
+ function revoke_token(target) {
+ var row = target.parentNode.parentNode.parentNode.parentNode.parentNode;
+ row.style.display = "none";
+ var count = document.getElementById("count");
+ count.textContent--;
+
+ var url =
+ "/token_ajax?action=revoke_token&redirect=false" +
+ "&referer=" +
+ encodeURIComponent(location.href) +
+ "&session=" +
+ target.getAttribute("data-session");
+
+ var payload =
+ "csrf_token=" +
+ target.parentNode.querySelector('input[name="csrf_token"]').value;
+
+ helpers.xhr(
+ "POST",
+ url,
+ { payload: payload },
+ {
+ onNon200: function (xhr) {
+ count.textContent++;
+ row.style.display = "";
+ },
+ },
+ );
+ }
+
+ function remove_subscription(target) {
+ var row = target.parentNode.parentNode.parentNode.parentNode.parentNode;
+ row.style.display = "none";
+ var count = document.getElementById("count");
+ count.textContent--;
+
+ var url =
+ "/subscription_ajax?action=remove_subscriptions&redirect=false" +
+ "&referer=" +
+ encodeURIComponent(location.href) +
+ "&c=" +
+ target.getAttribute("data-ucid");
+
+ var payload =
+ "csrf_token=" +
+ target.parentNode.querySelector('input[name="csrf_token"]').value;
+
+ helpers.xhr(
+ "POST",
+ url,
+ { payload: payload },
+ {
+ onNon200: function (xhr) {
+ count.textContent++;
+ row.style.display = "";
+ },
+ },
+ );
+ }
+
+ // Handle keypresses
+ addEventListener("keydown", function (event) {
+ // Ignore modifier keys
+ if (event.ctrlKey || event.metaKey) return;
+
+ // Ignore shortcuts if any text input is focused
+ let focused_tag = document.activeElement.tagName.toLowerCase();
+ const allowed = /^(button|checkbox|file|radio|submit)$/;
+
+ if (focused_tag === "textarea") return;
+ if (focused_tag === "input") {
+ let focused_type = document.activeElement.type.toLowerCase();
+ if (!allowed.test(focused_type)) return;
}
- // For dynamically inserted elements
- addEventListener('click', function (e) {
- if (!e || !e.target) return;
-
- var t = e.target;
- var handler_name = t.getAttribute('data-onclick');
-
- switch (handler_name) {
- case 'jump_to_time':
- e.preventDefault();
- var time = t.getAttribute('data-jump-time');
- player.currentTime(time);
- break;
- case 'get_youtube_replies':
- var load_more = t.getAttribute('data-load-more') !== null;
- var load_replies = t.getAttribute('data-load-replies') !== null;
- get_youtube_replies(t, load_more, load_replies);
- break;
- case 'toggle_parent':
- e.preventDefault();
- toggle_parent(t);
- break;
- default:
- break;
- }
- });
-
- 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); };
- });
-
- document.querySelectorAll('[data-onsubmit="return_false"]').forEach(function (el) {
- el.onsubmit = function () { return false; };
- });
-
- document.querySelectorAll('[data-onclick="mark_watched"]').forEach(function (el) {
- el.onclick = function () { mark_watched(el); };
- });
- document.querySelectorAll('[data-onclick="mark_unwatched"]').forEach(function (el) {
- el.onclick = function () { mark_unwatched(el); };
- });
- document.querySelectorAll('[data-onclick="add_playlist_video"]').forEach(function (el) {
- el.onclick = function (e) { add_playlist_video(e); };
- });
- document.querySelectorAll('[data-onclick="add_playlist_item"]').forEach(function (el) {
- el.onclick = function (e) { add_playlist_item(e); };
- });
- document.querySelectorAll('[data-onclick="remove_playlist_item"]').forEach(function (el) {
- el.onclick = function (e) { remove_playlist_item(e); };
- });
- document.querySelectorAll('[data-onclick="revoke_token"]').forEach(function (el) {
- el.onclick = function () { revoke_token(el); };
- });
- document.querySelectorAll('[data-onclick="remove_subscription"]').forEach(function (el) {
- el.onclick = function () { remove_subscription(el); };
- });
- document.querySelectorAll('[data-onclick="notification_requestPermission"]').forEach(function (el) {
- el.onclick = function () { Notification.requestPermission(); };
- });
-
- document.querySelectorAll('[data-onrange="update_volume_value"]').forEach(function (el) {
- function update_volume_value() {
- document.getElementById('volume-value').textContent = el.value;
- }
- el.oninput = update_volume_value;
- el.onchange = update_volume_value;
- });
-
-
- function revoke_token(target) {
- var row = target.parentNode.parentNode.parentNode.parentNode.parentNode;
- row.style.display = 'none';
- var count = document.getElementById('count');
- count.textContent--;
-
- var url = '/token_ajax?action=revoke_token&redirect=false' +
- '&referer=' + encodeURIComponent(location.href) +
- '&session=' + target.getAttribute('data-session');
-
- var payload = 'csrf_token=' + target.parentNode.querySelector('input[name="csrf_token"]').value;
-
- helpers.xhr('POST', url, {payload: payload}, {
- onNon200: function (xhr) {
- count.textContent++;
- row.style.display = '';
- }
- });
+ // Focus search bar on '/'
+ if (event.key === "/") {
+ document.getElementById("searchbox").focus();
+ event.preventDefault();
}
-
- function remove_subscription(target) {
- var row = target.parentNode.parentNode.parentNode.parentNode.parentNode;
- row.style.display = 'none';
- var count = document.getElementById('count');
- count.textContent--;
-
- var url = '/subscription_ajax?action=remove_subscriptions&redirect=false' +
- '&referer=' + encodeURIComponent(location.href) +
- '&c=' + target.getAttribute('data-ucid');
-
- var payload = 'csrf_token=' + target.parentNode.querySelector('input[name="csrf_token"]').value;
-
- helpers.xhr('POST', url, {payload: payload}, {
- onNon200: function (xhr) {
- count.textContent++;
- row.style.display = '';
- }
- });
- }
-
- // Handle keypresses
- addEventListener('keydown', function (event) {
- // Ignore modifier keys
- if (event.ctrlKey || event.metaKey) return;
-
- // Ignore shortcuts if any text input is focused
- let focused_tag = document.activeElement.tagName.toLowerCase();
- const allowed = /^(button|checkbox|file|radio|submit)$/;
-
- if (focused_tag === 'textarea') return;
- if (focused_tag === 'input') {
- let focused_type = document.activeElement.type.toLowerCase();
- if (!allowed.test(focused_type)) return;
- }
-
- // Focus search bar on '/'
- if (event.key === '/') {
- document.getElementById('searchbox').focus();
- event.preventDefault();
- }
- });
+ });
})();
diff --git a/assets/js/notifications.js b/assets/js/notifications.js
index 2c8552c8..0732d28d 100644
--- a/assets/js/notifications.js
+++ b/assets/js/notifications.js
@@ -31,7 +31,11 @@ async function get_subscriptions_call() {
}
// Start the retry mechanism
-const get_subscriptions = exponential_backoff(get_subscriptions_call, 100, 1000);
+const get_subscriptions = exponential_backoff(
+ get_subscriptions_call,
+ 100,
+ 1000,
+);
function create_notification_stream(subscriptions) {
// sse.js can't be replaced to EventSource in place as it lack support of payload and headers
@@ -96,7 +100,6 @@ function create_notification_stream(subscriptions) {
"Something went wrong with notifications, trying to reconnect...",
);
notifications = notifications_mock;
-
});
notifications.stream();
@@ -177,7 +180,7 @@ function exponential_backoff(
return function tryFunction() {
fn()
.then((response) => {
- attempt = 0;
+ attempt = 0;
})
.catch((error) => {
if (attempt < maxRetries) {
@@ -193,5 +196,5 @@ function exponential_backoff(
console.log("Max retries reached. Operation failed:", error);
}
});
- }
+ };
}
diff --git a/assets/js/pagination.js b/assets/js/pagination.js
index 2e560a34..9d67b839 100644
--- a/assets/js/pagination.js
+++ b/assets/js/pagination.js
@@ -1,93 +1,103 @@
-'use strict';
+"use strict";
-const CURRENT_CONTINUATION = (new URL(document.location)).searchParams.get("continuation");
+const CURRENT_CONTINUATION = new URL(document.location).searchParams.get(
+ "continuation",
+);
const CONT_CACHE_KEY = `continuation_cache_${encodeURIComponent(window.location.pathname)}`;
-function get_data(){
- return JSON.parse(sessionStorage.getItem(CONT_CACHE_KEY)) || [];
+function get_data() {
+ return JSON.parse(sessionStorage.getItem(CONT_CACHE_KEY)) || [];
}
-function save_data(){
- const prev_data = get_data();
- prev_data.push(CURRENT_CONTINUATION);
+function save_data() {
+ const prev_data = get_data();
+ prev_data.push(CURRENT_CONTINUATION);
- sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data));
+ sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data));
}
-function button_press(){
- let prev_data = get_data();
- if (!prev_data.length) return null;
+function button_press() {
+ let prev_data = get_data();
+ if (!prev_data.length) return null;
- // Sanity check. Nowhere should the current continuation token exist in the cache
- // but it can happen when using the browser's back feature. As such we'd need to travel
- // back to the point where the current continuation token first appears in order to
- // account for the rewind.
- const conflict_at = prev_data.indexOf(CURRENT_CONTINUATION);
- if (conflict_at != -1) {
- prev_data.length = conflict_at;
- }
+ // Sanity check. Nowhere should the current continuation token exist in the cache
+ // but it can happen when using the browser's back feature. As such we'd need to travel
+ // back to the point where the current continuation token first appears in order to
+ // account for the rewind.
+ const conflict_at = prev_data.indexOf(CURRENT_CONTINUATION);
+ if (conflict_at != -1) {
+ prev_data.length = conflict_at;
+ }
- const prev_ctoken = prev_data.pop();
-
- // On the first page, the stored continuation token is null.
- if (prev_ctoken === null) {
- sessionStorage.removeItem(CONT_CACHE_KEY);
- let url = set_continuation();
- window.location.href = url;
-
- return;
- }
-
- sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data));
- let url = set_continuation(prev_ctoken);
+ const prev_ctoken = prev_data.pop();
+ // On the first page, the stored continuation token is null.
+ if (prev_ctoken === null) {
+ sessionStorage.removeItem(CONT_CACHE_KEY);
+ let url = set_continuation();
window.location.href = url;
-};
+
+ return;
+ }
+
+ sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data));
+ let url = set_continuation(prev_ctoken);
+
+ window.location.href = url;
+}
// Method to set the current page's continuation token
// Removes the continuation parameter when a continuation token is not given
-function set_continuation(prev_ctoken = null){
- let url = window.location.href.split('?')[0];
- let params = window.location.href.split('?')[1];
- let url_params = new URLSearchParams(params);
+function set_continuation(prev_ctoken = null) {
+ let url = window.location.href.split("?")[0];
+ let params = window.location.href.split("?")[1];
+ let url_params = new URLSearchParams(params);
- if (prev_ctoken) {
- url_params.set("continuation", prev_ctoken);
- } else {
- url_params.delete('continuation');
- };
+ if (prev_ctoken) {
+ url_params.set("continuation", prev_ctoken);
+ } else {
+ url_params.delete("continuation");
+ }
- if(Array.from(url_params).length > 0){
- return `${url}?${url_params.toString()}`;
- } else {
- return url;
- }
+ if (Array.from(url_params).length > 0) {
+ return `${url}?${url_params.toString()}`;
+ } else {
+ return url;
+ }
}
-addEventListener('DOMContentLoaded', function(){
- const pagination_data = JSON.parse(document.getElementById('pagination-data').textContent);
- const next_page_containers = document.getElementsByClassName("page-next-container");
+addEventListener("DOMContentLoaded", function () {
+ const pagination_data = JSON.parse(
+ document.getElementById("pagination-data").textContent,
+ );
+ const next_page_containers = document.getElementsByClassName(
+ "page-next-container",
+ );
- for (let container of next_page_containers){
- const next_page_button = container.getElementsByClassName("pure-button")
+ for (let container of next_page_containers) {
+ const next_page_button = container.getElementsByClassName("pure-button");
- // exists?
- if (next_page_button.length > 0){
- next_page_button[0].addEventListener("click", save_data);
- }
+ // exists?
+ if (next_page_button.length > 0) {
+ next_page_button[0].addEventListener("click", save_data);
}
+ }
- // Only add previous page buttons when not on the first page
- if (CURRENT_CONTINUATION) {
- const prev_page_containers = document.getElementsByClassName("page-prev-container")
+ // Only add previous page buttons when not on the first page
+ if (CURRENT_CONTINUATION) {
+ const prev_page_containers = document.getElementsByClassName(
+ "page-prev-container",
+ );
- for (let container of prev_page_containers) {
- if (pagination_data.is_rtl) {
- container.innerHTML = ``
- } else {
- container.innerHTML = ``
- }
- container.getElementsByClassName("pure-button")[0].addEventListener("click", button_press);
- }
+ for (let container of prev_page_containers) {
+ if (pagination_data.is_rtl) {
+ container.innerHTML = ``;
+ } else {
+ container.innerHTML = ``;
+ }
+ container
+ .getElementsByClassName("pure-button")[0]
+ .addEventListener("click", button_press);
}
+ }
});
diff --git a/assets/js/player.js b/assets/js/player.js
index f32c9b56..ed74ed85 100644
--- a/assets/js/player.js
+++ b/assets/js/player.js
@@ -1,98 +1,99 @@
-'use strict';
-var player_data = JSON.parse(document.getElementById('player_data').textContent);
-var video_data = JSON.parse(document.getElementById('video_data').textContent);
+"use strict";
+var player_data = JSON.parse(
+ document.getElementById("player_data").textContent,
+);
+var video_data = JSON.parse(document.getElementById("video_data").textContent);
var options = {
- liveui: true,
- playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
- controlBar: {
- children: [
- 'playToggle',
- 'volumePanel',
- 'currentTimeDisplay',
- 'timeDivider',
- 'durationDisplay',
- 'progressControl',
- 'remainingTimeDisplay',
- 'Spacer',
- 'captionsButton',
- 'audioTrackButton',
- 'qualitySelector',
- 'playbackRateMenuButton',
- 'fullscreenToggle'
- ]
+ liveui: true,
+ playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
+ controlBar: {
+ children: [
+ "playToggle",
+ "volumePanel",
+ "currentTimeDisplay",
+ "timeDivider",
+ "durationDisplay",
+ "progressControl",
+ "remainingTimeDisplay",
+ "Spacer",
+ "captionsButton",
+ "audioTrackButton",
+ "qualitySelector",
+ "playbackRateMenuButton",
+ "fullscreenToggle",
+ ],
+ },
+ html5: {
+ preloadTextTracks: false,
+ vhs: {
+ overrideNative: true,
},
- html5: {
- preloadTextTracks: false,
- vhs: {
- overrideNative: true
- }
- }
+ },
};
if (player_data.aspect_ratio) {
- options.aspectRatio = player_data.aspect_ratio;
+ options.aspectRatio = player_data.aspect_ratio;
}
var embed_url = new URL(location);
-embed_url.searchParams.delete('v');
-var short_url = location.origin + '/' + video_data.id + embed_url.search;
-embed_url = location.origin + '/embed/' + video_data.id + embed_url.search;
+embed_url.searchParams.delete("v");
+var short_url = location.origin + "/" + video_data.id + embed_url.search;
+embed_url = location.origin + "/embed/" + video_data.id + embed_url.search;
-var save_player_pos_key = 'save_player_pos';
+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('local=true'))
- options.uri += '?local=true';
- }
- return options;
+videojs.Vhs.xhr.beforeRequest = function (options) {
+ // set local if requested not videoplayback
+ if (!options.uri.includes("videoplayback")) {
+ if (!options.uri.includes("local=true")) options.uri += "?local=true";
+ }
+ return options;
};
-var player = videojs('player', options);
+var player = videojs("player", options);
-player.on('error', function () {
- if (video_data.params.quality === 'dash') return;
+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
+ player.src(
+ player.currentSources().map(function (source) {
+ source.src += "&local=true";
+ return source;
+ }),
);
+ } else if (reloadMakesSense) {
+ setTimeout(function () {
+ console.warn("An error occurred in the player, reloading...");
- if (localNotDisabled) {
- // add local=true to all current sources
- player.src(player.currentSources().map(function (source) {
- source.src += '&local=true';
- return source;
- }));
- } else if (reloadMakesSense) {
- 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();
- // After load() all parameters are reset. Save them
- var currentTime = player.currentTime();
- var playbackRate = player.playbackRate();
- var paused = player.paused();
+ player.load();
- player.load();
+ if (currentTime > 0.5) currentTime -= 0.5;
- if (currentTime > 0.5) currentTime -= 0.5;
-
- player.currentTime(currentTime);
- player.playbackRate(playbackRate);
- if (!paused) player.play();
- }, 5000);
- }
+ player.currentTime(currentTime);
+ player.playbackRate(playbackRate);
+ if (!paused) player.play();
+ }, 5000);
+ }
});
-if (video_data.params.quality === 'dash') {
- player.reloadSourceOnError({
- errorInterval: 10
- });
+if (video_data.params.quality === "dash") {
+ player.reloadSourceOnError({
+ errorInterval: 10,
+ });
}
/**
@@ -103,14 +104,12 @@ if (video_data.params.quality === 'dash') {
* @returns {URL} urlWithTimeArg
*/
function addCurrentTimeToURL(url, base) {
- var urlUsed = new URL(url, base);
- urlUsed.searchParams.delete('start');
- var currentTime = Math.ceil(player.currentTime());
- if (currentTime > 0)
- urlUsed.searchParams.set('t', currentTime);
- else if (urlUsed.searchParams.has('t'))
- urlUsed.searchParams.delete('t');
- return urlUsed;
+ var urlUsed = new URL(url, base);
+ urlUsed.searchParams.delete("start");
+ var currentTime = Math.ceil(player.currentTime());
+ if (currentTime > 0) urlUsed.searchParams.set("t", currentTime);
+ else if (urlUsed.searchParams.has("t")) urlUsed.searchParams.delete("t");
+ return urlUsed;
}
/**
@@ -125,148 +124,182 @@ var timeupdate_last_ts = 5;
/**
* Callback that updates the timestamp on all external links
*/
-player.on('timeupdate', function () {
- // Only update once every second
- let current_ts = Math.floor(player.currentTime());
- if (current_ts > timeupdate_last_ts) timeupdate_last_ts = current_ts;
- else return;
+player.on("timeupdate", function () {
+ // Only update once every second
+ let current_ts = Math.floor(player.currentTime());
+ if (current_ts > timeupdate_last_ts) timeupdate_last_ts = current_ts;
+ else return;
- // YouTube links
+ // YouTube links
- let elem_yt_watch = document.getElementById('link-yt-watch');
- if (elem_yt_watch) {
- let base_url_yt_watch = elem_yt_watch.getAttribute('data-base-url');
- elem_yt_watch.href = addCurrentTimeToURL(base_url_yt_watch);
- }
-
- let elem_yt_embed = document.getElementById('link-yt-embed');
- if (elem_yt_embed) {
- let base_url_yt_embed = elem_yt_embed.getAttribute('data-base-url');
- elem_yt_embed.href = addCurrentTimeToURL(base_url_yt_embed);
- }
+ let elem_yt_watch = document.getElementById("link-yt-watch");
+ if (elem_yt_watch) {
+ let base_url_yt_watch = elem_yt_watch.getAttribute("data-base-url");
+ elem_yt_watch.href = addCurrentTimeToURL(base_url_yt_watch);
+ }
- // Invidious links
+ let elem_yt_embed = document.getElementById("link-yt-embed");
+ if (elem_yt_embed) {
+ let base_url_yt_embed = elem_yt_embed.getAttribute("data-base-url");
+ elem_yt_embed.href = addCurrentTimeToURL(base_url_yt_embed);
+ }
- let domain = window.location.origin;
+ // Invidious links
- let elem_iv_embed = document.getElementById('link-iv-embed');
- if (elem_iv_embed) {
- let base_url_iv_embed = elem_iv_embed.getAttribute('data-base-url');
- elem_iv_embed.href = addCurrentTimeToURL(base_url_iv_embed, domain);
- }
-
- let elem_iv_other = document.getElementById('link-iv-other');
- if (elem_iv_other) {
- let base_url_iv_other = elem_iv_other.getAttribute('data-base-url');
- elem_iv_other.href = addCurrentTimeToURL(base_url_iv_other, domain);
- }
+ let domain = window.location.origin;
+
+ let elem_iv_embed = document.getElementById("link-iv-embed");
+ if (elem_iv_embed) {
+ let base_url_iv_embed = elem_iv_embed.getAttribute("data-base-url");
+ elem_iv_embed.href = addCurrentTimeToURL(base_url_iv_embed, domain);
+ }
+
+ let elem_iv_other = document.getElementById("link-iv-other");
+ if (elem_iv_other) {
+ let base_url_iv_other = elem_iv_other.getAttribute("data-base-url");
+ elem_iv_other.href = addCurrentTimeToURL(base_url_iv_other, domain);
+ }
});
-
var shareOptions = {
- socials: ['fbFeed', 'tw', 'reddit', 'email'],
+ socials: ["fbFeed", "tw", "reddit", "email"],
- get url() {
- return addCurrentTimeToURL(short_url);
- },
- title: player_data.title,
- description: player_data.description,
- image: player_data.thumbnail,
- get embedCode() {
- // Single quotes inside here required. HTML inserted as is into value attribute of input
- return "";
- }
+ get url() {
+ return addCurrentTimeToURL(short_url);
+ },
+ title: player_data.title,
+ description: player_data.description,
+ image: player_data.thumbnail,
+ get embedCode() {
+ // Single quotes inside here required. HTML inserted as is into value attribute of input
+ return (
+ ""
+ );
+ },
};
-if (location.pathname.startsWith('/embed/')) {
- var overlay_content = '';
- player.overlay({
- overlays: [
- { start: 'loadstart', content: overlay_content, end: 'playing', align: 'top'},
- { start: 'pause', content: overlay_content, end: 'playing', align: 'top'}
- ]
- });
+if (location.pathname.startsWith("/embed/")) {
+ var overlay_content =
+ '";
+ player.overlay({
+ overlays: [
+ {
+ start: "loadstart",
+ content: overlay_content,
+ end: "playing",
+ align: "top",
+ },
+ {
+ start: "pause",
+ content: overlay_content,
+ end: "playing",
+ align: "top",
+ },
+ ],
+ });
}
// Detect mobile users and initialize mobileUi for better UX
// Detection code taken from https://stackoverflow.com/a/20293441
function isMobile() {
- try{ document.createEvent('TouchEvent'); return true; }
- catch(e){ return false; }
+ try {
+ document.createEvent("TouchEvent");
+ return true;
+ } catch (e) {
+ return false;
+ }
}
if (isMobile()) {
- player.mobileUi({ touchControls: { seekSeconds: 5 * player.playbackRate() } });
+ player.mobileUi({
+ touchControls: { seekSeconds: 5 * player.playbackRate() },
+ });
- var buttons = ['playToggle', 'volumePanel', 'captionsButton'];
+ var buttons = ["playToggle", "volumePanel", "captionsButton"];
- if (!video_data.params.listen && video_data.params.quality === 'dash') buttons.push('audioTrackButton');
- if (video_data.params.listen || video_data.params.quality !== 'dash') buttons.push('qualitySelector');
+ if (!video_data.params.listen && video_data.params.quality === "dash")
+ buttons.push("audioTrackButton");
+ if (video_data.params.listen || video_data.params.quality !== "dash")
+ buttons.push("qualitySelector");
- // Create new control bar object for operation buttons
- const ControlBar = videojs.getComponent('controlBar');
- let operations_bar = new ControlBar(player, {
- children: [],
- playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0]
- });
- buttons.slice(1).forEach(function (child) {operations_bar.addChild(child);});
+ // Create new control bar object for operation buttons
+ const ControlBar = videojs.getComponent("controlBar");
+ let operations_bar = new ControlBar(player, {
+ children: [],
+ playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
+ });
+ buttons.slice(1).forEach(function (child) {
+ operations_bar.addChild(child);
+ });
- // Remove operation buttons from primary control bar
- var primary_control_bar = player.getChild('controlBar');
- buttons.forEach(function (child) {primary_control_bar.removeChild(child);});
+ // Remove operation buttons from primary control bar
+ var primary_control_bar = player.getChild("controlBar");
+ buttons.forEach(function (child) {
+ primary_control_bar.removeChild(child);
+ });
- var operations_bar_element = operations_bar.el();
- operations_bar_element.classList.add('mobile-operations-bar');
- player.addChild(operations_bar);
+ var operations_bar_element = operations_bar.el();
+ 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
- var playback_element = document.getElementsByClassName('vjs-playback-rate')[0];
- operations_bar_element.append(playback_element);
+ // Playback menu doesn't work when it's initialized outside of the primary control bar
+ var playback_element =
+ document.getElementsByClassName("vjs-playback-rate")[0];
+ operations_bar_element.append(playback_element);
- // The share and http source selector element can't be fetched till the players ready.
- player.one('playing', function () {
- var share_element = document.getElementsByClassName('vjs-share-control')[0];
- operations_bar_element.append(share_element);
+ // The share and http source selector element can't be fetched till the players ready.
+ player.one("playing", function () {
+ var share_element = document.getElementsByClassName("vjs-share-control")[0];
+ operations_bar_element.append(share_element);
- if (!video_data.params.listen && 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);
- }
- });
+ if (!video_data.params.listen && 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);
+ }
+ });
}
// Enable VR video support
if (!video_data.params.listen && video_data.vr && video_data.params.vr_mode) {
- player.crossOrigin('anonymous');
- switch (video_data.projection_type) {
- case 'EQUIRECTANGULAR':
- player.vr({projection: 'equirectangular'});
- default: // Should only be 'MESH' but we'll use this as a fallback.
- player.vr({projection: 'EAC'});
- }
+ player.crossOrigin("anonymous");
+ switch (video_data.projection_type) {
+ case "EQUIRECTANGULAR":
+ player.vr({ projection: "equirectangular" });
+ default: // Should only be 'MESH' but we'll use this as a fallback.
+ player.vr({ projection: "EAC" });
+ }
}
// Add markers
if (video_data.params.video_start > 0 || video_data.params.video_end > 0) {
- var markers = [{ time: video_data.params.video_start, text: 'Start' }];
+ var markers = [{ time: video_data.params.video_start, text: "Start" }];
- if (video_data.params.video_end < 0) {
- markers.push({ time: video_data.length_seconds - 0.5, text: 'End' });
- } else {
- markers.push({ time: video_data.params.video_end, text: 'End' });
- }
+ if (video_data.params.video_end < 0) {
+ markers.push({ time: video_data.length_seconds - 0.5, text: "End" });
+ } else {
+ markers.push({ time: video_data.params.video_end, text: "End" });
+ }
- player.markers({
- onMarkerReached: function (marker) {
- if (marker.text === 'End')
- player.loop() ? player.markers.prev('Start') : player.pause();
- },
- markers: markers
- });
+ player.markers({
+ onMarkerReached: function (marker) {
+ if (marker.text === "End")
+ player.loop() ? player.markers.prev("Start") : player.pause();
+ },
+ markers: markers,
+ });
- player.currentTime(video_data.params.video_start);
+ player.currentTime(video_data.params.video_start);
}
player.volume(video_data.params.volume / 100);
@@ -279,11 +312,12 @@ player.playbackRate(video_data.params.speed);
* @returns {String|null} cookieValue
*/
function getCookieValue(name) {
- var cookiePrefix = name + '=';
- var matchedCookie = document.cookie.split(';').find(function (item) {return item.includes(cookiePrefix);});
- if (matchedCookie)
- return matchedCookie.replace(cookiePrefix, '');
- return null;
+ var cookiePrefix = name + "=";
+ var matchedCookie = document.cookie.split(";").find(function (item) {
+ return item.includes(cookiePrefix);
+ });
+ if (matchedCookie) return matchedCookie.replace(cookiePrefix, "");
+ return null;
}
/**
@@ -293,502 +327,596 @@ function getCookieValue(name) {
* @param {number} newSpeed New speed defined (null if unchanged)
*/
function updateCookie(newVolume, newSpeed) {
- var volumeValue = newVolume !== null ? newVolume : video_data.params.volume;
- var speedValue = newSpeed !== null ? newSpeed : video_data.params.speed;
+ var volumeValue = newVolume !== null ? newVolume : video_data.params.volume;
+ var speedValue = newSpeed !== null ? newSpeed : video_data.params.speed;
- var cookieValue = getCookieValue('PREFS');
- var cookieData;
+ var cookieValue = getCookieValue("PREFS");
+ var cookieData;
- if (cookieValue !== null) {
- var cookieJson = JSON.parse(decodeURIComponent(cookieValue));
- cookieJson.volume = volumeValue;
- cookieJson.speed = speedValue;
- cookieData = encodeURIComponent(JSON.stringify(cookieJson));
- } else {
- cookieData = encodeURIComponent(JSON.stringify({ 'volume': volumeValue, 'speed': speedValue }));
- }
+ if (cookieValue !== null) {
+ var cookieJson = JSON.parse(decodeURIComponent(cookieValue));
+ cookieJson.volume = volumeValue;
+ cookieJson.speed = speedValue;
+ cookieData = encodeURIComponent(JSON.stringify(cookieJson));
+ } else {
+ cookieData = encodeURIComponent(
+ JSON.stringify({ volume: volumeValue, speed: speedValue }),
+ );
+ }
- // Set expiration in 2 year
- var date = new Date();
- date.setFullYear(date.getFullYear() + 2);
+ // Set expiration in 2 year
+ var date = new Date();
+ date.setFullYear(date.getFullYear() + 2);
- var ipRegex = /^((\d+\.){3}\d+|[\dA-Fa-f]*:[\d:A-Fa-f]*:[\d:A-Fa-f]+)$/;
- var domainUsed = location.hostname;
+ 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
- if (domainUsed.charAt(0) !== '.' && !ipRegex.test(domainUsed) && domainUsed !== 'localhost')
- 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 = "." + location.hostname;
- var secure = location.protocol.startsWith("https") ? " Secure;" : "";
+ var secure = location.protocol.startsWith("https") ? " Secure;" : "";
- document.cookie = 'PREFS=' + cookieData + '; SameSite=Lax; path=/; domain=' +
- domainUsed + '; expires=' + date.toGMTString() + ';' + secure;
+ document.cookie =
+ "PREFS=" +
+ cookieData +
+ "; SameSite=Lax; path=/; domain=" +
+ domainUsed +
+ "; expires=" +
+ date.toGMTString() +
+ ";" +
+ secure;
- video_data.params.volume = volumeValue;
- video_data.params.speed = speedValue;
+ video_data.params.volume = volumeValue;
+ video_data.params.speed = speedValue;
}
-player.on('ratechange', function () {
- updateCookie(null, player.playbackRate());
- if (isMobile()) {
- player.mobileUi({ touchControls: { seekSeconds: 5 * player.playbackRate() } });
- }
+player.on("ratechange", function () {
+ updateCookie(null, player.playbackRate());
+ if (isMobile()) {
+ player.mobileUi({
+ touchControls: { seekSeconds: 5 * player.playbackRate() },
+ });
+ }
});
-player.on('volumechange', function () {
- updateCookie(Math.ceil(player.volume() * 100), null);
+player.on("volumechange", function () {
+ updateCookie(Math.ceil(player.volume() * 100), null);
});
-player.on('waiting', function () {
- if (player.playbackRate() > 1 && player.liveTracker.isLive() && player.liveTracker.atLiveEdge()) {
- console.info('Player has caught up to source, resetting playbackRate');
- player.playbackRate(1);
- }
+player.on("waiting", function () {
+ if (
+ player.playbackRate() > 1 &&
+ player.liveTracker.isLive() &&
+ player.liveTracker.atLiveEdge()
+ ) {
+ console.info("Player has caught up to source, resetting playbackRate");
+ player.playbackRate(1);
+ }
});
-if (video_data.premiere_timestamp && Math.round(new Date() / 1000) < video_data.premiere_timestamp) {
- player.getChild('bigPlayButton').hide();
+if (
+ video_data.premiere_timestamp &&
+ Math.round(new Date() / 1000) < video_data.premiere_timestamp
+) {
+ player.getChild("bigPlayButton").hide();
}
if (video_data.params.save_player_pos) {
- const url = new URL(location);
- const hasTimeParam = url.searchParams.has('t');
- const rememberedTime = get_video_time();
- let lastUpdated = 0;
+ const url = new URL(location);
+ const hasTimeParam = url.searchParams.has("t");
+ const rememberedTime = get_video_time();
+ let lastUpdated = 0;
- if(!hasTimeParam) {
- if (rememberedTime >= video_data.length_seconds - 20)
- set_seconds_after_start(0);
- else
- set_seconds_after_start(rememberedTime);
+ if (!hasTimeParam) {
+ if (rememberedTime >= video_data.length_seconds - 20)
+ set_seconds_after_start(0);
+ else set_seconds_after_start(rememberedTime);
+ }
+
+ player.on("timeupdate", function () {
+ const raw = player.currentTime();
+ const time = Math.floor(raw);
+
+ if (lastUpdated !== time && raw <= video_data.length_seconds - 15) {
+ save_video_time(time);
+ lastUpdated = time;
}
-
- player.on('timeupdate', function () {
- const raw = player.currentTime();
- const time = Math.floor(raw);
-
- if(lastUpdated !== time && raw <= video_data.length_seconds - 15) {
- save_video_time(time);
- lastUpdated = time;
- }
- });
-}
-else remove_all_video_times();
+ });
+} else remove_all_video_times();
if (video_data.params.autoplay) {
- var bpb = player.getChild('bigPlayButton');
- bpb.hide();
+ var bpb = player.getChild("bigPlayButton");
+ bpb.hide();
- player.ready(function () {
- new Promise(function (resolve, reject) {
- setTimeout(function () {resolve(1);}, 1);
- }).then(function (result) {
- var promise = player.play();
+ player.ready(function () {
+ new Promise(function (resolve, reject) {
+ setTimeout(function () {
+ resolve(1);
+ }, 1);
+ }).then(function (result) {
+ var promise = player.play();
- if (promise !== undefined) {
- promise.then(function () {
- }).catch(function (error) {
- bpb.show();
- });
- }
- });
+ if (promise !== undefined) {
+ promise
+ .then(function () {})
+ .catch(function (error) {
+ bpb.show();
+ });
+ }
});
+ });
}
-if (!video_data.params.listen && video_data.params.quality === 'dash') {
- player.httpSourceSelector();
+if (!video_data.params.listen && video_data.params.quality === "dash") {
+ player.httpSourceSelector();
- if (video_data.params.quality_dash !== 'auto') {
- player.ready(function () {
- player.on('loadedmetadata', function () {
- const qualityLevels = Array.from(player.qualityLevels()).sort(function (a, b) {return a.height - b.height;});
- let targetQualityLevel;
- switch (video_data.params.quality_dash) {
- case 'best':
- targetQualityLevel = qualityLevels.length - 1;
- break;
- case 'worst':
- targetQualityLevel = 0;
- break;
- default:
- const targetHeight = parseInt(video_data.params.quality_dash);
- for (let i = 0; i < qualityLevels.length; i++) {
- if (qualityLevels[i].height <= targetHeight)
- targetQualityLevel = i;
- else
- break;
- }
- }
- qualityLevels.forEach(function (level, index) {
- level.enabled = (index === targetQualityLevel);
- });
- });
+ if (video_data.params.quality_dash !== "auto") {
+ player.ready(function () {
+ player.on("loadedmetadata", function () {
+ const qualityLevels = Array.from(player.qualityLevels()).sort(
+ function (a, b) {
+ return a.height - b.height;
+ },
+ );
+ let targetQualityLevel;
+ switch (video_data.params.quality_dash) {
+ case "best":
+ targetQualityLevel = qualityLevels.length - 1;
+ break;
+ case "worst":
+ targetQualityLevel = 0;
+ break;
+ default:
+ const targetHeight = parseInt(video_data.params.quality_dash);
+ for (let i = 0; i < qualityLevels.length; i++) {
+ if (qualityLevels[i].height <= targetHeight)
+ targetQualityLevel = i;
+ else break;
+ }
+ }
+ qualityLevels.forEach(function (level, index) {
+ level.enabled = index === targetQualityLevel;
});
- }
+ });
+ });
+ }
}
player.vttThumbnails({
- src: '/api/v1/storyboards/' + video_data.id + '?height=90',
- showTimestamp: true
+ src: "/api/v1/storyboards/" + video_data.id + "?height=90",
+ showTimestamp: true,
});
// Enable annotations
if (!video_data.params.listen && video_data.params.annotations) {
- addEventListener('load', function (e) {
- addEventListener('__ar_annotation_click', function (e) {
- const url = e.detail.url,
- target = e.detail.target,
- seconds = e.detail.seconds;
- var path = new URL(url);
+ addEventListener("load", function (e) {
+ addEventListener("__ar_annotation_click", function (e) {
+ const url = e.detail.url,
+ target = e.detail.target,
+ seconds = e.detail.seconds;
+ var path = new URL(url);
- if (path.href.startsWith('https://www.youtube.com/watch?') && seconds) {
- path.search += '&t=' + seconds;
- }
+ if (path.href.startsWith("https://www.youtube.com/watch?") && seconds) {
+ path.search += "&t=" + seconds;
+ }
- path = path.pathname + path.search;
-
- if (target === 'current') {
- location.href = path;
- } else if (target === 'new') {
- 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 });
- }
- }
- });
+ path = path.pathname + path.search;
+ if (target === "current") {
+ location.href = path;
+ } else if (target === "new") {
+ 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,
+ });
+ }
+ },
+ },
+ );
+ });
}
function change_volume(delta) {
- const curVolume = player.volume();
- let newVolume = curVolume + delta;
- newVolume = helpers.clamp(newVolume, 0, 1);
- player.volume(newVolume);
+ const curVolume = player.volume();
+ let newVolume = curVolume + delta;
+ newVolume = helpers.clamp(newVolume, 0, 1);
+ player.volume(newVolume);
}
function toggle_muted() {
- player.muted(!player.muted());
+ player.muted(!player.muted());
}
function skip_seconds(delta) {
- const duration = player.duration();
- const curTime = player.currentTime();
- let newTime = curTime + delta;
- newTime = helpers.clamp(newTime, 0, duration);
- player.currentTime(newTime);
+ const duration = player.duration();
+ const curTime = player.currentTime();
+ let newTime = curTime + delta;
+ newTime = helpers.clamp(newTime, 0, duration);
+ player.currentTime(newTime);
}
function set_seconds_after_start(delta) {
- const start = video_data.params.video_start;
- player.currentTime(start + delta);
+ const start = video_data.params.video_start;
+ player.currentTime(start + delta);
}
function save_video_time(seconds) {
- const all_video_times = get_all_video_times();
- all_video_times[video_data.id] = seconds;
- helpers.storage.set(save_player_pos_key, all_video_times);
+ const all_video_times = get_all_video_times();
+ all_video_times[video_data.id] = seconds;
+ helpers.storage.set(save_player_pos_key, all_video_times);
}
function get_video_time() {
- return get_all_video_times()[video_data.id] || 0;
+ return get_all_video_times()[video_data.id] || 0;
}
function get_all_video_times() {
- return helpers.storage.get(save_player_pos_key) || {};
+ return helpers.storage.get(save_player_pos_key) || {};
}
function remove_all_video_times() {
- helpers.storage.remove(save_player_pos_key);
+ helpers.storage.remove(save_player_pos_key);
}
function set_time_percent(percent) {
- const duration = player.duration();
- const newTime = duration * (percent / 100);
- player.currentTime(newTime);
+ const duration = player.duration();
+ const newTime = duration * (percent / 100);
+ player.currentTime(newTime);
}
-function play() { player.play(); }
-function pause() { player.pause(); }
-function stop() { player.pause(); player.currentTime(0); }
-function toggle_play() { player.paused() ? play() : pause(); }
+function play() {
+ player.play();
+}
+function pause() {
+ player.pause();
+}
+function stop() {
+ player.pause();
+ player.currentTime(0);
+}
+function toggle_play() {
+ player.paused() ? play() : pause();
+}
const toggle_captions = (function () {
- let toggledTrack = null;
+ let toggledTrack = null;
- function bindChange(onOrOff) {
- player.textTracks()[onOrOff]('change', function (e) {
- toggledTrack = null;
- });
+ 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.
+ function setMode(track, mode) {
+ bindChange("off");
+ track.mode = mode;
+ setTimeout(function () {
+ bindChange("on");
+ }, 0);
+ }
+
+ bindChange("on");
+ return function () {
+ if (toggledTrack !== null) {
+ if (toggledTrack.mode !== "showing") {
+ setMode(toggledTrack, "showing");
+ } else {
+ setMode(toggledTrack, "disabled");
+ }
+ toggledTrack = null;
+ return;
}
- // Wrapper function to ignore our own emitted events and only listen
- // to events emitted by Video.js on click on the captions menu items.
- function setMode(track, mode) {
- bindChange('off');
- track.mode = mode;
- setTimeout(function () {
- bindChange('on');
- }, 0);
+ // Used as a fallback if no captions are currently active.
+ // TODO: Make this more intelligent by e.g. relying on browser language.
+ let fallbackCaptionsTrack = null;
+
+ const tracks = player.textTracks();
+ for (let i = 0; i < tracks.length; i++) {
+ const track = tracks[i];
+ if (track.kind !== "captions") continue;
+
+ if (fallbackCaptionsTrack === null) {
+ fallbackCaptionsTrack = track;
+ }
+ if (track.mode === "showing") {
+ setMode(track, "disabled");
+ toggledTrack = track;
+ return;
+ }
}
- bindChange('on');
- return function () {
- if (toggledTrack !== null) {
- if (toggledTrack.mode !== 'showing') {
- setMode(toggledTrack, 'showing');
- } else {
- setMode(toggledTrack, 'disabled');
- }
- toggledTrack = null;
- return;
- }
-
- // Used as a fallback if no captions are currently active.
- // TODO: Make this more intelligent by e.g. relying on browser language.
- let fallbackCaptionsTrack = null;
-
- const tracks = player.textTracks();
- for (let i = 0; i < tracks.length; i++) {
- const track = tracks[i];
- if (track.kind !== 'captions') continue;
-
- if (fallbackCaptionsTrack === null) {
- fallbackCaptionsTrack = track;
- }
- if (track.mode === 'showing') {
- setMode(track, 'disabled');
- toggledTrack = track;
- return;
- }
- }
-
- // Fallback if no captions are currently active.
- if (fallbackCaptionsTrack !== null) {
- setMode(fallbackCaptionsTrack, 'showing');
- toggledTrack = fallbackCaptionsTrack;
- }
- };
+ // Fallback if no captions are currently active.
+ if (fallbackCaptionsTrack !== null) {
+ setMode(fallbackCaptionsTrack, "showing");
+ toggledTrack = fallbackCaptionsTrack;
+ }
+ };
})();
function toggle_fullscreen() {
- player.isFullscreen() ? player.exitFullscreen() : player.requestFullscreen();
+ player.isFullscreen() ? player.exitFullscreen() : player.requestFullscreen();
}
function increase_playback_rate(steps) {
- const maxIndex = options.playbackRates.length - 1;
- const curIndex = options.playbackRates.indexOf(player.playbackRate());
- let newIndex = curIndex + steps;
- newIndex = helpers.clamp(newIndex, 0, maxIndex);
- player.playbackRate(options.playbackRates[newIndex]);
+ const maxIndex = options.playbackRates.length - 1;
+ const curIndex = options.playbackRates.indexOf(player.playbackRate());
+ let newIndex = curIndex + steps;
+ newIndex = helpers.clamp(newIndex, 0, maxIndex);
+ player.playbackRate(options.playbackRates[newIndex]);
}
-addEventListener('keydown', function (e) {
- if (e.target.tagName.toLowerCase() === 'input') {
- // Ignore input when focus is on certain elements, e.g. form fields.
- return;
+addEventListener(
+ "keydown",
+ function (e) {
+ if (e.target.tagName.toLowerCase() === "input") {
+ // Ignore input when focus is on certain elements, e.g. form fields.
+ return;
}
// See https://github.com/ctd1500/videojs-hotkeys/blob/bb4a158b2e214ccab87c2e7b95f42bc45c6bfd87/videojs.hotkeys.js#L310-L313
- const isPlayerFocused = false
- || e.target === document.querySelector('.video-js')
- || e.target === document.querySelector('.vjs-tech')
- || e.target === document.querySelector('.iframeblocker')
- || e.target === document.querySelector('.vjs-control-bar')
- ;
+ const isPlayerFocused =
+ false ||
+ e.target === document.querySelector(".video-js") ||
+ e.target === document.querySelector(".vjs-tech") ||
+ e.target === document.querySelector(".iframeblocker") ||
+ e.target === document.querySelector(".vjs-control-bar");
let action = null;
const code = e.keyCode;
const decoratedKey =
- e.key
- + (e.altKey ? '+alt' : '')
- + (e.ctrlKey ? '+ctrl' : '')
- + (e.metaKey ? '+meta' : '')
- ;
+ e.key +
+ (e.altKey ? "+alt" : "") +
+ (e.ctrlKey ? "+ctrl" : "") +
+ (e.metaKey ? "+meta" : "");
switch (decoratedKey) {
- case ' ':
- case 'k':
- case 'MediaPlayPause':
- action = toggle_play;
- break;
+ case " ":
+ case "k":
+ case "MediaPlayPause":
+ action = toggle_play;
+ break;
- case 'MediaPlay': action = play; break;
- case 'MediaPause': action = pause; break;
- case 'MediaStop': action = stop; break;
+ case "MediaPlay":
+ action = play;
+ break;
+ case "MediaPause":
+ action = pause;
+ break;
+ case "MediaStop":
+ action = stop;
+ break;
- case 'ArrowUp':
- if (isPlayerFocused) action = change_volume.bind(this, 0.1);
- break;
- case 'ArrowDown':
- if (isPlayerFocused) action = change_volume.bind(this, -0.1);
- break;
+ case "ArrowUp":
+ if (isPlayerFocused) action = change_volume.bind(this, 0.1);
+ break;
+ case "ArrowDown":
+ if (isPlayerFocused) action = change_volume.bind(this, -0.1);
+ break;
- case 'm':
- action = toggle_muted;
- break;
+ case "m":
+ action = toggle_muted;
+ break;
- case 'ArrowRight':
- case 'MediaFastForward':
- action = skip_seconds.bind(this, 5 * player.playbackRate());
- break;
- case 'ArrowLeft':
- case 'MediaTrackPrevious':
- action = skip_seconds.bind(this, -5 * player.playbackRate());
- break;
- case 'l':
- action = skip_seconds.bind(this, 10 * player.playbackRate());
- break;
- case 'j':
- action = skip_seconds.bind(this, -10 * player.playbackRate());
- break;
+ case "ArrowRight":
+ case "MediaFastForward":
+ action = skip_seconds.bind(this, 5 * player.playbackRate());
+ break;
+ case "ArrowLeft":
+ case "MediaTrackPrevious":
+ action = skip_seconds.bind(this, -5 * player.playbackRate());
+ break;
+ case "l":
+ action = skip_seconds.bind(this, 10 * player.playbackRate());
+ break;
+ case "j":
+ action = skip_seconds.bind(this, -10 * player.playbackRate());
+ break;
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- // Ignore numpad numbers
- if (code > 57) break;
+ case "0":
+ case "1":
+ case "2":
+ case "3":
+ case "4":
+ case "5":
+ case "6":
+ case "7":
+ case "8":
+ case "9":
+ // Ignore numpad numbers
+ if (code > 57) break;
- const percent = (code - 48) * 10;
- action = set_time_percent.bind(this, percent);
- break;
+ const percent = (code - 48) * 10;
+ action = set_time_percent.bind(this, percent);
+ break;
- case 'c': action = toggle_captions; break;
- case 'f': action = toggle_fullscreen; break;
+ case "c":
+ action = toggle_captions;
+ break;
+ case "f":
+ action = toggle_fullscreen;
+ break;
- case 'N':
- case 'MediaTrackNext':
- action = next_video;
- break;
- case 'P':
- case 'MediaTrackPrevious':
- // TODO: Add support to play back previous video.
- break;
+ case "N":
+ case "MediaTrackNext":
+ action = next_video;
+ break;
+ case "P":
+ case "MediaTrackPrevious":
+ // TODO: Add support to play back previous video.
+ 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;
+ // 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;
+ case ">":
+ action = increase_playback_rate.bind(this, 1);
+ break;
+ case "<":
+ action = increase_playback_rate.bind(this, -1);
+ break;
- default:
- console.info('Unhandled key down event: %s:', decoratedKey, e);
- break;
+ default:
+ console.info("Unhandled key down event: %s:", decoratedKey, e);
+ break;
}
if (action) {
- e.preventDefault();
- action();
+ e.preventDefault();
+ action();
}
-}, false);
+ },
+ false,
+);
// 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 pEl = document.getElementById('player');
+ const pEl = document.getElementById("player");
- var volumeHover = false;
- var volumeSelector = pEl.querySelector('.vjs-volume-menu-button') || pEl.querySelector('.vjs-volume-panel');
- if (volumeSelector !== null) {
- volumeSelector.onmouseover = function () { volumeHover = true; };
- volumeSelector.onmouseout = function () { volumeHover = false; };
- }
+ var volumeHover = false;
+ var volumeSelector =
+ pEl.querySelector(".vjs-volume-menu-button") ||
+ pEl.querySelector(".vjs-volume-panel");
+ if (volumeSelector !== null) {
+ volumeSelector.onmouseover = function () {
+ volumeHover = true;
+ };
+ volumeSelector.onmouseout = function () {
+ volumeHover = false;
+ };
+ }
- function mouseScroll(event) {
- // When controls are disabled, hotkeys will be disabled as well
- if (!player.controls() || !volumeHover) return;
+ function mouseScroll(event) {
+ // When controls are disabled, hotkeys will be disabled as well
+ if (!player.controls() || !volumeHover) return;
- event.preventDefault();
- var wheelMove = event.wheelDelta || -event.detail;
- var volumeSign = Math.sign(wheelMove);
+ event.preventDefault();
+ var wheelMove = event.wheelDelta || -event.detail;
+ var volumeSign = Math.sign(wheelMove);
- change_volume(volumeSign * 0.05); // decrease/increase by 5%
- }
+ change_volume(volumeSign * 0.05); // decrease/increase by 5%
+ }
- player.on('mousewheel', mouseScroll);
- player.on('DOMMouseScroll', mouseScroll);
-}());
+ 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);
// show the preferred caption by default
if (player_data.preferred_caption_found) {
- player.ready(function () {
- if (!video_data.params.listen && video_data.params.quality === 'dash') {
- // play.textTracks()[0] on DASH mode is showing some debug messages
- player.textTracks()[1].mode = 'showing';
- } else {
- player.textTracks()[0].mode = 'showing';
- }
- });
+ player.ready(function () {
+ if (!video_data.params.listen && video_data.params.quality === "dash") {
+ // play.textTracks()[0] on DASH mode is showing some debug messages
+ player.textTracks()[1].mode = "showing";
+ } else {
+ player.textTracks()[0].mode = "showing";
+ }
+ });
}
// Safari audio double duration fix
-if (navigator.vendor === 'Apple Computer, Inc.' && video_data.params.listen) {
- player.on('loadedmetadata', function () {
- player.on('timeupdate', function () {
- if (player.remainingTime() < player.duration() / 2 && player.remainingTime() >= 2) {
- player.currentTime(player.duration() - 1);
- }
- });
+if (navigator.vendor === "Apple Computer, Inc." && video_data.params.listen) {
+ player.on("loadedmetadata", function () {
+ player.on("timeupdate", function () {
+ if (
+ player.remainingTime() < player.duration() / 2 &&
+ player.remainingTime() >= 2
+ ) {
+ player.currentTime(player.duration() - 1);
+ }
});
+ });
}
// Safari screen timeout on looped video playback fix
-if (navigator.vendor === 'Apple Computer, Inc.' && !video_data.params.listen && video_data.params.video_loop) {
- player.loop(false);
- player.ready(function () {
- player.on('ended', function () {
- player.currentTime(0);
- player.play();
- });
+if (
+ navigator.vendor === "Apple Computer, Inc." &&
+ !video_data.params.listen &&
+ video_data.params.video_loop
+) {
+ player.loop(false);
+ player.ready(function () {
+ player.on("ended", function () {
+ player.currentTime(0);
+ player.play();
});
+ });
}
// Watch on Invidious link
-if (location.pathname.startsWith('/embed/')) {
- const Button = videojs.getComponent('Button');
- let watch_on_invidious_button = new Button(player);
+if (location.pathname.startsWith("/embed/")) {
+ const Button = videojs.getComponent("Button");
+ let watch_on_invidious_button = new Button(player);
- // Create hyperlink for current instance
- var redirect_element = document.createElement('a');
- redirect_element.setAttribute('href', location.pathname.replace('/embed/', '/watch?v='));
- redirect_element.appendChild(document.createTextNode('Invidious'));
+ // Create hyperlink for current instance
+ var redirect_element = document.createElement("a");
+ redirect_element.setAttribute(
+ "href",
+ location.pathname.replace("/embed/", "/watch?v="),
+ );
+ redirect_element.appendChild(document.createTextNode("Invidious"));
- watch_on_invidious_button.el().appendChild(redirect_element);
- watch_on_invidious_button.addClass('watch-on-invidious');
+ watch_on_invidious_button.el().appendChild(redirect_element);
+ watch_on_invidious_button.addClass("watch-on-invidious");
- var cb = player.getChild('ControlBar');
- cb.addChild(watch_on_invidious_button);
+ var cb = player.getChild("ControlBar");
+ cb.addChild(watch_on_invidious_button);
}
-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);
+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);
});
});
diff --git a/assets/js/playlist_widget.js b/assets/js/playlist_widget.js
index d40ae985..7001061b 100644
--- a/assets/js/playlist_widget.js
+++ b/assets/js/playlist_widget.js
@@ -1,55 +1,83 @@
-'use strict';
-var playlist_data = JSON.parse(document.getElementById('playlist_data').textContent);
-var payload = 'csrf_token=' + playlist_data.csrf_token;
+"use strict";
+var playlist_data = JSON.parse(
+ document.getElementById("playlist_data").textContent,
+);
+var payload = "csrf_token=" + playlist_data.csrf_token;
function add_playlist_video(event) {
- const target = event.target;
- var select = document.querySelector("#playlists");
- var option = select.children[select.selectedIndex];
+ const target = event.target;
+ var select = document.querySelector("#playlists");
+ var option = select.children[select.selectedIndex];
- var url = '/playlist_ajax?action=add_video&redirect=false' +
- '&video_id=' + target.getAttribute('data-id') +
- '&playlist_id=' + option.getAttribute('data-plid');
+ var url =
+ "/playlist_ajax?action=add_video&redirect=false" +
+ "&video_id=" +
+ target.getAttribute("data-id") +
+ "&playlist_id=" +
+ option.getAttribute("data-plid");
- helpers.xhr('POST', url, {payload: payload}, {
- on200: function (response) {
- option.textContent = '✓ ' + option.textContent;
- }
- });
+ helpers.xhr(
+ "POST",
+ url,
+ { payload: payload },
+ {
+ on200: function (response) {
+ option.textContent = "✓ " + option.textContent;
+ },
+ },
+ );
}
function add_playlist_item(event) {
- event.preventDefault();
- const target = event.target;
- const video_id = target.getAttribute('data-id');
- var card = document.querySelector(`#video-card-${video_id}`);
- card.classList.add("hide");
+ event.preventDefault();
+ const target = event.target;
+ const video_id = target.getAttribute("data-id");
+ var card = document.querySelector(`#video-card-${video_id}`);
+ card.classList.add("hide");
- var url = '/playlist_ajax?action=add_video&redirect=false' +
- '&video_id=' + target.getAttribute('data-id') +
- '&playlist_id=' + target.getAttribute('data-plid');
+ var url =
+ "/playlist_ajax?action=add_video&redirect=false" +
+ "&video_id=" +
+ target.getAttribute("data-id") +
+ "&playlist_id=" +
+ target.getAttribute("data-plid");
- helpers.xhr('POST', url, {payload: payload}, {
- onNon200: function (xhr) {
- card.classList.remove("hide");
- }
- });
+ helpers.xhr(
+ "POST",
+ url,
+ { payload: payload },
+ {
+ onNon200: function (xhr) {
+ card.classList.remove("hide");
+ },
+ },
+ );
}
function remove_playlist_item(event) {
- event.preventDefault();
- const target = event.target;
- const video_index = target.getAttribute('data-index');
- const card = document.querySelector(`.video-card [data-index="${video_index}"]`)
- card.classList.add("hide");
+ event.preventDefault();
+ const target = event.target;
+ const video_index = target.getAttribute("data-index");
+ const card = document.querySelector(
+ `.video-card [data-index="${video_index}"]`,
+ );
+ card.classList.add("hide");
- var url = '/playlist_ajax?action=remove_video&redirect=false' +
- '&set_video_id=' + target.getAttribute('data-index') +
- '&playlist_id=' + target.getAttribute('data-plid');
+ var url =
+ "/playlist_ajax?action=remove_video&redirect=false" +
+ "&set_video_id=" +
+ target.getAttribute("data-index") +
+ "&playlist_id=" +
+ target.getAttribute("data-plid");
- helpers.xhr('POST', url, {payload: payload}, {
- onNon200: function (xhr) {
- card.classList.remove("hide");
- }
- });
+ helpers.xhr(
+ "POST",
+ url,
+ { payload: payload },
+ {
+ onNon200: function (xhr) {
+ card.classList.remove("hide");
+ },
+ },
+ );
}
diff --git a/assets/js/post.js b/assets/js/post.js
index fcbc9155..ba09acbb 100644
--- a/assets/js/post.js
+++ b/assets/js/post.js
@@ -1,3 +1,3 @@
-addEventListener('load', function (e) {
- get_youtube_comments();
+addEventListener("load", function (e) {
+ get_youtube_comments();
});
diff --git a/assets/js/silvermine-videojs-quality-selector.min.js b/assets/js/silvermine-videojs-quality-selector.min.js
index 1877047d..b80bc733 100644
--- a/assets/js/silvermine-videojs-quality-selector.min.js
+++ b/assets/js/silvermine-videojs-quality-selector.min.js
@@ -1,4 +1,1549 @@
/*! @silvermine/videojs-quality-selector 2022-04-13 v1.1.2-43-gaa06e72-dirty */
-!function u(o,c,a){function l(e,n){if(!c[e]){if(!o[e]){var t="function"==typeof require&&require;if(!n&&t)return t(e,!0);if(s)return s(e,!0);var r=new Error("Cannot find module '"+e+"'");throw r.code="MODULE_NOT_FOUND",r}var i=c[e]={exports:{}};o[e][0].call(i.exports,function(n){return l(o[e][1][n]||n)},i,i.exports,u,o,c,a)}return c[e].exports}for(var s="function"==typeof require&&require,n=0;n":">",'"':""","'":"'","`":"`"},W=h.invert(P);h.escape=D(P),h.unescape=D(W),h.result=function(n,e,t){h.isArray(e)||(e=[e]);var r=e.length;if(!r)return h.isFunction(t)?t.call(n):t;for(var i=0;i/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};function Y(n){return"\\"+K[n]}var z=/(.)^/,K={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},G=/\\|'|\r|\n|\u2028|\u2029/g;h.template=function(u,n,e){!n&&e&&(n=e),n=h.defaults({},n,h.templateSettings);var t,r=RegExp([(n.escape||z).source,(n.interpolate||z).source,(n.evaluate||z).source].join("|")+"|$","g"),o=0,c="__p+='";u.replace(r,function(n,e,t,r,i){return c+=u.slice(o,i).replace(G,Y),o=i+n.length,e?c+="'+\n((__t=("+e+"))==null?'':_.escape(__t))+\n'":t?c+="'+\n((__t=("+t+"))==null?'':__t)+\n'":r&&(c+="';\n"+r+"\n__p+='"),n}),c+="';\n",n.variable||(c="with(obj||{}){\n"+c+"}\n"),c="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+c+"return __p;\n";try{t=new Function(n.variable||"obj","_",c)}catch(n){throw n.source=c,n}function i(n){return t.call(this,n,h)}var a=n.variable||"obj";return i.source="function("+a+"){\n"+c+"}",i},h.chain=function(n){var e=h(n);return e._chain=!0,e};function H(n,e){return n._chain?h(e).chain():e}h.mixin=function(t){return h.each(h.functions(t),function(n){var e=h[n]=t[n];h.prototype[n]=function(){var n=[this._wrapped];return i.apply(n,arguments),H(this,e.apply(h,n))}}),h},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];h.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),"shift"!==e&&"splice"!==e||0!==n.length||delete n[0],H(this,n)}}),h.each(["concat","join","slice"],function(n){var e=r[n];h.prototype[n]=function(){return H(this,e.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},h.prototype.valueOf=h.prototype.toJSON=h.prototype.value,h.prototype.toString=function(){return String(this._wrapped)},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}()}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],3:[function(n,e,t){"use strict";var i=n("underscore"),u=n("../events");e.exports=function(n){var r=n.getComponent("MenuItem");return n.extend(r,{constructor:function(n,e){var t=e.source;if(!i.isObject(t))throw new Error('was not provided a "source" object, but rather: '+typeof t);e=i.extend({selectable:!0,label:t.label},e),r.call(this,n,e),this.source=t},handleClick:function(n){r.prototype.handleClick.call(this,n),this.player().trigger(u.QUALITY_REQUESTED,this.source)}})}},{"../events":5,underscore:2}],4:[function(n,e,t){"use strict";var i=n("underscore"),u=n("../events"),o=n("./QualityOption"),c="vjs-quality-changing";e.exports=function(n){var e,r=n.getComponent("MenuButton"),t=o(n);return e=n.extend(r,{constructor:function(t,n){r.call(this,t,n),t.on(u.QUALITY_REQUESTED,function(n,e){this.setSelectedSource(e),t.addClass(c),t.one("loadeddata",function(){t.removeClass(c)})}.bind(this)),t.on(u.PLAYER_SOURCES_CHANGED,function(){this.update()}.bind(this)),t.on(u.QUALITY_SELECTED,function(n,e){this.setSelectedSource(e)}.bind(this)),t.one("ready",function(){this.selectedSrc=t.src(),this.update()}.bind(this)),this.controlText("Open quality selector menu")},setSelectedSource:function(n){var e=n?n.src:void 0;this.selectedSrc!==e&&(this.selectedSrc=e,i.each(this.items,function(n){n.selected(n.source.src===e)}))},createItems:function(){var e=this.player(),n=e.currentSources();return n=n.filter(function(n){return null==n.hidequalityoption}),i.map(n,function(n){return new t(e,{source:n,selected:n.src===this.selectedSrc})}.bind(this))},buildWrapperCSSClass:function(){return"vjs-quality-selector "+r.prototype.buildWrapperCSSClass.call(this)}}),n.registerComponent("QualitySelector",e),e}},{"../events":5,"./QualityOption":3,underscore:2}],5:[function(n,e,t){"use strict";e.exports={QUALITY_REQUESTED:"qualityRequested",QUALITY_SELECTED:"qualitySelected",PLAYER_SOURCES_CHANGED:"playerSourcesChanged"}},{}],6:[function(n,e,t){"use strict";var c=n("underscore"),r=n("./events"),i=n("./components/QualitySelector"),u=n("./middleware/SourceInterceptor"),a=n("./util/SafeSeek");e.exports=function(n){n=n||window.videojs,i(n),u(n),n.hook("setup",function(o){o.on(r.QUALITY_REQUESTED,function(n,e){var t=o.currentSources(),r=o.currentTime(),i=o.playbackRate(),u=o.paused();c.each(t,function(n){n.selected=!1}),c.findWhere(t,{src:e.src}).selected=!0,o._qualitySelectorSafeSeek&&o._qualitySelectorSafeSeek.onQualitySelectionChange(),o.src(t),o.ready(function(){o._qualitySelectorSafeSeek&&!o._qualitySelectorSafeSeek.hasFinished()||(o._qualitySelectorSafeSeek=new a(o,r),o.playbackRate(i)),u||o.play()})})})},e.exports.EVENTS=r},{"./components/QualitySelector":4,"./events":5,"./middleware/SourceInterceptor":7,"./util/SafeSeek":9,underscore:2}],7:[function(n,e,t){"use strict";var u=n("underscore"),o=n("../events");e.exports=function(n){n.use("*",function(i){return{setSource:function(n,e){var t,r=i.currentSources();i._qualitySelectorSafeSeek&&i._qualitySelectorSafeSeek.onPlayerSourcesChange(),u.isEqual(r,i._qualitySelectorPreviousSources)||(i.trigger(o.PLAYER_SOURCES_CHANGED,r),i._qualitySelectorPreviousSources=r),t=u.find(r,function(n){return!0===n.selected||"true"===n.selected||"selected"===n.selected})||n,i.trigger(o.QUALITY_SELECTED,t),e(null,t)}}})}},{"../events":5,underscore:2}],8:[function(n,e,t){"use strict";n("./index")()},{"./index":6}],9:[function(n,e,t){"use strict";var r=n("class.extend");e.exports=r.extend({init:function(n,e){this._player=n,this._seekToTime=e,this._hasFinished=!1,this._keepThisInstanceWhenPlayerSourcesChange=!1,this._seekWhenSafe()},_seekWhenSafe:function(){this._player.readyState()<3?(this._seekFn=this._seek.bind(this),this._player.one("canplay",this._seekFn)):this._seek()},onPlayerSourcesChange:function(){this._keepThisInstanceWhenPlayerSourcesChange?this._keepThisInstanceWhenPlayerSourcesChange=!1:this.cancel()},onQualitySelectionChange:function(){this.hasFinished()||(this._keepThisInstanceWhenPlayerSourcesChange=!0)},_seek:function(){this._player.currentTime(this._seekToTime),this._keepThisInstanceWhenPlayerSourcesChange=!1,this._hasFinished=!0},hasFinished:function(){return this._hasFinished},cancel:function(){this._player.off("canplay",this._seekFn),this._keepThisInstanceWhenPlayerSourcesChange=!1,this._hasFinished=!0}})},{"class.extend":1}]},{},[8]);
-//# sourceMappingURL=silvermine-videojs-quality-selector.min.js.map
\ No newline at end of file
+!(function u(o, c, a) {
+ function l(e, n) {
+ if (!c[e]) {
+ if (!o[e]) {
+ var t = "function" == typeof require && require;
+ if (!n && t) return t(e, !0);
+ if (s) return s(e, !0);
+ var r = new Error("Cannot find module '" + e + "'");
+ throw ((r.code = "MODULE_NOT_FOUND"), r);
+ }
+ var i = (c[e] = { exports: {} });
+ o[e][0].call(
+ i.exports,
+ function (n) {
+ return l(o[e][1][n] || n);
+ },
+ i,
+ i.exports,
+ u,
+ o,
+ c,
+ a,
+ );
+ }
+ return c[e].exports;
+ }
+ for (
+ var s = "function" == typeof require && require, n = 0;
+ n < a.length;
+ n++
+ )
+ l(a[n]);
+ return l;
+})(
+ {
+ 1: [
+ function (n, e, t) {
+ !(function () {
+ var u = !1,
+ o = /xyz/.test(function () {
+ xyz;
+ })
+ ? /\b_super\b/
+ : /.*/;
+ (this.Class = function () {}),
+ (Class.extend = function (n) {
+ var i = this.prototype;
+ u = !0;
+ var e = new this();
+ for (var t in ((u = !1), n))
+ e[t] =
+ "function" == typeof n[t] &&
+ "function" == typeof i[t] &&
+ o.test(n[t])
+ ? (function (t, r) {
+ return function () {
+ var n = this._super;
+ this._super = i[t];
+ var e = r.apply(this, arguments);
+ return (this._super = n), e;
+ };
+ })(t, n[t])
+ : n[t];
+ function r() {
+ !u && this.init && this.init.apply(this, arguments);
+ }
+ return (
+ (((r.prototype = e).constructor = r).extend = arguments.callee),
+ r
+ );
+ }),
+ (e.exports = Class);
+ })();
+ },
+ {},
+ ],
+ 2: [
+ function (n, J, $) {
+ (function (V) {
+ !(function () {
+ function t() {}
+ var n =
+ ("object" == typeof self && self.self === self && self) ||
+ ("object" == typeof V && V.global === V && V) ||
+ this ||
+ {},
+ e = n._,
+ r = Array.prototype,
+ o = Object.prototype,
+ f = "undefined" != typeof Symbol ? Symbol.prototype : null,
+ i = r.push,
+ a = r.slice,
+ p = o.toString,
+ u = o.hasOwnProperty,
+ c = Array.isArray,
+ l = Object.keys,
+ s = Object.create,
+ h = function (n) {
+ return n instanceof h
+ ? n
+ : this instanceof h
+ ? void (this._wrapped = n)
+ : new h(n);
+ };
+ void 0 === $ || $.nodeType
+ ? (n._ = h)
+ : (void 0 !== J &&
+ !J.nodeType &&
+ J.exports &&
+ ($ = J.exports = h),
+ ($._ = h)),
+ (h.VERSION = "1.9.1");
+ function d(i, u, n) {
+ if (void 0 === u) return i;
+ switch (null == n ? 3 : n) {
+ case 1:
+ return function (n) {
+ return i.call(u, n);
+ };
+ case 3:
+ return function (n, e, t) {
+ return i.call(u, n, e, t);
+ };
+ case 4:
+ return function (n, e, t, r) {
+ return i.call(u, n, e, t, r);
+ };
+ }
+ return function () {
+ return i.apply(u, arguments);
+ };
+ }
+ function y(n, e, t) {
+ return h.iteratee !== v
+ ? h.iteratee(n, e)
+ : null == n
+ ? h.identity
+ : h.isFunction(n)
+ ? d(n, e, t)
+ : h.isObject(n) && !h.isArray(n)
+ ? h.matcher(n)
+ : h.property(n);
+ }
+ var v;
+ h.iteratee = v = function (n, e) {
+ return y(n, e, 1 / 0);
+ };
+ function g(i, u) {
+ return (
+ (u = null == u ? i.length - 1 : +u),
+ function () {
+ for (
+ var n = Math.max(arguments.length - u, 0),
+ e = Array(n),
+ t = 0;
+ t < n;
+ t++
+ )
+ e[t] = arguments[t + u];
+ switch (u) {
+ case 0:
+ return i.call(this, e);
+ case 1:
+ return i.call(this, arguments[0], e);
+ case 2:
+ return i.call(this, arguments[0], arguments[1], e);
+ }
+ var r = Array(u + 1);
+ for (t = 0; t < u; t++) r[t] = arguments[t];
+ return (r[u] = e), i.apply(this, r);
+ }
+ );
+ }
+ function S(n) {
+ if (!h.isObject(n)) return {};
+ if (s) return s(n);
+ t.prototype = n;
+ var e = new t();
+ return (t.prototype = null), e;
+ }
+ function m(e) {
+ return function (n) {
+ return null == n ? void 0 : n[e];
+ };
+ }
+ function _(n, e) {
+ return null != n && u.call(n, e);
+ }
+ function b(n, e) {
+ for (var t = e.length, r = 0; r < t; r++) {
+ if (null == n) return;
+ n = n[e[r]];
+ }
+ return t ? n : void 0;
+ }
+ function x(n) {
+ var e = j(n);
+ return "number" == typeof e && 0 <= e && e <= k;
+ }
+ var k = Math.pow(2, 53) - 1,
+ j = m("length");
+ (h.each = h.forEach =
+ function (n, e, t) {
+ var r, i;
+ if (((e = d(e, t)), x(n)))
+ for (r = 0, i = n.length; r < i; r++) e(n[r], r, n);
+ else {
+ var u = h.keys(n);
+ for (r = 0, i = u.length; r < i; r++) e(n[u[r]], u[r], n);
+ }
+ return n;
+ }),
+ (h.map = h.collect =
+ function (n, e, t) {
+ e = y(e, t);
+ for (
+ var r = !x(n) && h.keys(n),
+ i = (r || n).length,
+ u = Array(i),
+ o = 0;
+ o < i;
+ o++
+ ) {
+ var c = r ? r[o] : o;
+ u[o] = e(n[c], c, n);
+ }
+ return u;
+ });
+ function E(a) {
+ return function (n, e, t, r) {
+ var i = 3 <= arguments.length;
+ return (function (n, e, t, r) {
+ var i = !x(n) && h.keys(n),
+ u = (i || n).length,
+ o = 0 < a ? 0 : u - 1;
+ for (
+ r || ((t = n[i ? i[o] : o]), (o += a));
+ 0 <= o && o < u;
+ o += a
+ ) {
+ var c = i ? i[o] : o;
+ t = e(t, n[c], c, n);
+ }
+ return t;
+ })(n, d(e, r, 4), t, i);
+ };
+ }
+ (h.reduce = h.foldl = h.inject = E(1)),
+ (h.reduceRight = h.foldr = E(-1)),
+ (h.find = h.detect =
+ function (n, e, t) {
+ var r = (x(n) ? h.findIndex : h.findKey)(n, e, t);
+ if (void 0 !== r && -1 !== r) return n[r];
+ }),
+ (h.filter = h.select =
+ function (n, r, e) {
+ var i = [];
+ return (
+ (r = y(r, e)),
+ h.each(n, function (n, e, t) {
+ r(n, e, t) && i.push(n);
+ }),
+ i
+ );
+ }),
+ (h.reject = function (n, e, t) {
+ return h.filter(n, h.negate(y(e)), t);
+ }),
+ (h.every = h.all =
+ function (n, e, t) {
+ e = y(e, t);
+ for (
+ var r = !x(n) && h.keys(n), i = (r || n).length, u = 0;
+ u < i;
+ u++
+ ) {
+ var o = r ? r[u] : u;
+ if (!e(n[o], o, n)) return !1;
+ }
+ return !0;
+ }),
+ (h.some = h.any =
+ function (n, e, t) {
+ e = y(e, t);
+ for (
+ var r = !x(n) && h.keys(n), i = (r || n).length, u = 0;
+ u < i;
+ u++
+ ) {
+ var o = r ? r[u] : u;
+ if (e(n[o], o, n)) return !0;
+ }
+ return !1;
+ }),
+ (h.contains =
+ h.includes =
+ h.include =
+ function (n, e, t, r) {
+ return (
+ x(n) || (n = h.values(n)),
+ ("number" == typeof t && !r) || (t = 0),
+ 0 <= h.indexOf(n, e, t)
+ );
+ }),
+ (h.invoke = g(function (n, t, r) {
+ var i, u;
+ return (
+ h.isFunction(t)
+ ? (u = t)
+ : h.isArray(t) &&
+ ((i = t.slice(0, -1)), (t = t[t.length - 1])),
+ h.map(n, function (n) {
+ var e = u;
+ if (!e) {
+ if ((i && i.length && (n = b(n, i)), null == n)) return;
+ e = n[t];
+ }
+ return null == e ? e : e.apply(n, r);
+ })
+ );
+ })),
+ (h.pluck = function (n, e) {
+ return h.map(n, h.property(e));
+ }),
+ (h.where = function (n, e) {
+ return h.filter(n, h.matcher(e));
+ }),
+ (h.findWhere = function (n, e) {
+ return h.find(n, h.matcher(e));
+ }),
+ (h.max = function (n, r, e) {
+ var t,
+ i,
+ u = -1 / 0,
+ o = -1 / 0;
+ if (
+ null == r ||
+ ("number" == typeof r && "object" != typeof n[0] && null != n)
+ )
+ for (
+ var c = 0, a = (n = x(n) ? n : h.values(n)).length;
+ c < a;
+ c++
+ )
+ null != (t = n[c]) && u < t && (u = t);
+ else
+ (r = y(r, e)),
+ h.each(n, function (n, e, t) {
+ (i = r(n, e, t)),
+ (o < i || (i === -1 / 0 && u === -1 / 0)) &&
+ ((u = n), (o = i));
+ });
+ return u;
+ }),
+ (h.min = function (n, r, e) {
+ var t,
+ i,
+ u = 1 / 0,
+ o = 1 / 0;
+ if (
+ null == r ||
+ ("number" == typeof r && "object" != typeof n[0] && null != n)
+ )
+ for (
+ var c = 0, a = (n = x(n) ? n : h.values(n)).length;
+ c < a;
+ c++
+ )
+ null != (t = n[c]) && t < u && (u = t);
+ else
+ (r = y(r, e)),
+ h.each(n, function (n, e, t) {
+ ((i = r(n, e, t)) < o || (i === 1 / 0 && u === 1 / 0)) &&
+ ((u = n), (o = i));
+ });
+ return u;
+ }),
+ (h.shuffle = function (n) {
+ return h.sample(n, 1 / 0);
+ }),
+ (h.sample = function (n, e, t) {
+ if (null == e || t)
+ return x(n) || (n = h.values(n)), n[h.random(n.length - 1)];
+ var r = x(n) ? h.clone(n) : h.values(n),
+ i = j(r);
+ e = Math.max(Math.min(e, i), 0);
+ for (var u = i - 1, o = 0; o < e; o++) {
+ var c = h.random(o, u),
+ a = r[o];
+ (r[o] = r[c]), (r[c] = a);
+ }
+ return r.slice(0, e);
+ }),
+ (h.sortBy = function (n, r, e) {
+ var i = 0;
+ return (
+ (r = y(r, e)),
+ h.pluck(
+ h
+ .map(n, function (n, e, t) {
+ return { value: n, index: i++, criteria: r(n, e, t) };
+ })
+ .sort(function (n, e) {
+ var t = n.criteria,
+ r = e.criteria;
+ if (t !== r) {
+ if (r < t || void 0 === t) return 1;
+ if (t < r || void 0 === r) return -1;
+ }
+ return n.index - e.index;
+ }),
+ "value",
+ )
+ );
+ });
+ function w(o, e) {
+ return function (r, i, n) {
+ var u = e ? [[], []] : {};
+ return (
+ (i = y(i, n)),
+ h.each(r, function (n, e) {
+ var t = i(n, e, r);
+ o(u, n, t);
+ }),
+ u
+ );
+ };
+ }
+ (h.groupBy = w(function (n, e, t) {
+ _(n, t) ? n[t].push(e) : (n[t] = [e]);
+ })),
+ (h.indexBy = w(function (n, e, t) {
+ n[t] = e;
+ })),
+ (h.countBy = w(function (n, e, t) {
+ _(n, t) ? n[t]++ : (n[t] = 1);
+ }));
+ var A =
+ /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
+ (h.toArray = function (n) {
+ return n
+ ? h.isArray(n)
+ ? a.call(n)
+ : h.isString(n)
+ ? n.match(A)
+ : x(n)
+ ? h.map(n, h.identity)
+ : h.values(n)
+ : [];
+ }),
+ (h.size = function (n) {
+ return null == n ? 0 : x(n) ? n.length : h.keys(n).length;
+ }),
+ (h.partition = w(function (n, e, t) {
+ n[t ? 0 : 1].push(e);
+ }, !0)),
+ (h.first =
+ h.head =
+ h.take =
+ function (n, e, t) {
+ return null == n || n.length < 1
+ ? null == e
+ ? void 0
+ : []
+ : null == e || t
+ ? n[0]
+ : h.initial(n, n.length - e);
+ }),
+ (h.initial = function (n, e, t) {
+ return a.call(
+ n,
+ 0,
+ Math.max(0, n.length - (null == e || t ? 1 : e)),
+ );
+ }),
+ (h.last = function (n, e, t) {
+ return null == n || n.length < 1
+ ? null == e
+ ? void 0
+ : []
+ : null == e || t
+ ? n[n.length - 1]
+ : h.rest(n, Math.max(0, n.length - e));
+ }),
+ (h.rest =
+ h.tail =
+ h.drop =
+ function (n, e, t) {
+ return a.call(n, null == e || t ? 1 : e);
+ }),
+ (h.compact = function (n) {
+ return h.filter(n, Boolean);
+ });
+ var T = function (n, e, t, r) {
+ for (var i = (r = r || []).length, u = 0, o = j(n); u < o; u++) {
+ var c = n[u];
+ if (x(c) && (h.isArray(c) || h.isArguments(c)))
+ if (e) for (var a = 0, l = c.length; a < l; ) r[i++] = c[a++];
+ else T(c, e, t, r), (i = r.length);
+ else t || (r[i++] = c);
+ }
+ return r;
+ };
+ (h.flatten = function (n, e) {
+ return T(n, e, !1);
+ }),
+ (h.without = g(function (n, e) {
+ return h.difference(n, e);
+ })),
+ (h.uniq = h.unique =
+ function (n, e, t, r) {
+ h.isBoolean(e) || ((r = t), (t = e), (e = !1)),
+ null != t && (t = y(t, r));
+ for (var i = [], u = [], o = 0, c = j(n); o < c; o++) {
+ var a = n[o],
+ l = t ? t(a, o, n) : a;
+ e && !t
+ ? ((o && u === l) || i.push(a), (u = l))
+ : t
+ ? h.contains(u, l) || (u.push(l), i.push(a))
+ : h.contains(i, a) || i.push(a);
+ }
+ return i;
+ }),
+ (h.union = g(function (n) {
+ return h.uniq(T(n, !0, !0));
+ })),
+ (h.intersection = function (n) {
+ for (
+ var e = [], t = arguments.length, r = 0, i = j(n);
+ r < i;
+ r++
+ ) {
+ var u = n[r];
+ if (!h.contains(e, u)) {
+ var o;
+ for (o = 1; o < t && h.contains(arguments[o], u); o++);
+ o === t && e.push(u);
+ }
+ }
+ return e;
+ }),
+ (h.difference = g(function (n, e) {
+ return (
+ (e = T(e, !0, !0)),
+ h.filter(n, function (n) {
+ return !h.contains(e, n);
+ })
+ );
+ })),
+ (h.unzip = function (n) {
+ for (
+ var e = (n && h.max(n, j).length) || 0, t = Array(e), r = 0;
+ r < e;
+ r++
+ )
+ t[r] = h.pluck(n, r);
+ return t;
+ }),
+ (h.zip = g(h.unzip)),
+ (h.object = function (n, e) {
+ for (var t = {}, r = 0, i = j(n); r < i; r++)
+ e ? (t[n[r]] = e[r]) : (t[n[r][0]] = n[r][1]);
+ return t;
+ });
+ function O(u) {
+ return function (n, e, t) {
+ e = y(e, t);
+ for (
+ var r = j(n), i = 0 < u ? 0 : r - 1;
+ 0 <= i && i < r;
+ i += u
+ )
+ if (e(n[i], i, n)) return i;
+ return -1;
+ };
+ }
+ (h.findIndex = O(1)),
+ (h.findLastIndex = O(-1)),
+ (h.sortedIndex = function (n, e, t, r) {
+ for (var i = (t = y(t, r, 1))(e), u = 0, o = j(n); u < o; ) {
+ var c = Math.floor((u + o) / 2);
+ t(n[c]) < i ? (u = c + 1) : (o = c);
+ }
+ return u;
+ });
+ function C(u, o, c) {
+ return function (n, e, t) {
+ var r = 0,
+ i = j(n);
+ if ("number" == typeof t)
+ 0 < u
+ ? (r = 0 <= t ? t : Math.max(t + i, r))
+ : (i = 0 <= t ? Math.min(t + 1, i) : t + i + 1);
+ else if (c && t && i) return n[(t = c(n, e))] === e ? t : -1;
+ if (e != e)
+ return 0 <= (t = o(a.call(n, r, i), h.isNaN)) ? t + r : -1;
+ for (t = 0 < u ? r : i - 1; 0 <= t && t < i; t += u)
+ if (n[t] === e) return t;
+ return -1;
+ };
+ }
+ (h.indexOf = C(1, h.findIndex, h.sortedIndex)),
+ (h.lastIndexOf = C(-1, h.findLastIndex)),
+ (h.range = function (n, e, t) {
+ null == e && ((e = n || 0), (n = 0)),
+ (t = t || (e < n ? -1 : 1));
+ for (
+ var r = Math.max(Math.ceil((e - n) / t), 0),
+ i = Array(r),
+ u = 0;
+ u < r;
+ u++, n += t
+ )
+ i[u] = n;
+ return i;
+ }),
+ (h.chunk = function (n, e) {
+ if (null == e || e < 1) return [];
+ for (var t = [], r = 0, i = n.length; r < i; )
+ t.push(a.call(n, r, (r += e)));
+ return t;
+ });
+ function I(n, e, t, r, i) {
+ if (!(r instanceof e)) return n.apply(t, i);
+ var u = S(n.prototype),
+ o = n.apply(u, i);
+ return h.isObject(o) ? o : u;
+ }
+ (h.bind = g(function (e, t, r) {
+ if (!h.isFunction(e))
+ throw new TypeError("Bind must be called on a function");
+ var i = g(function (n) {
+ return I(e, i, t, this, r.concat(n));
+ });
+ return i;
+ })),
+ (h.partial = g(function (i, u) {
+ var o = h.partial.placeholder,
+ c = function () {
+ for (
+ var n = 0, e = u.length, t = Array(e), r = 0;
+ r < e;
+ r++
+ )
+ t[r] = u[r] === o ? arguments[n++] : u[r];
+ for (; n < arguments.length; ) t.push(arguments[n++]);
+ return I(i, c, this, this, t);
+ };
+ return c;
+ })),
+ ((h.partial.placeholder = h).bindAll = g(function (n, e) {
+ var t = (e = T(e, !1, !1)).length;
+ if (t < 1)
+ throw new Error("bindAll must be passed function names");
+ for (; t--; ) {
+ var r = e[t];
+ n[r] = h.bind(n[r], n);
+ }
+ })),
+ (h.memoize = function (r, i) {
+ var u = function (n) {
+ var e = u.cache,
+ t = "" + (i ? i.apply(this, arguments) : n);
+ return _(e, t) || (e[t] = r.apply(this, arguments)), e[t];
+ };
+ return (u.cache = {}), u;
+ }),
+ (h.delay = g(function (n, e, t) {
+ return setTimeout(function () {
+ return n.apply(null, t);
+ }, e);
+ })),
+ (h.defer = h.partial(h.delay, h, 1)),
+ (h.throttle = function (t, r, i) {
+ var u,
+ o,
+ c,
+ a,
+ l = 0;
+ i = i || {};
+ function s() {
+ (l = !1 === i.leading ? 0 : h.now()),
+ (u = null),
+ (a = t.apply(o, c)),
+ u || (o = c = null);
+ }
+ function n() {
+ var n = h.now();
+ l || !1 !== i.leading || (l = n);
+ var e = r - (n - l);
+ return (
+ (o = this),
+ (c = arguments),
+ e <= 0 || r < e
+ ? (u && (clearTimeout(u), (u = null)),
+ (l = n),
+ (a = t.apply(o, c)),
+ u || (o = c = null))
+ : u || !1 === i.trailing || (u = setTimeout(s, e)),
+ a
+ );
+ }
+ return (
+ (n.cancel = function () {
+ clearTimeout(u), (l = 0), (u = o = c = null);
+ }),
+ n
+ );
+ }),
+ (h.debounce = function (t, r, i) {
+ function u(n, e) {
+ (o = null), e && (c = t.apply(n, e));
+ }
+ var o,
+ c,
+ n = g(function (n) {
+ if ((o && clearTimeout(o), i)) {
+ var e = !o;
+ (o = setTimeout(u, r)), e && (c = t.apply(this, n));
+ } else o = h.delay(u, r, this, n);
+ return c;
+ });
+ return (
+ (n.cancel = function () {
+ clearTimeout(o), (o = null);
+ }),
+ n
+ );
+ }),
+ (h.wrap = function (n, e) {
+ return h.partial(e, n);
+ }),
+ (h.negate = function (n) {
+ return function () {
+ return !n.apply(this, arguments);
+ };
+ }),
+ (h.compose = function () {
+ var t = arguments,
+ r = t.length - 1;
+ return function () {
+ for (var n = r, e = t[r].apply(this, arguments); n--; )
+ e = t[n].call(this, e);
+ return e;
+ };
+ }),
+ (h.after = function (n, e) {
+ return function () {
+ if (--n < 1) return e.apply(this, arguments);
+ };
+ }),
+ (h.before = function (n, e) {
+ var t;
+ return function () {
+ return (
+ 0 < --n && (t = e.apply(this, arguments)),
+ n <= 1 && (e = null),
+ t
+ );
+ };
+ }),
+ (h.once = h.partial(h.before, 2)),
+ (h.restArguments = g);
+ function F(n, e) {
+ var t = M.length,
+ r = n.constructor,
+ i = (h.isFunction(r) && r.prototype) || o,
+ u = "constructor";
+ for (_(n, u) && !h.contains(e, u) && e.push(u); t--; )
+ (u = M[t]) in n &&
+ n[u] !== i[u] &&
+ !h.contains(e, u) &&
+ e.push(u);
+ }
+ var q = !{ toString: null }.propertyIsEnumerable("toString"),
+ M = [
+ "valueOf",
+ "isPrototypeOf",
+ "toString",
+ "propertyIsEnumerable",
+ "hasOwnProperty",
+ "toLocaleString",
+ ];
+ (h.keys = function (n) {
+ if (!h.isObject(n)) return [];
+ if (l) return l(n);
+ var e = [];
+ for (var t in n) _(n, t) && e.push(t);
+ return q && F(n, e), e;
+ }),
+ (h.allKeys = function (n) {
+ if (!h.isObject(n)) return [];
+ var e = [];
+ for (var t in n) e.push(t);
+ return q && F(n, e), e;
+ }),
+ (h.values = function (n) {
+ for (
+ var e = h.keys(n), t = e.length, r = Array(t), i = 0;
+ i < t;
+ i++
+ )
+ r[i] = n[e[i]];
+ return r;
+ }),
+ (h.mapObject = function (n, e, t) {
+ e = y(e, t);
+ for (
+ var r = h.keys(n), i = r.length, u = {}, o = 0;
+ o < i;
+ o++
+ ) {
+ var c = r[o];
+ u[c] = e(n[c], c, n);
+ }
+ return u;
+ }),
+ (h.pairs = function (n) {
+ for (
+ var e = h.keys(n), t = e.length, r = Array(t), i = 0;
+ i < t;
+ i++
+ )
+ r[i] = [e[i], n[e[i]]];
+ return r;
+ }),
+ (h.invert = function (n) {
+ for (var e = {}, t = h.keys(n), r = 0, i = t.length; r < i; r++)
+ e[n[t[r]]] = t[r];
+ return e;
+ }),
+ (h.functions = h.methods =
+ function (n) {
+ var e = [];
+ for (var t in n) h.isFunction(n[t]) && e.push(t);
+ return e.sort();
+ });
+ function N(a, l) {
+ return function (n) {
+ var e = arguments.length;
+ if ((l && (n = Object(n)), e < 2 || null == n)) return n;
+ for (var t = 1; t < e; t++)
+ for (
+ var r = arguments[t], i = a(r), u = i.length, o = 0;
+ o < u;
+ o++
+ ) {
+ var c = i[o];
+ (l && void 0 !== n[c]) || (n[c] = r[c]);
+ }
+ return n;
+ };
+ }
+ (h.extend = N(h.allKeys)),
+ (h.extendOwn = h.assign = N(h.keys)),
+ (h.findKey = function (n, e, t) {
+ e = y(e, t);
+ for (var r, i = h.keys(n), u = 0, o = i.length; u < o; u++)
+ if (e(n[(r = i[u])], r, n)) return r;
+ });
+ function R(n, e, t) {
+ return e in t;
+ }
+ var Q, L;
+ (h.pick = g(function (n, e) {
+ var t = {},
+ r = e[0];
+ if (null == n) return t;
+ h.isFunction(r)
+ ? (1 < e.length && (r = d(r, e[1])), (e = h.allKeys(n)))
+ : ((r = R), (e = T(e, !1, !1)), (n = Object(n)));
+ for (var i = 0, u = e.length; i < u; i++) {
+ var o = e[i],
+ c = n[o];
+ r(c, o, n) && (t[o] = c);
+ }
+ return t;
+ })),
+ (h.omit = g(function (n, t) {
+ var e,
+ r = t[0];
+ return (
+ h.isFunction(r)
+ ? ((r = h.negate(r)), 1 < t.length && (e = t[1]))
+ : ((t = h.map(T(t, !1, !1), String)),
+ (r = function (n, e) {
+ return !h.contains(t, e);
+ })),
+ h.pick(n, r, e)
+ );
+ })),
+ (h.defaults = N(h.allKeys, !0)),
+ (h.create = function (n, e) {
+ var t = S(n);
+ return e && h.extendOwn(t, e), t;
+ }),
+ (h.clone = function (n) {
+ return h.isObject(n)
+ ? h.isArray(n)
+ ? n.slice()
+ : h.extend({}, n)
+ : n;
+ }),
+ (h.tap = function (n, e) {
+ return e(n), n;
+ }),
+ (h.isMatch = function (n, e) {
+ var t = h.keys(e),
+ r = t.length;
+ if (null == n) return !r;
+ for (var i = Object(n), u = 0; u < r; u++) {
+ var o = t[u];
+ if (e[o] !== i[o] || !(o in i)) return !1;
+ }
+ return !0;
+ }),
+ (Q = function (n, e, t, r) {
+ if (n === e) return 0 !== n || 1 / n == 1 / e;
+ if (null == n || null == e) return !1;
+ if (n != n) return e != e;
+ var i = typeof n;
+ return (
+ ("function" == i || "object" == i || "object" == typeof e) &&
+ L(n, e, t, r)
+ );
+ }),
+ (L = function (n, e, t, r) {
+ n instanceof h && (n = n._wrapped),
+ e instanceof h && (e = e._wrapped);
+ var i = p.call(n);
+ if (i !== p.call(e)) return !1;
+ switch (i) {
+ case "[object RegExp]":
+ case "[object String]":
+ return "" + n == "" + e;
+ case "[object Number]":
+ return +n != +n
+ ? +e != +e
+ : 0 == +n
+ ? 1 / +n == 1 / e
+ : +n == +e;
+ case "[object Date]":
+ case "[object Boolean]":
+ return +n == +e;
+ case "[object Symbol]":
+ return f.valueOf.call(n) === f.valueOf.call(e);
+ }
+ var u = "[object Array]" === i;
+ if (!u) {
+ if ("object" != typeof n || "object" != typeof e) return !1;
+ var o = n.constructor,
+ c = e.constructor;
+ if (
+ o !== c &&
+ !(
+ h.isFunction(o) &&
+ o instanceof o &&
+ h.isFunction(c) &&
+ c instanceof c
+ ) &&
+ "constructor" in n &&
+ "constructor" in e
+ )
+ return !1;
+ }
+ r = r || [];
+ for (var a = (t = t || []).length; a--; )
+ if (t[a] === n) return r[a] === e;
+ if ((t.push(n), r.push(e), u)) {
+ if ((a = n.length) !== e.length) return !1;
+ for (; a--; ) if (!Q(n[a], e[a], t, r)) return !1;
+ } else {
+ var l,
+ s = h.keys(n);
+ if (((a = s.length), h.keys(e).length !== a)) return !1;
+ for (; a--; )
+ if (((l = s[a]), !_(e, l) || !Q(n[l], e[l], t, r)))
+ return !1;
+ }
+ return t.pop(), r.pop(), !0;
+ }),
+ (h.isEqual = function (n, e) {
+ return Q(n, e);
+ }),
+ (h.isEmpty = function (n) {
+ return (
+ null == n ||
+ (x(n) && (h.isArray(n) || h.isString(n) || h.isArguments(n))
+ ? 0 === n.length
+ : 0 === h.keys(n).length)
+ );
+ }),
+ (h.isElement = function (n) {
+ return !(!n || 1 !== n.nodeType);
+ }),
+ (h.isArray =
+ c ||
+ function (n) {
+ return "[object Array]" === p.call(n);
+ }),
+ (h.isObject = function (n) {
+ var e = typeof n;
+ return "function" == e || ("object" == e && !!n);
+ }),
+ h.each(
+ [
+ "Arguments",
+ "Function",
+ "String",
+ "Number",
+ "Date",
+ "RegExp",
+ "Error",
+ "Symbol",
+ "Map",
+ "WeakMap",
+ "Set",
+ "WeakSet",
+ ],
+ function (e) {
+ h["is" + e] = function (n) {
+ return p.call(n) === "[object " + e + "]";
+ };
+ },
+ ),
+ h.isArguments(arguments) ||
+ (h.isArguments = function (n) {
+ return _(n, "callee");
+ });
+ var U = n.document && n.document.childNodes;
+ "function" != typeof /./ &&
+ "object" != typeof Int8Array &&
+ "function" != typeof U &&
+ (h.isFunction = function (n) {
+ return "function" == typeof n || !1;
+ }),
+ (h.isFinite = function (n) {
+ return !h.isSymbol(n) && isFinite(n) && !isNaN(parseFloat(n));
+ }),
+ (h.isNaN = function (n) {
+ return h.isNumber(n) && isNaN(n);
+ }),
+ (h.isBoolean = function (n) {
+ return !0 === n || !1 === n || "[object Boolean]" === p.call(n);
+ }),
+ (h.isNull = function (n) {
+ return null === n;
+ }),
+ (h.isUndefined = function (n) {
+ return void 0 === n;
+ }),
+ (h.has = function (n, e) {
+ if (!h.isArray(e)) return _(n, e);
+ for (var t = e.length, r = 0; r < t; r++) {
+ var i = e[r];
+ if (null == n || !u.call(n, i)) return !1;
+ n = n[i];
+ }
+ return !!t;
+ }),
+ (h.noConflict = function () {
+ return (n._ = e), this;
+ }),
+ (h.identity = function (n) {
+ return n;
+ }),
+ (h.constant = function (n) {
+ return function () {
+ return n;
+ };
+ }),
+ (h.noop = function () {}),
+ (h.property = function (e) {
+ return h.isArray(e)
+ ? function (n) {
+ return b(n, e);
+ }
+ : m(e);
+ }),
+ (h.propertyOf = function (e) {
+ return null == e
+ ? function () {}
+ : function (n) {
+ return h.isArray(n) ? b(e, n) : e[n];
+ };
+ }),
+ (h.matcher = h.matches =
+ function (e) {
+ return (
+ (e = h.extendOwn({}, e)),
+ function (n) {
+ return h.isMatch(n, e);
+ }
+ );
+ }),
+ (h.times = function (n, e, t) {
+ var r = Array(Math.max(0, n));
+ e = d(e, t, 1);
+ for (var i = 0; i < n; i++) r[i] = e(i);
+ return r;
+ }),
+ (h.random = function (n, e) {
+ return (
+ null == e && ((e = n), (n = 0)),
+ n + Math.floor(Math.random() * (e - n + 1))
+ );
+ }),
+ (h.now =
+ Date.now ||
+ function () {
+ return new Date().getTime();
+ });
+ function D(e) {
+ function t(n) {
+ return e[n];
+ }
+ var n = "(?:" + h.keys(e).join("|") + ")",
+ r = RegExp(n),
+ i = RegExp(n, "g");
+ return function (n) {
+ return (
+ (n = null == n ? "" : "" + n), r.test(n) ? n.replace(i, t) : n
+ );
+ };
+ }
+ var P = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ "`": "`",
+ },
+ W = h.invert(P);
+ (h.escape = D(P)),
+ (h.unescape = D(W)),
+ (h.result = function (n, e, t) {
+ h.isArray(e) || (e = [e]);
+ var r = e.length;
+ if (!r) return h.isFunction(t) ? t.call(n) : t;
+ for (var i = 0; i < r; i++) {
+ var u = null == n ? void 0 : n[e[i]];
+ void 0 === u && ((u = t), (i = r)),
+ (n = h.isFunction(u) ? u.call(n) : u);
+ }
+ return n;
+ });
+ var B = 0;
+ (h.uniqueId = function (n) {
+ var e = ++B + "";
+ return n ? n + e : e;
+ }),
+ (h.templateSettings = {
+ evaluate: /<%([\s\S]+?)%>/g,
+ interpolate: /<%=([\s\S]+?)%>/g,
+ escape: /<%-([\s\S]+?)%>/g,
+ });
+ function Y(n) {
+ return "\\" + K[n];
+ }
+ var z = /(.)^/,
+ K = {
+ "'": "'",
+ "\\": "\\",
+ "\r": "r",
+ "\n": "n",
+ "\u2028": "u2028",
+ "\u2029": "u2029",
+ },
+ G = /\\|'|\r|\n|\u2028|\u2029/g;
+ (h.template = function (u, n, e) {
+ !n && e && (n = e), (n = h.defaults({}, n, h.templateSettings));
+ var t,
+ r = RegExp(
+ [
+ (n.escape || z).source,
+ (n.interpolate || z).source,
+ (n.evaluate || z).source,
+ ].join("|") + "|$",
+ "g",
+ ),
+ o = 0,
+ c = "__p+='";
+ u.replace(r, function (n, e, t, r, i) {
+ return (
+ (c += u.slice(o, i).replace(G, Y)),
+ (o = i + n.length),
+ e
+ ? (c +=
+ "'+\n((__t=(" + e + "))==null?'':_.escape(__t))+\n'")
+ : t
+ ? (c += "'+\n((__t=(" + t + "))==null?'':__t)+\n'")
+ : r && (c += "';\n" + r + "\n__p+='"),
+ n
+ );
+ }),
+ (c += "';\n"),
+ n.variable || (c = "with(obj||{}){\n" + c + "}\n"),
+ (c =
+ "var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n" +
+ c +
+ "return __p;\n");
+ try {
+ t = new Function(n.variable || "obj", "_", c);
+ } catch (n) {
+ throw ((n.source = c), n);
+ }
+ function i(n) {
+ return t.call(this, n, h);
+ }
+ var a = n.variable || "obj";
+ return (i.source = "function(" + a + "){\n" + c + "}"), i;
+ }),
+ (h.chain = function (n) {
+ var e = h(n);
+ return (e._chain = !0), e;
+ });
+ function H(n, e) {
+ return n._chain ? h(e).chain() : e;
+ }
+ (h.mixin = function (t) {
+ return (
+ h.each(h.functions(t), function (n) {
+ var e = (h[n] = t[n]);
+ h.prototype[n] = function () {
+ var n = [this._wrapped];
+ return i.apply(n, arguments), H(this, e.apply(h, n));
+ };
+ }),
+ h
+ );
+ }),
+ h.mixin(h),
+ h.each(
+ [
+ "pop",
+ "push",
+ "reverse",
+ "shift",
+ "sort",
+ "splice",
+ "unshift",
+ ],
+ function (e) {
+ var t = r[e];
+ h.prototype[e] = function () {
+ var n = this._wrapped;
+ return (
+ t.apply(n, arguments),
+ ("shift" !== e && "splice" !== e) ||
+ 0 !== n.length ||
+ delete n[0],
+ H(this, n)
+ );
+ };
+ },
+ ),
+ h.each(["concat", "join", "slice"], function (n) {
+ var e = r[n];
+ h.prototype[n] = function () {
+ return H(this, e.apply(this._wrapped, arguments));
+ };
+ }),
+ (h.prototype.value = function () {
+ return this._wrapped;
+ }),
+ (h.prototype.valueOf = h.prototype.toJSON = h.prototype.value),
+ (h.prototype.toString = function () {
+ return String(this._wrapped);
+ }),
+ "function" == typeof define &&
+ define.amd &&
+ define("underscore", [], function () {
+ return h;
+ });
+ })();
+ }).call(
+ this,
+ "undefined" != typeof global
+ ? global
+ : "undefined" != typeof self
+ ? self
+ : "undefined" != typeof window
+ ? window
+ : {},
+ );
+ },
+ {},
+ ],
+ 3: [
+ function (n, e, t) {
+ "use strict";
+ var i = n("underscore"),
+ u = n("../events");
+ e.exports = function (n) {
+ var r = n.getComponent("MenuItem");
+ return n.extend(r, {
+ constructor: function (n, e) {
+ var t = e.source;
+ if (!i.isObject(t))
+ throw new Error(
+ 'was not provided a "source" object, but rather: ' + typeof t,
+ );
+ (e = i.extend({ selectable: !0, label: t.label }, e)),
+ r.call(this, n, e),
+ (this.source = t);
+ },
+ handleClick: function (n) {
+ r.prototype.handleClick.call(this, n),
+ this.player().trigger(u.QUALITY_REQUESTED, this.source);
+ },
+ });
+ };
+ },
+ { "../events": 5, underscore: 2 },
+ ],
+ 4: [
+ function (n, e, t) {
+ "use strict";
+ var i = n("underscore"),
+ u = n("../events"),
+ o = n("./QualityOption"),
+ c = "vjs-quality-changing";
+ e.exports = function (n) {
+ var e,
+ r = n.getComponent("MenuButton"),
+ t = o(n);
+ return (
+ (e = n.extend(r, {
+ constructor: function (t, n) {
+ r.call(this, t, n),
+ t.on(
+ u.QUALITY_REQUESTED,
+ function (n, e) {
+ this.setSelectedSource(e),
+ t.addClass(c),
+ t.one("loadeddata", function () {
+ t.removeClass(c);
+ });
+ }.bind(this),
+ ),
+ t.on(
+ u.PLAYER_SOURCES_CHANGED,
+ function () {
+ this.update();
+ }.bind(this),
+ ),
+ t.on(
+ u.QUALITY_SELECTED,
+ function (n, e) {
+ this.setSelectedSource(e);
+ }.bind(this),
+ ),
+ t.one(
+ "ready",
+ function () {
+ (this.selectedSrc = t.src()), this.update();
+ }.bind(this),
+ ),
+ this.controlText("Open quality selector menu");
+ },
+ setSelectedSource: function (n) {
+ var e = n ? n.src : void 0;
+ this.selectedSrc !== e &&
+ ((this.selectedSrc = e),
+ i.each(this.items, function (n) {
+ n.selected(n.source.src === e);
+ }));
+ },
+ createItems: function () {
+ var e = this.player(),
+ n = e.currentSources();
+ return (
+ (n = n.filter(function (n) {
+ return null == n.hidequalityoption;
+ })),
+ i.map(
+ n,
+ function (n) {
+ return new t(e, {
+ source: n,
+ selected: n.src === this.selectedSrc,
+ });
+ }.bind(this),
+ )
+ );
+ },
+ buildWrapperCSSClass: function () {
+ return (
+ "vjs-quality-selector " +
+ r.prototype.buildWrapperCSSClass.call(this)
+ );
+ },
+ })),
+ n.registerComponent("QualitySelector", e),
+ e
+ );
+ };
+ },
+ { "../events": 5, "./QualityOption": 3, underscore: 2 },
+ ],
+ 5: [
+ function (n, e, t) {
+ "use strict";
+ e.exports = {
+ QUALITY_REQUESTED: "qualityRequested",
+ QUALITY_SELECTED: "qualitySelected",
+ PLAYER_SOURCES_CHANGED: "playerSourcesChanged",
+ };
+ },
+ {},
+ ],
+ 6: [
+ function (n, e, t) {
+ "use strict";
+ var c = n("underscore"),
+ r = n("./events"),
+ i = n("./components/QualitySelector"),
+ u = n("./middleware/SourceInterceptor"),
+ a = n("./util/SafeSeek");
+ (e.exports = function (n) {
+ (n = n || window.videojs),
+ i(n),
+ u(n),
+ n.hook("setup", function (o) {
+ o.on(r.QUALITY_REQUESTED, function (n, e) {
+ var t = o.currentSources(),
+ r = o.currentTime(),
+ i = o.playbackRate(),
+ u = o.paused();
+ c.each(t, function (n) {
+ n.selected = !1;
+ }),
+ (c.findWhere(t, { src: e.src }).selected = !0),
+ o._qualitySelectorSafeSeek &&
+ o._qualitySelectorSafeSeek.onQualitySelectionChange(),
+ o.src(t),
+ o.ready(function () {
+ (o._qualitySelectorSafeSeek &&
+ !o._qualitySelectorSafeSeek.hasFinished()) ||
+ ((o._qualitySelectorSafeSeek = new a(o, r)),
+ o.playbackRate(i)),
+ u || o.play();
+ });
+ });
+ });
+ }),
+ (e.exports.EVENTS = r);
+ },
+ {
+ "./components/QualitySelector": 4,
+ "./events": 5,
+ "./middleware/SourceInterceptor": 7,
+ "./util/SafeSeek": 9,
+ underscore: 2,
+ },
+ ],
+ 7: [
+ function (n, e, t) {
+ "use strict";
+ var u = n("underscore"),
+ o = n("../events");
+ e.exports = function (n) {
+ n.use("*", function (i) {
+ return {
+ setSource: function (n, e) {
+ var t,
+ r = i.currentSources();
+ i._qualitySelectorSafeSeek &&
+ i._qualitySelectorSafeSeek.onPlayerSourcesChange(),
+ u.isEqual(r, i._qualitySelectorPreviousSources) ||
+ (i.trigger(o.PLAYER_SOURCES_CHANGED, r),
+ (i._qualitySelectorPreviousSources = r)),
+ (t =
+ u.find(r, function (n) {
+ return (
+ !0 === n.selected ||
+ "true" === n.selected ||
+ "selected" === n.selected
+ );
+ }) || n),
+ i.trigger(o.QUALITY_SELECTED, t),
+ e(null, t);
+ },
+ };
+ });
+ };
+ },
+ { "../events": 5, underscore: 2 },
+ ],
+ 8: [
+ function (n, e, t) {
+ "use strict";
+ n("./index")();
+ },
+ { "./index": 6 },
+ ],
+ 9: [
+ function (n, e, t) {
+ "use strict";
+ var r = n("class.extend");
+ e.exports = r.extend({
+ init: function (n, e) {
+ (this._player = n),
+ (this._seekToTime = e),
+ (this._hasFinished = !1),
+ (this._keepThisInstanceWhenPlayerSourcesChange = !1),
+ this._seekWhenSafe();
+ },
+ _seekWhenSafe: function () {
+ this._player.readyState() < 3
+ ? ((this._seekFn = this._seek.bind(this)),
+ this._player.one("canplay", this._seekFn))
+ : this._seek();
+ },
+ onPlayerSourcesChange: function () {
+ this._keepThisInstanceWhenPlayerSourcesChange
+ ? (this._keepThisInstanceWhenPlayerSourcesChange = !1)
+ : this.cancel();
+ },
+ onQualitySelectionChange: function () {
+ this.hasFinished() ||
+ (this._keepThisInstanceWhenPlayerSourcesChange = !0);
+ },
+ _seek: function () {
+ this._player.currentTime(this._seekToTime),
+ (this._keepThisInstanceWhenPlayerSourcesChange = !1),
+ (this._hasFinished = !0);
+ },
+ hasFinished: function () {
+ return this._hasFinished;
+ },
+ cancel: function () {
+ this._player.off("canplay", this._seekFn),
+ (this._keepThisInstanceWhenPlayerSourcesChange = !1),
+ (this._hasFinished = !0);
+ },
+ });
+ },
+ { "class.extend": 1 },
+ ],
+ },
+ {},
+ [8],
+);
+//# sourceMappingURL=silvermine-videojs-quality-selector.min.js.map
diff --git a/assets/js/sse.js b/assets/js/sse.js
index 4f7320b3..4b29f460 100644
--- a/assets/js/sse.js
+++ b/assets/js/sse.js
@@ -17,18 +17,18 @@ var SSE = function (url, options) {
options = options || {};
this.headers = options.headers || {};
- this.payload = options.payload !== undefined ? options.payload : '';
- this.method = options.method || (this.payload && 'POST' || 'GET');
+ this.payload = options.payload !== undefined ? options.payload : "";
+ this.method = options.method || (this.payload && "POST") || "GET";
- this.FIELD_SEPARATOR = ':';
+ this.FIELD_SEPARATOR = ":";
this.listeners = {};
this.xhr = null;
this.readyState = this.INITIALIZING;
this.progress = 0;
- this.chunk = '';
+ this.chunk = "";
- this.addEventListener = function(type, listener) {
+ this.addEventListener = function (type, listener) {
if (this.listeners[type] === undefined) {
this.listeners[type] = [];
}
@@ -38,13 +38,13 @@ var SSE = function (url, options) {
}
};
- this.removeEventListener = function(type, listener) {
+ this.removeEventListener = function (type, listener) {
if (this.listeners[type] === undefined) {
return;
}
var filtered = [];
- this.listeners[type].forEach(function(element) {
+ this.listeners[type].forEach(function (element) {
if (element !== listener) {
filtered.push(element);
}
@@ -56,14 +56,14 @@ var SSE = function (url, options) {
}
};
- this.dispatchEvent = function(e) {
+ this.dispatchEvent = function (e) {
if (!e) {
return true;
}
e.source = this;
- var onHandler = 'on' + e.type;
+ var onHandler = "on" + e.type;
if (this.hasOwnProperty(onHandler)) {
this[onHandler].call(this, e);
if (e.defaultPrevented) {
@@ -72,7 +72,7 @@ var SSE = function (url, options) {
}
if (this.listeners[e.type]) {
- return this.listeners[e.type].every(function(callback) {
+ return this.listeners[e.type].every(function (callback) {
callback(e);
return !e.defaultPrevented;
});
@@ -82,78 +82,82 @@ var SSE = function (url, options) {
};
this._setReadyState = function (state) {
- var event = new CustomEvent('readystatechange');
+ var event = new CustomEvent("readystatechange");
event.readyState = state;
this.readyState = state;
this.dispatchEvent(event);
};
- this._onStreamFailure = function(e) {
- this.dispatchEvent(new CustomEvent('error'));
+ this._onStreamFailure = function (e) {
+ this.dispatchEvent(new CustomEvent("error"));
this.close();
- }
+ };
- this._onStreamProgress = function(e) {
+ this._onStreamProgress = function (e) {
if (this.xhr.status !== 200 && this.readyState !== this.CLOSED) {
this._onStreamFailure(e);
return;
}
if (this.readyState == this.CONNECTING) {
- this.dispatchEvent(new CustomEvent('open'));
+ this.dispatchEvent(new CustomEvent("open"));
this._setReadyState(this.OPEN);
}
var data = this.xhr.responseText.substring(this.progress);
this.progress += data.length;
- data.split(/(\r\n|\r|\n){2}/g).forEach(function(part) {
- if (part.trim().length === 0) {
- this.dispatchEvent(this._parseEventChunk(this.chunk.trim()));
- this.chunk = '';
- } else {
- this.chunk += part;
- }
- }.bind(this));
+ data.split(/(\r\n|\r|\n){2}/g).forEach(
+ function (part) {
+ if (part.trim().length === 0) {
+ this.dispatchEvent(this._parseEventChunk(this.chunk.trim()));
+ this.chunk = "";
+ } else {
+ this.chunk += part;
+ }
+ }.bind(this),
+ );
};
- this._onStreamLoaded = function(e) {
+ this._onStreamLoaded = function (e) {
this._onStreamProgress(e);
// Parse the last chunk.
this.dispatchEvent(this._parseEventChunk(this.chunk));
- this.chunk = '';
+ this.chunk = "";
};
/**
* Parse a received SSE event chunk into a constructed event object.
*/
- this._parseEventChunk = function(chunk) {
+ this._parseEventChunk = function (chunk) {
if (!chunk || chunk.length === 0) {
return null;
}
- var e = {'id': null, 'retry': null, 'data': '', 'event': 'message'};
- chunk.split(/\n|\r\n|\r/).forEach(function(line) {
- line = line.trimRight();
- var index = line.indexOf(this.FIELD_SEPARATOR);
- if (index <= 0) {
- // Line was either empty, or started with a separator and is a comment.
- // Either way, ignore.
- return;
- }
+ var e = { id: null, retry: null, data: "", event: "message" };
+ chunk.split(/\n|\r\n|\r/).forEach(
+ function (line) {
+ line = line.trimRight();
+ var index = line.indexOf(this.FIELD_SEPARATOR);
+ if (index <= 0) {
+ // Line was either empty, or started with a separator and is a comment.
+ // Either way, ignore.
+ return;
+ }
- var field = line.substring(0, index);
- if (!(field in e)) {
- return;
- }
+ var field = line.substring(0, index);
+ if (!(field in e)) {
+ return;
+ }
- var value = line.substring(index + 1).trimLeft();
- if (field === 'data') {
- e[field] += value;
- } else {
- e[field] = value;
- }
- }.bind(this));
+ var value = line.substring(index + 1).trimLeft();
+ if (field === "data") {
+ e[field] += value;
+ } else {
+ e[field] = value;
+ }
+ }.bind(this),
+ );
var event = new CustomEvent(e.event);
event.data = e.data;
@@ -161,21 +165,24 @@ var SSE = function (url, options) {
return event;
};
- this._checkStreamClosed = function() {
+ this._checkStreamClosed = function () {
if (this.xhr.readyState === XMLHttpRequest.DONE) {
this._setReadyState(this.CLOSED);
}
};
- this.stream = function() {
+ this.stream = function () {
this._setReadyState(this.CONNECTING);
this.xhr = new XMLHttpRequest();
- this.xhr.addEventListener('progress', this._onStreamProgress.bind(this));
- this.xhr.addEventListener('load', this._onStreamLoaded.bind(this));
- this.xhr.addEventListener('readystatechange', this._checkStreamClosed.bind(this));
- this.xhr.addEventListener('error', this._onStreamFailure.bind(this));
- this.xhr.addEventListener('abort', this._onStreamFailure.bind(this));
+ this.xhr.addEventListener("progress", this._onStreamProgress.bind(this));
+ this.xhr.addEventListener("load", this._onStreamLoaded.bind(this));
+ this.xhr.addEventListener(
+ "readystatechange",
+ this._checkStreamClosed.bind(this),
+ );
+ this.xhr.addEventListener("error", this._onStreamFailure.bind(this));
+ this.xhr.addEventListener("abort", this._onStreamFailure.bind(this));
this.xhr.open(this.method, this.url);
for (var header in this.headers) {
this.xhr.setRequestHeader(header, this.headers[header]);
@@ -183,7 +190,7 @@ var SSE = function (url, options) {
this.xhr.send(this.payload);
};
- this.close = function() {
+ this.close = function () {
if (this.readyState === this.CLOSED) {
return;
}
@@ -195,6 +202,6 @@ var SSE = function (url, options) {
};
// Export our SSE module for npm.js
-if (typeof exports !== 'undefined') {
+if (typeof exports !== "undefined") {
exports.SSE = SSE;
}
diff --git a/assets/js/subscribe_widget.js b/assets/js/subscribe_widget.js
index d0a2c975..be934187 100644
--- a/assets/js/subscribe_widget.js
+++ b/assets/js/subscribe_widget.js
@@ -1,62 +1,80 @@
-'use strict';
-var subscribe_data = JSON.parse(document.getElementById('subscribe_data').textContent);
-var payload = 'csrf_token=' + subscribe_data.csrf_token;
+"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');
+var subscribe_button = document.getElementById("subscribe");
-if (subscribe_button.getAttribute('data-type') === 'subscribe') {
- subscribe_button.onclick = subscribe;
+if (subscribe_button.getAttribute("data-type") === "subscribe") {
+ subscribe_button.onclick = subscribe;
} else {
- subscribe_button.onclick = unsubscribe;
+ subscribe_button.onclick = unsubscribe;
}
function toggleSubscribeButton() {
- subscribe_button.classList.remove("primary");
- subscribe_button.classList.remove("secondary");
- subscribe_button.classList.remove("unsubscribe");
- subscribe_button.classList.remove("subscribe");
+ subscribe_button.classList.remove("primary");
+ subscribe_button.classList.remove("secondary");
+ subscribe_button.classList.remove("unsubscribe");
+ subscribe_button.classList.remove("subscribe");
- if (subscribe_button.getAttribute('data-type') === 'subscribe') {
- subscribe_button.textContent = subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text;
- subscribe_button.onclick = unsubscribe;
- subscribe_button.classList.add("secondary");
- subscribe_button.classList.add("unsubscribe");
- } else {
- subscribe_button.textContent = subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text;
- subscribe_button.onclick = subscribe;
- subscribe_button.classList.add("primary");
- subscribe_button.classList.add("subscribe");
- }
+ if (subscribe_button.getAttribute("data-type") === "subscribe") {
+ subscribe_button.textContent =
+ subscribe_data.unsubscribe_text + " | " + subscribe_data.sub_count_text;
+ subscribe_button.onclick = unsubscribe;
+ subscribe_button.classList.add("secondary");
+ subscribe_button.classList.add("unsubscribe");
+ } else {
+ subscribe_button.textContent =
+ subscribe_data.subscribe_text + " | " + subscribe_data.sub_count_text;
+ subscribe_button.onclick = subscribe;
+ subscribe_button.classList.add("primary");
+ subscribe_button.classList.add("subscribe");
+ }
}
function subscribe(e) {
- e.preventDefault();
- var fallback = subscribe_button.textContent;
- toggleSubscribeButton();
+ e.preventDefault();
+ var fallback = subscribe_button.textContent;
+ toggleSubscribeButton();
- var url = '/subscription_ajax?action=create_subscription_to_channel&redirect=false' +
- '&c=' + subscribe_data.ucid;
+ var url =
+ "/subscription_ajax?action=create_subscription_to_channel&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.textContent = fallback;
- }
- });
+ helpers.xhr(
+ "POST",
+ url,
+ { payload: payload, retries: 5, entity_name: "subscribe request" },
+ {
+ onNon200: function (xhr) {
+ subscribe_button.onclick = subscribe;
+ subscribe_button.textContent = fallback;
+ },
+ },
+ );
}
function unsubscribe(e) {
- e.preventDefault();
- var fallback = subscribe_button.textContent;
- toggleSubscribeButton();
+ e.preventDefault();
+ var fallback = subscribe_button.textContent;
+ toggleSubscribeButton();
- var url = '/subscription_ajax?action=remove_subscriptions&redirect=false' +
- '&c=' + subscribe_data.ucid;
+ var url =
+ "/subscription_ajax?action=remove_subscriptions&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.textContent = fallback;
- }
- });
+ helpers.xhr(
+ "POST",
+ url,
+ { payload: payload, retries: 5, entity_name: "unsubscribe request" },
+ {
+ onNon200: function (xhr) {
+ subscribe_button.onclick = unsubscribe;
+ subscribe_button.textContent = fallback;
+ },
+ },
+ );
}
diff --git a/assets/js/themes.js b/assets/js/themes.js
index 545d2c18..50dc2dfd 100644
--- a/assets/js/themes.js
+++ b/assets/js/themes.js
@@ -1,46 +1,46 @@
-'use strict';
-var toggle_theme = document.getElementById('toggle_theme');
+"use strict";
+var toggle_theme = document.getElementById("toggle_theme");
-const STORAGE_KEY_THEME = 'dark_mode';
-const THEME_DARK = 'dark';
-const THEME_LIGHT = 'light';
+const STORAGE_KEY_THEME = "dark_mode";
+const THEME_DARK = "dark";
+const THEME_LIGHT = "light";
// TODO: theme state controlled by system
-toggle_theme.addEventListener('click', function (e) {
- e.preventDefault();
- const isDarkTheme = helpers.storage.get(STORAGE_KEY_THEME) === 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', {}, {});
+toggle_theme.addEventListener("click", function (e) {
+ e.preventDefault();
+ const isDarkTheme = helpers.storage.get(STORAGE_KEY_THEME) === 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", {}, {});
});
/** @param {THEME_DARK|THEME_LIGHT} theme */
function setTheme(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 if (theme === THEME_LIGHT) {
- toggle_theme.children[0].className = 'icon ion-ios-moon';
- document.body.className = 'light-theme';
- } else {
- document.body.className = 'no-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 if (theme === THEME_LIGHT) {
+ toggle_theme.children[0].className = "icon ion-ios-moon";
+ document.body.className = "light-theme";
+ } else {
+ document.body.className = "no-theme";
+ }
}
// Handles theme change event caused by other tab
-addEventListener('storage', function (e) {
- if (e.key === STORAGE_KEY_THEME)
- setTheme(helpers.storage.get(STORAGE_KEY_THEME));
+addEventListener("storage", function (e) {
+ 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;
- if (prefTheme) {
- setTheme(prefTheme);
- helpers.storage.set(STORAGE_KEY_THEME, prefTheme);
- }
+addEventListener("DOMContentLoaded", function () {
+ const prefTheme = document.getElementById("dark_mode_pref").textContent;
+ if (prefTheme) {
+ setTheme(prefTheme);
+ helpers.storage.set(STORAGE_KEY_THEME, prefTheme);
+ }
});
diff --git a/assets/js/videojs-youtube-annotations.min.js b/assets/js/videojs-youtube-annotations.min.js
index c93e14e8..1943744d 100644
--- a/assets/js/videojs-youtube-annotations.min.js
+++ b/assets/js/videojs-youtube-annotations.min.js
@@ -1 +1,936 @@
-class AnnotationParser{static get defaultAppearanceAttributes(){return{bgColor:16777215,bgOpacity:.8,fgColor:0,textSize:3.15}}static get attributeMap(){return{type:"tp",style:"s",x:"x",y:"y",width:"w",height:"h",sx:"sx",sy:"sy",timeStart:"ts",timeEnd:"te",text:"t",actionType:"at",actionUrl:"au",actionUrlTarget:"aut",actionSeconds:"as",bgOpacity:"bgo",bgColor:"bgc",fgColor:"fgc",textSize:"txsz"}}deserializeAnnotation(serializedAnnotation){const map=this.constructor.attributeMap;const attributes=serializedAnnotation.split(",");const annotation={};for(const attribute of attributes){const[key,value]=attribute.split("=");const mappedKey=this.getKeyByValue(map,key);let finalValue="";if(["text","actionType","actionUrl","actionUrlTarget","type","style"].indexOf(mappedKey)>-1){finalValue=decodeURIComponent(value)}else{finalValue=parseFloat(value,10)}annotation[mappedKey]=finalValue}return annotation}serializeAnnotation(annotation){const map=this.constructor.attributeMap;let serialized="";for(const key in annotation){const mappedKey=map[key];if(["text","actionType","actionUrl","actionUrlTarget"].indexOf(key)>-1&&mappedKey&&annotation.hasOwnProperty(key)){let text=encodeURIComponent(annotation[key]);serialized+=`${mappedKey}=${text},`}else if(["text","actionType","actionUrl","actionUrlTarget"].indexOf("key")===-1&&mappedKey&&annotation.hasOwnProperty(key)){serialized+=`${mappedKey}=${annotation[key]},`}}return serialized.substring(0,serialized.length-1)}deserializeAnnotationList(serializedAnnotationString){const serializedAnnotations=serializedAnnotationString.split(";");serializedAnnotations.length=serializedAnnotations.length-1;const annotations=[];for(const annotation of serializedAnnotations){annotations.push(this.deserializeAnnotation(annotation))}return annotations}serializeAnnotationList(annotations){let serialized="";for(const annotation of annotations){serialized+=this.serializeAnnotation(annotation)+";"}return serialized}xmlToDom(xml){const parser=new DOMParser;const dom=parser.parseFromString(xml,"application/xml");return dom}getAnnotationsFromXml(xml){const dom=this.xmlToDom(xml);return dom.getElementsByTagName("annotation")}parseYoutubeAnnotationList(annotationElements){const annotations=[];for(const el of annotationElements){const parsedAnnotation=this.parseYoutubeAnnotation(el);if(parsedAnnotation)annotations.push(parsedAnnotation)}return annotations}parseYoutubeAnnotation(annotationElement){const base=annotationElement;const attributes=this.getAttributesFromBase(base);if(!attributes.type||attributes.type==="pause")return null;const text=this.getTextFromBase(base);const action=this.getActionFromBase(base);const backgroundShape=this.getBackgroundShapeFromBase(base);if(!backgroundShape)return null;const timeStart=backgroundShape.timeRange.start;const timeEnd=backgroundShape.timeRange.end;if(isNaN(timeStart)||isNaN(timeEnd)||timeStart===null||timeEnd===null){return null}const appearance=this.getAppearanceFromBase(base);let annotation={type:attributes.type,x:backgroundShape.x,y:backgroundShape.y,width:backgroundShape.width,height:backgroundShape.height,timeStart:timeStart,timeEnd:timeEnd};if(attributes.style)annotation.style=attributes.style;if(text)annotation.text=text;if(action)annotation=Object.assign(action,annotation);if(appearance)annotation=Object.assign(appearance,annotation);if(backgroundShape.hasOwnProperty("sx"))annotation.sx=backgroundShape.sx;if(backgroundShape.hasOwnProperty("sy"))annotation.sy=backgroundShape.sy;return annotation}getBackgroundShapeFromBase(base){const movingRegion=base.getElementsByTagName("movingRegion")[0];if(!movingRegion)return null;const regionType=movingRegion.getAttribute("type");const regions=movingRegion.getElementsByTagName(`${regionType}Region`);const timeRange=this.extractRegionTime(regions);const shape={type:regionType,x:parseFloat(regions[0].getAttribute("x"),10),y:parseFloat(regions[0].getAttribute("y"),10),width:parseFloat(regions[0].getAttribute("w"),10),height:parseFloat(regions[0].getAttribute("h"),10),timeRange:timeRange};const sx=regions[0].getAttribute("sx");const sy=regions[0].getAttribute("sy");if(sx)shape.sx=parseFloat(sx,10);if(sy)shape.sy=parseFloat(sy,10);return shape}getAttributesFromBase(base){const attributes={};attributes.type=base.getAttribute("type");attributes.style=base.getAttribute("style");return attributes}getTextFromBase(base){const textElement=base.getElementsByTagName("TEXT")[0];if(textElement)return textElement.textContent}getActionFromBase(base){const actionElement=base.getElementsByTagName("action")[0];if(!actionElement)return null;const typeAttr=actionElement.getAttribute("type");const urlElement=actionElement.getElementsByTagName("url")[0];if(!urlElement)return null;const actionUrlTarget=urlElement.getAttribute("target");const href=urlElement.getAttribute("value");if(href.startsWith("https://www.youtube.com/")){const url=new URL(href);const srcVid=url.searchParams.get("src_vid");const toVid=url.searchParams.get("v");return this.linkOrTimestamp(url,srcVid,toVid,actionUrlTarget)}}linkOrTimestamp(url,srcVid,toVid,actionUrlTarget){if(srcVid&&toVid&&srcVid===toVid){let seconds=0;const hash=url.hash;if(hash&&hash.startsWith("#t=")){const timeString=url.hash.split("#t=")[1];seconds=this.timeStringToSeconds(timeString)}return{actionType:"time",actionSeconds:seconds}}else{return{actionType:"url",actionUrl:url.href,actionUrlTarget:actionUrlTarget}}}getAppearanceFromBase(base){const appearanceElement=base.getElementsByTagName("appearance")[0];const styles=this.constructor.defaultAppearanceAttributes;if(appearanceElement){const bgOpacity=appearanceElement.getAttribute("bgAlpha");const bgColor=appearanceElement.getAttribute("bgColor");const fgColor=appearanceElement.getAttribute("fgColor");const textSize=appearanceElement.getAttribute("textSize");if(bgOpacity)styles.bgOpacity=parseFloat(bgOpacity,10);if(bgColor)styles.bgColor=parseInt(bgColor,10);if(fgColor)styles.fgColor=parseInt(fgColor,10);if(textSize)styles.textSize=parseFloat(textSize,10)}return styles}extractRegionTime(regions){let timeStart=regions[0].getAttribute("t");timeStart=this.hmsToSeconds(timeStart);let timeEnd=regions[regions.length-1].getAttribute("t");timeEnd=this.hmsToSeconds(timeEnd);return{start:timeStart,end:timeEnd}}hmsToSeconds(hms){let p=hms.split(":");let s=0;let m=1;while(p.length>0){s+=m*parseFloat(p.pop(),10);m*=60}return s}timeStringToSeconds(time){let seconds=0;const h=time.split("h");const m=(h[1]||time).split("m");const s=(m[1]||time).split("s");if(h[0]&&h.length===2)seconds+=parseInt(h[0],10)*60*60;if(m[0]&&m.length===2)seconds+=parseInt(m[0],10)*60;if(s[0]&&s.length===2)seconds+=parseInt(s[0],10);return seconds}getKeyByValue(obj,value){for(const key in obj){if(obj.hasOwnProperty(key)){if(obj[key]===value){return key}}}}}class AnnotationRenderer{constructor(annotations,container,playerOptions,updateInterval=1e3){if(!annotations)throw new Error("Annotation objects must be provided");if(!container)throw new Error("An element to contain the annotations must be provided");if(playerOptions&&playerOptions.getVideoTime&&playerOptions.seekTo){this.playerOptions=playerOptions}else{console.info("AnnotationRenderer is running without a player. The update method will need to be called manually.")}this.annotations=annotations;this.container=container;this.annotationsContainer=document.createElement("div");this.annotationsContainer.classList.add("__cxt-ar-annotations-container__");this.annotationsContainer.setAttribute("data-layer","4");this.annotationsContainer.addEventListener("click",e=>{this.annotationClickHandler(e)});this.container.prepend(this.annotationsContainer);this.createAnnotationElements();this.updateAllAnnotationSizes();window.addEventListener("DOMContentLoaded",e=>{this.updateAllAnnotationSizes()});this.updateInterval=updateInterval;this.updateIntervalId=null}changeAnnotationData(annotations){this.stop();this.removeAnnotationElements();this.annotations=annotations;this.createAnnotationElements();this.start()}createAnnotationElements(){for(const annotation of this.annotations){const el=document.createElement("div");el.classList.add("__cxt-ar-annotation__");annotation.__element=el;el.__annotation=annotation;const closeButton=this.createCloseElement();closeButton.addEventListener("click",e=>{el.setAttribute("hidden","");el.setAttribute("data-ar-closed","");if(el.__annotation.__speechBubble){const speechBubble=el.__annotation.__speechBubble;speechBubble.style.display="none"}});el.append(closeButton);if(annotation.text){const textNode=document.createElement("span");textNode.textContent=annotation.text;el.append(textNode);el.setAttribute("data-ar-has-text","")}if(annotation.style==="speech"){const containerDimensions=this.container.getBoundingClientRect();const speechX=this.percentToPixels(containerDimensions.width,annotation.x);const speechY=this.percentToPixels(containerDimensions.height,annotation.y);const speechWidth=this.percentToPixels(containerDimensions.width,annotation.width);const speechHeight=this.percentToPixels(containerDimensions.height,annotation.height);const speechPointX=this.percentToPixels(containerDimensions.width,annotation.sx);const speechPointY=this.percentToPixels(containerDimensions.height,annotation.sy);const bubbleColor=this.getFinalAnnotationColor(annotation,false);const bubble=this.createSvgSpeechBubble(speechX,speechY,speechWidth,speechHeight,speechPointX,speechPointY,bubbleColor,annotation.__element);bubble.style.display="none";bubble.style.overflow="visible";el.style.pointerEvents="none";bubble.__annotationEl=el;annotation.__speechBubble=bubble;const path=bubble.getElementsByTagName("path")[0];path.addEventListener("mouseover",()=>{closeButton.style.display="block";closeButton.style.cursor="pointer";path.setAttribute("fill",this.getFinalAnnotationColor(annotation,true))});path.addEventListener("mouseout",e=>{if(!e.relatedTarget.classList.contains("__cxt-ar-annotation-close__")){closeButton.style.display="none";closeButton.style.cursor="default";path.setAttribute("fill",this.getFinalAnnotationColor(annotation,false))}});closeButton.addEventListener("mouseleave",()=>{closeButton.style.display="none";path.style.cursor="default";closeButton.style.cursor="default";path.setAttribute("fill",this.getFinalAnnotationColor(annotation,false))});el.prepend(bubble)}else if(annotation.type==="highlight"){el.style.backgroundColor="";el.style.border=`2.5px solid ${this.getFinalAnnotationColor(annotation,false)}`;if(annotation.actionType==="url")el.style.cursor="pointer"}else if(annotation.style!=="title"){el.style.backgroundColor=this.getFinalAnnotationColor(annotation);el.addEventListener("mouseenter",()=>{el.style.backgroundColor=this.getFinalAnnotationColor(annotation,true)});el.addEventListener("mouseleave",()=>{el.style.backgroundColor=this.getFinalAnnotationColor(annotation,false)});if(annotation.actionType==="url")el.style.cursor="pointer"}el.style.color=`#${this.decimalToHex(annotation.fgColor)}`;el.setAttribute("data-ar-type",annotation.type);el.setAttribute("hidden","");this.annotationsContainer.append(el)}}createCloseElement(){const svg=document.createElementNS("http://www.w3.org/2000/svg","svg");svg.setAttribute("viewBox","0 0 100 100");svg.classList.add("__cxt-ar-annotation-close__");const path=document.createElementNS(svg.namespaceURI,"path");path.setAttribute("d","M25 25 L 75 75 M 75 25 L 25 75");path.setAttribute("stroke","#bbb");path.setAttribute("stroke-width",10);path.setAttribute("x",5);path.setAttribute("y",5);const circle=document.createElementNS(svg.namespaceURI,"circle");circle.setAttribute("cx",50);circle.setAttribute("cy",50);circle.setAttribute("r",50);svg.append(circle,path);return svg}createSvgSpeechBubble(x,y,width,height,pointX,pointY,color="white",element,svg){const horizontalBaseStartMultiplier=.17379070765180116;const horizontalBaseEndMultiplier=.14896346370154384;const verticalBaseStartMultiplier=.12;const verticalBaseEndMultiplier=.3;let path;if(!svg){svg=document.createElementNS("http://www.w3.org/2000/svg","svg");svg.classList.add("__cxt-ar-annotation-speech-bubble__");path=document.createElementNS("http://www.w3.org/2000/svg","path");path.setAttribute("fill",color);svg.append(path)}else{path=svg.children[0]}svg.style.position="absolute";svg.setAttribute("width","100%");svg.setAttribute("height","100%");svg.style.left="0";svg.style.top="0";let positionStart;let baseStartX=0;let baseStartY=0;let baseEndX=0;let baseEndY=0;let pointFinalX=pointX;let pointFinalY=pointY;let commentRectPath;const pospad=20;let textWidth=0;let textHeight=0;let textX=0;let textY=0;let textElement;let closeElement;if(element){textElement=element.getElementsByTagName("span")[0];closeElement=element.getElementsByClassName("__cxt-ar-annotation-close__")[0]}if(pointX>x+width-width/2&&pointY>y+height){positionStart="br";baseStartX=width-width*horizontalBaseStartMultiplier*2;baseEndX=baseStartX+width*horizontalBaseEndMultiplier;baseStartY=height;baseEndY=height;pointFinalX=pointX-x;pointFinalY=pointY-y;element.style.height=pointY-y;commentRectPath=`L${width} ${height} L${width} 0 L0 0 L0 ${baseStartY} L${baseStartX} ${baseStartY}`;if(textElement){textWidth=width;textHeight=height;textX=0;textY=0}}else if(pointXy+height){positionStart="bl";baseStartX=width*horizontalBaseStartMultiplier;baseEndX=baseStartX+width*horizontalBaseEndMultiplier;baseStartY=height;baseEndY=height;pointFinalX=pointX-x;pointFinalY=pointY-y;element.style.height=`${pointY-y}px`;commentRectPath=`L${width} ${height} L${width} 0 L0 0 L0 ${baseStartY} L${baseStartX} ${baseStartY}`;if(textElement){textWidth=width;textHeight=height;textX=0;textY=0}}else if(pointX>x+width-width/2&&pointYx+width&&pointY>y-pospad&&pointYy&&pointY=start&&videoTimeend)){el.setAttribute("hidden","");if(annotation.style==="speech"&&annotation.__speechBubble){annotation.__speechBubble.style.display="none"}}}}start(){if(!this.playerOptions)throw new Error("playerOptions must be provided to use the start method");const videoTime=this.playerOptions.getVideoTime();if(!this.updateIntervalId){this.update(videoTime);this.updateIntervalId=setInterval(()=>{const videoTime=this.playerOptions.getVideoTime();this.update(videoTime);window.dispatchEvent(new CustomEvent("__ar_renderer_start"))},this.updateInterval)}}stop(){if(!this.playerOptions)throw new Error("playerOptions must be provided to use the stop method");const videoTime=this.playerOptions.getVideoTime();if(this.updateIntervalId){this.update(videoTime);clearInterval(this.updateIntervalId);this.updateIntervalId=null;window.dispatchEvent(new CustomEvent("__ar_renderer_stop"))}}updateAnnotationTextSize(annotation,containerHeight){if(annotation.textSize){const textSize=annotation.textSize/100*containerHeight;annotation.__element.style.fontSize=`${textSize}px`}}updateTextSize(){const containerHeight=this.container.getBoundingClientRect().height;for(const annotation of this.annotations){this.updateAnnotationTextSize(annotation,containerHeight)}}updateCloseSize(containerHeight){if(!containerHeight)containerHeight=this.container.getBoundingClientRect().height;const multiplier=.0423;this.annotationsContainer.style.setProperty("--annotation-close-size",`${containerHeight*multiplier}px`)}updateAnnotationDimensions(annotations,videoWidth,videoHeight){const playerWidth=this.container.getBoundingClientRect().width;const playerHeight=this.container.getBoundingClientRect().height;const widthDivider=playerWidth/videoWidth;const heightDivider=playerHeight/videoHeight;let scaledVideoWidth=playerWidth;let scaledVideoHeight=playerHeight;if(widthDivider%1!==0||heightDivider%1!==0){if(widthDivider>heightDivider){scaledVideoWidth=playerHeight/videoHeight*videoWidth;scaledVideoHeight=playerHeight}else if(heightDivider>widthDivider){scaledVideoWidth=playerWidth;scaledVideoHeight=playerWidth/videoWidth*videoHeight}}const verticalBlackBarWidth=(playerWidth-scaledVideoWidth)/2;const horizontalBlackBarHeight=(playerHeight-scaledVideoHeight)/2;const widthOffsetPercent=verticalBlackBarWidth/playerWidth*100;const heightOffsetPercent=horizontalBlackBarHeight/playerHeight*100;const widthMultiplier=scaledVideoWidth/playerWidth;const heightMultiplier=scaledVideoHeight/playerHeight;for(const annotation of annotations){const el=annotation.__element;let ax=widthOffsetPercent+annotation.x*widthMultiplier;let ay=heightOffsetPercent+annotation.y*heightMultiplier;let aw=annotation.width*widthMultiplier;let ah=annotation.height*heightMultiplier;el.style.left=`${ax}%`;el.style.top=`${ay}%`;el.style.width=`${aw}%`;el.style.height=`${ah}%`;let horizontalPadding=scaledVideoWidth*.008;let verticalPadding=scaledVideoHeight*.008;if(annotation.style==="speech"&&annotation.text){const pel=annotation.__element.getElementsByTagName("span")[0];horizontalPadding*=2;verticalPadding*=2;pel.style.paddingLeft=horizontalPadding+"px";pel.style.paddingRight=horizontalPadding+"px";pel.style.paddingBottom=verticalPadding+"px";pel.style.paddingTop=verticalPadding+"px"}else if(annotation.style!=="speech"){el.style.paddingLeft=horizontalPadding+"px";el.style.paddingRight=horizontalPadding+"px";el.style.paddingBottom=verticalPadding+"px";el.style.paddingTop=verticalPadding+"px"}if(annotation.__speechBubble){const asx=this.percentToPixels(playerWidth,ax);const asy=this.percentToPixels(playerHeight,ay);const asw=this.percentToPixels(playerWidth,aw);const ash=this.percentToPixels(playerHeight,ah);let sx=widthOffsetPercent+annotation.sx*widthMultiplier;let sy=heightOffsetPercent+annotation.sy*heightMultiplier;sx=this.percentToPixels(playerWidth,sx);sy=this.percentToPixels(playerHeight,sy);this.createSvgSpeechBubble(asx,asy,asw,ash,sx,sy,null,annotation.__element,annotation.__speechBubble)}this.updateAnnotationTextSize(annotation,scaledVideoHeight);this.updateCloseSize(scaledVideoHeight)}}updateAllAnnotationSizes(){if(this.playerOptions&&this.playerOptions.getOriginalVideoWidth&&this.playerOptions.getOriginalVideoHeight){const videoWidth=this.playerOptions.getOriginalVideoWidth();const videoHeight=this.playerOptions.getOriginalVideoHeight();this.updateAnnotationDimensions(this.annotations,videoWidth,videoHeight)}else{const playerWidth=this.container.getBoundingClientRect().width;const playerHeight=this.container.getBoundingClientRect().height;this.updateAnnotationDimensions(this.annotations,playerWidth,playerHeight)}}hideAll(){for(const annotation of this.annotations){annotation.__element.setAttribute("hidden","")}}annotationClickHandler(e){let annotationElement=e.target;if(!annotationElement.matches(".__cxt-ar-annotation__")&&!annotationElement.closest(".__cxt-ar-annotation-close__")){annotationElement=annotationElement.closest(".__cxt-ar-annotation__");if(!annotationElement)return null}let annotationData=annotationElement.__annotation;if(!annotationElement||!annotationData)return;if(annotationData.actionType==="time"){const seconds=annotationData.actionSeconds;if(this.playerOptions){this.playerOptions.seekTo(seconds);const videoTime=this.playerOptions.getVideoTime();this.update(videoTime)}window.dispatchEvent(new CustomEvent("__ar_seek_to",{detail:{seconds:seconds}}))}else if(annotationData.actionType==="url"){const data={url:annotationData.actionUrl,target:annotationData.actionUrlTarget||"current"};const timeHash=this.extractTimeHash(new URL(data.url));if(timeHash&&timeHash.hasOwnProperty("seconds")){data.seconds=timeHash.seconds}window.dispatchEvent(new CustomEvent("__ar_annotation_click",{detail:data}))}}setUpdateInterval(ms){this.updateInterval=ms;this.stop();this.start()}decimalToHex(dec){let hex=dec.toString(16);hex="000000".substr(0,6-hex.length)+hex;return hex}extractTimeHash(url){if(!url)throw new Error("A URL must be provided");const hash=url.hash;if(hash&&hash.startsWith("#t=")){const timeString=url.hash.split("#t=")[1];const seconds=this.timeStringToSeconds(timeString);return{seconds:seconds}}else{return false}}timeStringToSeconds(time){let seconds=0;const h=time.split("h");const m=(h[1]||time).split("m");const s=(m[1]||time).split("s");if(h[0]&&h.length===2)seconds+=parseInt(h[0],10)*60*60;if(m[0]&&m.length===2)seconds+=parseInt(m[0],10)*60;if(s[0]&&s.length===2)seconds+=parseInt(s[0],10);return seconds}percentToPixels(a,b){return a*b/100}}function youtubeAnnotationsPlugin(options){if(!options.annotationXml)throw new Error("Annotation data must be provided");if(!options.videoContainer)throw new Error("A video container to overlay the data on must be provided");const player=this;const xml=options.annotationXml;const parser=new AnnotationParser;const annotationElements=parser.getAnnotationsFromXml(xml);const annotations=parser.parseYoutubeAnnotationList(annotationElements);const videoContainer=options.videoContainer;const playerOptions={getVideoTime(){return player.currentTime()},seekTo(seconds){player.currentTime(seconds)},getOriginalVideoWidth(){return player.videoWidth()},getOriginalVideoHeight(){return player.videoHeight()}};raiseControls();const renderer=new AnnotationRenderer(annotations,videoContainer,playerOptions,options.updateInterval);setupEventListeners(player,renderer);renderer.start()}function setupEventListeners(player,renderer){if(!player)throw new Error("A video player must be provided");player.on("playerresize",e=>{renderer.updateAllAnnotationSizes(renderer.annotations)});player.one("loadedmetadata",e=>{renderer.updateAllAnnotationSizes(renderer.annotations)});player.on("pause",e=>{renderer.stop()});player.on("play",e=>{renderer.start()});player.on("seeking",e=>{renderer.update()});player.on("seeked",e=>{renderer.update()})}function raiseControls(){const styles=document.createElement("style");styles.textContent=`\n\t.vjs-control-bar {\n\t\tz-index: 21;\n\t}\n\t`;document.body.append(styles)}
+class AnnotationParser {
+ static get defaultAppearanceAttributes() {
+ return { bgColor: 16777215, bgOpacity: 0.8, fgColor: 0, textSize: 3.15 };
+ }
+ static get attributeMap() {
+ return {
+ type: "tp",
+ style: "s",
+ x: "x",
+ y: "y",
+ width: "w",
+ height: "h",
+ sx: "sx",
+ sy: "sy",
+ timeStart: "ts",
+ timeEnd: "te",
+ text: "t",
+ actionType: "at",
+ actionUrl: "au",
+ actionUrlTarget: "aut",
+ actionSeconds: "as",
+ bgOpacity: "bgo",
+ bgColor: "bgc",
+ fgColor: "fgc",
+ textSize: "txsz",
+ };
+ }
+ deserializeAnnotation(serializedAnnotation) {
+ const map = this.constructor.attributeMap;
+ const attributes = serializedAnnotation.split(",");
+ const annotation = {};
+ for (const attribute of attributes) {
+ const [key, value] = attribute.split("=");
+ const mappedKey = this.getKeyByValue(map, key);
+ let finalValue = "";
+ if (
+ [
+ "text",
+ "actionType",
+ "actionUrl",
+ "actionUrlTarget",
+ "type",
+ "style",
+ ].indexOf(mappedKey) > -1
+ ) {
+ finalValue = decodeURIComponent(value);
+ } else {
+ finalValue = parseFloat(value, 10);
+ }
+ annotation[mappedKey] = finalValue;
+ }
+ return annotation;
+ }
+ serializeAnnotation(annotation) {
+ const map = this.constructor.attributeMap;
+ let serialized = "";
+ for (const key in annotation) {
+ const mappedKey = map[key];
+ if (
+ ["text", "actionType", "actionUrl", "actionUrlTarget"].indexOf(key) >
+ -1 &&
+ mappedKey &&
+ annotation.hasOwnProperty(key)
+ ) {
+ let text = encodeURIComponent(annotation[key]);
+ serialized += `${mappedKey}=${text},`;
+ } else if (
+ ["text", "actionType", "actionUrl", "actionUrlTarget"].indexOf(
+ "key",
+ ) === -1 &&
+ mappedKey &&
+ annotation.hasOwnProperty(key)
+ ) {
+ serialized += `${mappedKey}=${annotation[key]},`;
+ }
+ }
+ return serialized.substring(0, serialized.length - 1);
+ }
+ deserializeAnnotationList(serializedAnnotationString) {
+ const serializedAnnotations = serializedAnnotationString.split(";");
+ serializedAnnotations.length = serializedAnnotations.length - 1;
+ const annotations = [];
+ for (const annotation of serializedAnnotations) {
+ annotations.push(this.deserializeAnnotation(annotation));
+ }
+ return annotations;
+ }
+ serializeAnnotationList(annotations) {
+ let serialized = "";
+ for (const annotation of annotations) {
+ serialized += this.serializeAnnotation(annotation) + ";";
+ }
+ return serialized;
+ }
+ xmlToDom(xml) {
+ const parser = new DOMParser();
+ const dom = parser.parseFromString(xml, "application/xml");
+ return dom;
+ }
+ getAnnotationsFromXml(xml) {
+ const dom = this.xmlToDom(xml);
+ return dom.getElementsByTagName("annotation");
+ }
+ parseYoutubeAnnotationList(annotationElements) {
+ const annotations = [];
+ for (const el of annotationElements) {
+ const parsedAnnotation = this.parseYoutubeAnnotation(el);
+ if (parsedAnnotation) annotations.push(parsedAnnotation);
+ }
+ return annotations;
+ }
+ parseYoutubeAnnotation(annotationElement) {
+ const base = annotationElement;
+ const attributes = this.getAttributesFromBase(base);
+ if (!attributes.type || attributes.type === "pause") return null;
+ const text = this.getTextFromBase(base);
+ const action = this.getActionFromBase(base);
+ const backgroundShape = this.getBackgroundShapeFromBase(base);
+ if (!backgroundShape) return null;
+ const timeStart = backgroundShape.timeRange.start;
+ const timeEnd = backgroundShape.timeRange.end;
+ if (
+ isNaN(timeStart) ||
+ isNaN(timeEnd) ||
+ timeStart === null ||
+ timeEnd === null
+ ) {
+ return null;
+ }
+ const appearance = this.getAppearanceFromBase(base);
+ let annotation = {
+ type: attributes.type,
+ x: backgroundShape.x,
+ y: backgroundShape.y,
+ width: backgroundShape.width,
+ height: backgroundShape.height,
+ timeStart: timeStart,
+ timeEnd: timeEnd,
+ };
+ if (attributes.style) annotation.style = attributes.style;
+ if (text) annotation.text = text;
+ if (action) annotation = Object.assign(action, annotation);
+ if (appearance) annotation = Object.assign(appearance, annotation);
+ if (backgroundShape.hasOwnProperty("sx"))
+ annotation.sx = backgroundShape.sx;
+ if (backgroundShape.hasOwnProperty("sy"))
+ annotation.sy = backgroundShape.sy;
+ return annotation;
+ }
+ getBackgroundShapeFromBase(base) {
+ const movingRegion = base.getElementsByTagName("movingRegion")[0];
+ if (!movingRegion) return null;
+ const regionType = movingRegion.getAttribute("type");
+ const regions = movingRegion.getElementsByTagName(`${regionType}Region`);
+ const timeRange = this.extractRegionTime(regions);
+ const shape = {
+ type: regionType,
+ x: parseFloat(regions[0].getAttribute("x"), 10),
+ y: parseFloat(regions[0].getAttribute("y"), 10),
+ width: parseFloat(regions[0].getAttribute("w"), 10),
+ height: parseFloat(regions[0].getAttribute("h"), 10),
+ timeRange: timeRange,
+ };
+ const sx = regions[0].getAttribute("sx");
+ const sy = regions[0].getAttribute("sy");
+ if (sx) shape.sx = parseFloat(sx, 10);
+ if (sy) shape.sy = parseFloat(sy, 10);
+ return shape;
+ }
+ getAttributesFromBase(base) {
+ const attributes = {};
+ attributes.type = base.getAttribute("type");
+ attributes.style = base.getAttribute("style");
+ return attributes;
+ }
+ getTextFromBase(base) {
+ const textElement = base.getElementsByTagName("TEXT")[0];
+ if (textElement) return textElement.textContent;
+ }
+ getActionFromBase(base) {
+ const actionElement = base.getElementsByTagName("action")[0];
+ if (!actionElement) return null;
+ const typeAttr = actionElement.getAttribute("type");
+ const urlElement = actionElement.getElementsByTagName("url")[0];
+ if (!urlElement) return null;
+ const actionUrlTarget = urlElement.getAttribute("target");
+ const href = urlElement.getAttribute("value");
+ if (href.startsWith("https://www.youtube.com/")) {
+ const url = new URL(href);
+ const srcVid = url.searchParams.get("src_vid");
+ const toVid = url.searchParams.get("v");
+ return this.linkOrTimestamp(url, srcVid, toVid, actionUrlTarget);
+ }
+ }
+ linkOrTimestamp(url, srcVid, toVid, actionUrlTarget) {
+ if (srcVid && toVid && srcVid === toVid) {
+ let seconds = 0;
+ const hash = url.hash;
+ if (hash && hash.startsWith("#t=")) {
+ const timeString = url.hash.split("#t=")[1];
+ seconds = this.timeStringToSeconds(timeString);
+ }
+ return { actionType: "time", actionSeconds: seconds };
+ } else {
+ return {
+ actionType: "url",
+ actionUrl: url.href,
+ actionUrlTarget: actionUrlTarget,
+ };
+ }
+ }
+ getAppearanceFromBase(base) {
+ const appearanceElement = base.getElementsByTagName("appearance")[0];
+ const styles = this.constructor.defaultAppearanceAttributes;
+ if (appearanceElement) {
+ const bgOpacity = appearanceElement.getAttribute("bgAlpha");
+ const bgColor = appearanceElement.getAttribute("bgColor");
+ const fgColor = appearanceElement.getAttribute("fgColor");
+ const textSize = appearanceElement.getAttribute("textSize");
+ if (bgOpacity) styles.bgOpacity = parseFloat(bgOpacity, 10);
+ if (bgColor) styles.bgColor = parseInt(bgColor, 10);
+ if (fgColor) styles.fgColor = parseInt(fgColor, 10);
+ if (textSize) styles.textSize = parseFloat(textSize, 10);
+ }
+ return styles;
+ }
+ extractRegionTime(regions) {
+ let timeStart = regions[0].getAttribute("t");
+ timeStart = this.hmsToSeconds(timeStart);
+ let timeEnd = regions[regions.length - 1].getAttribute("t");
+ timeEnd = this.hmsToSeconds(timeEnd);
+ return { start: timeStart, end: timeEnd };
+ }
+ hmsToSeconds(hms) {
+ let p = hms.split(":");
+ let s = 0;
+ let m = 1;
+ while (p.length > 0) {
+ s += m * parseFloat(p.pop(), 10);
+ m *= 60;
+ }
+ return s;
+ }
+ timeStringToSeconds(time) {
+ let seconds = 0;
+ const h = time.split("h");
+ const m = (h[1] || time).split("m");
+ const s = (m[1] || time).split("s");
+ if (h[0] && h.length === 2) seconds += parseInt(h[0], 10) * 60 * 60;
+ if (m[0] && m.length === 2) seconds += parseInt(m[0], 10) * 60;
+ if (s[0] && s.length === 2) seconds += parseInt(s[0], 10);
+ return seconds;
+ }
+ getKeyByValue(obj, value) {
+ for (const key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ if (obj[key] === value) {
+ return key;
+ }
+ }
+ }
+ }
+}
+class AnnotationRenderer {
+ constructor(annotations, container, playerOptions, updateInterval = 1e3) {
+ if (!annotations) throw new Error("Annotation objects must be provided");
+ if (!container)
+ throw new Error("An element to contain the annotations must be provided");
+ if (playerOptions && playerOptions.getVideoTime && playerOptions.seekTo) {
+ this.playerOptions = playerOptions;
+ } else {
+ console.info(
+ "AnnotationRenderer is running without a player. The update method will need to be called manually.",
+ );
+ }
+ this.annotations = annotations;
+ this.container = container;
+ this.annotationsContainer = document.createElement("div");
+ this.annotationsContainer.classList.add("__cxt-ar-annotations-container__");
+ this.annotationsContainer.setAttribute("data-layer", "4");
+ this.annotationsContainer.addEventListener("click", (e) => {
+ this.annotationClickHandler(e);
+ });
+ this.container.prepend(this.annotationsContainer);
+ this.createAnnotationElements();
+ this.updateAllAnnotationSizes();
+ window.addEventListener("DOMContentLoaded", (e) => {
+ this.updateAllAnnotationSizes();
+ });
+ this.updateInterval = updateInterval;
+ this.updateIntervalId = null;
+ }
+ changeAnnotationData(annotations) {
+ this.stop();
+ this.removeAnnotationElements();
+ this.annotations = annotations;
+ this.createAnnotationElements();
+ this.start();
+ }
+ createAnnotationElements() {
+ for (const annotation of this.annotations) {
+ const el = document.createElement("div");
+ el.classList.add("__cxt-ar-annotation__");
+ annotation.__element = el;
+ el.__annotation = annotation;
+ const closeButton = this.createCloseElement();
+ closeButton.addEventListener("click", (e) => {
+ el.setAttribute("hidden", "");
+ el.setAttribute("data-ar-closed", "");
+ if (el.__annotation.__speechBubble) {
+ const speechBubble = el.__annotation.__speechBubble;
+ speechBubble.style.display = "none";
+ }
+ });
+ el.append(closeButton);
+ if (annotation.text) {
+ const textNode = document.createElement("span");
+ textNode.textContent = annotation.text;
+ el.append(textNode);
+ el.setAttribute("data-ar-has-text", "");
+ }
+ if (annotation.style === "speech") {
+ const containerDimensions = this.container.getBoundingClientRect();
+ const speechX = this.percentToPixels(
+ containerDimensions.width,
+ annotation.x,
+ );
+ const speechY = this.percentToPixels(
+ containerDimensions.height,
+ annotation.y,
+ );
+ const speechWidth = this.percentToPixels(
+ containerDimensions.width,
+ annotation.width,
+ );
+ const speechHeight = this.percentToPixels(
+ containerDimensions.height,
+ annotation.height,
+ );
+ const speechPointX = this.percentToPixels(
+ containerDimensions.width,
+ annotation.sx,
+ );
+ const speechPointY = this.percentToPixels(
+ containerDimensions.height,
+ annotation.sy,
+ );
+ const bubbleColor = this.getFinalAnnotationColor(annotation, false);
+ const bubble = this.createSvgSpeechBubble(
+ speechX,
+ speechY,
+ speechWidth,
+ speechHeight,
+ speechPointX,
+ speechPointY,
+ bubbleColor,
+ annotation.__element,
+ );
+ bubble.style.display = "none";
+ bubble.style.overflow = "visible";
+ el.style.pointerEvents = "none";
+ bubble.__annotationEl = el;
+ annotation.__speechBubble = bubble;
+ const path = bubble.getElementsByTagName("path")[0];
+ path.addEventListener("mouseover", () => {
+ closeButton.style.display = "block";
+ closeButton.style.cursor = "pointer";
+ path.setAttribute(
+ "fill",
+ this.getFinalAnnotationColor(annotation, true),
+ );
+ });
+ path.addEventListener("mouseout", (e) => {
+ if (
+ !e.relatedTarget.classList.contains("__cxt-ar-annotation-close__")
+ ) {
+ closeButton.style.display = "none";
+ closeButton.style.cursor = "default";
+ path.setAttribute(
+ "fill",
+ this.getFinalAnnotationColor(annotation, false),
+ );
+ }
+ });
+ closeButton.addEventListener("mouseleave", () => {
+ closeButton.style.display = "none";
+ path.style.cursor = "default";
+ closeButton.style.cursor = "default";
+ path.setAttribute(
+ "fill",
+ this.getFinalAnnotationColor(annotation, false),
+ );
+ });
+ el.prepend(bubble);
+ } else if (annotation.type === "highlight") {
+ el.style.backgroundColor = "";
+ el.style.border = `2.5px solid ${this.getFinalAnnotationColor(annotation, false)}`;
+ if (annotation.actionType === "url") el.style.cursor = "pointer";
+ } else if (annotation.style !== "title") {
+ el.style.backgroundColor = this.getFinalAnnotationColor(annotation);
+ el.addEventListener("mouseenter", () => {
+ el.style.backgroundColor = this.getFinalAnnotationColor(
+ annotation,
+ true,
+ );
+ });
+ el.addEventListener("mouseleave", () => {
+ el.style.backgroundColor = this.getFinalAnnotationColor(
+ annotation,
+ false,
+ );
+ });
+ if (annotation.actionType === "url") el.style.cursor = "pointer";
+ }
+ el.style.color = `#${this.decimalToHex(annotation.fgColor)}`;
+ el.setAttribute("data-ar-type", annotation.type);
+ el.setAttribute("hidden", "");
+ this.annotationsContainer.append(el);
+ }
+ }
+ createCloseElement() {
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.setAttribute("viewBox", "0 0 100 100");
+ svg.classList.add("__cxt-ar-annotation-close__");
+ const path = document.createElementNS(svg.namespaceURI, "path");
+ path.setAttribute("d", "M25 25 L 75 75 M 75 25 L 25 75");
+ path.setAttribute("stroke", "#bbb");
+ path.setAttribute("stroke-width", 10);
+ path.setAttribute("x", 5);
+ path.setAttribute("y", 5);
+ const circle = document.createElementNS(svg.namespaceURI, "circle");
+ circle.setAttribute("cx", 50);
+ circle.setAttribute("cy", 50);
+ circle.setAttribute("r", 50);
+ svg.append(circle, path);
+ return svg;
+ }
+ createSvgSpeechBubble(
+ x,
+ y,
+ width,
+ height,
+ pointX,
+ pointY,
+ color = "white",
+ element,
+ svg,
+ ) {
+ const horizontalBaseStartMultiplier = 0.17379070765180116;
+ const horizontalBaseEndMultiplier = 0.14896346370154384;
+ const verticalBaseStartMultiplier = 0.12;
+ const verticalBaseEndMultiplier = 0.3;
+ let path;
+ if (!svg) {
+ svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.classList.add("__cxt-ar-annotation-speech-bubble__");
+ path = document.createElementNS("http://www.w3.org/2000/svg", "path");
+ path.setAttribute("fill", color);
+ svg.append(path);
+ } else {
+ path = svg.children[0];
+ }
+ svg.style.position = "absolute";
+ svg.setAttribute("width", "100%");
+ svg.setAttribute("height", "100%");
+ svg.style.left = "0";
+ svg.style.top = "0";
+ let positionStart;
+ let baseStartX = 0;
+ let baseStartY = 0;
+ let baseEndX = 0;
+ let baseEndY = 0;
+ let pointFinalX = pointX;
+ let pointFinalY = pointY;
+ let commentRectPath;
+ const pospad = 20;
+ let textWidth = 0;
+ let textHeight = 0;
+ let textX = 0;
+ let textY = 0;
+ let textElement;
+ let closeElement;
+ if (element) {
+ textElement = element.getElementsByTagName("span")[0];
+ closeElement = element.getElementsByClassName(
+ "__cxt-ar-annotation-close__",
+ )[0];
+ }
+ if (pointX > x + width - width / 2 && pointY > y + height) {
+ positionStart = "br";
+ baseStartX = width - width * horizontalBaseStartMultiplier * 2;
+ baseEndX = baseStartX + width * horizontalBaseEndMultiplier;
+ baseStartY = height;
+ baseEndY = height;
+ pointFinalX = pointX - x;
+ pointFinalY = pointY - y;
+ element.style.height = pointY - y;
+ commentRectPath = `L${width} ${height} L${width} 0 L0 0 L0 ${baseStartY} L${baseStartX} ${baseStartY}`;
+ if (textElement) {
+ textWidth = width;
+ textHeight = height;
+ textX = 0;
+ textY = 0;
+ }
+ } else if (pointX < x + width - width / 2 && pointY > y + height) {
+ positionStart = "bl";
+ baseStartX = width * horizontalBaseStartMultiplier;
+ baseEndX = baseStartX + width * horizontalBaseEndMultiplier;
+ baseStartY = height;
+ baseEndY = height;
+ pointFinalX = pointX - x;
+ pointFinalY = pointY - y;
+ element.style.height = `${pointY - y}px`;
+ commentRectPath = `L${width} ${height} L${width} 0 L0 0 L0 ${baseStartY} L${baseStartX} ${baseStartY}`;
+ if (textElement) {
+ textWidth = width;
+ textHeight = height;
+ textX = 0;
+ textY = 0;
+ }
+ } else if (pointX > x + width - width / 2 && pointY < y - pospad) {
+ positionStart = "tr";
+ baseStartX = width - width * horizontalBaseStartMultiplier * 2;
+ baseEndX = baseStartX + width * horizontalBaseEndMultiplier;
+ const yOffset = y - pointY;
+ baseStartY = yOffset;
+ baseEndY = yOffset;
+ element.style.top = y - yOffset + "px";
+ element.style.height = height + yOffset + "px";
+ pointFinalX = pointX - x;
+ pointFinalY = 0;
+ commentRectPath = `L${width} ${yOffset} L${width} ${height + yOffset} L0 ${height + yOffset} L0 ${yOffset} L${baseStartX} ${baseStartY}`;
+ if (textElement) {
+ textWidth = width;
+ textHeight = height;
+ textX = 0;
+ textY = yOffset;
+ }
+ } else if (pointX < x + width - width / 2 && pointY < y) {
+ positionStart = "tl";
+ baseStartX = width * horizontalBaseStartMultiplier;
+ baseEndX = baseStartX + width * horizontalBaseEndMultiplier;
+ const yOffset = y - pointY;
+ baseStartY = yOffset;
+ baseEndY = yOffset;
+ element.style.top = y - yOffset + "px";
+ element.style.height = height + yOffset + "px";
+ pointFinalX = pointX - x;
+ pointFinalY = 0;
+ commentRectPath = `L${width} ${yOffset} L${width} ${height + yOffset} L0 ${height + yOffset} L0 ${yOffset} L${baseStartX} ${baseStartY}`;
+ if (textElement) {
+ textWidth = width;
+ textHeight = height;
+ textX = 0;
+ textY = yOffset;
+ }
+ } else if (
+ pointX > x + width &&
+ pointY > y - pospad &&
+ pointY < y + height - pospad
+ ) {
+ positionStart = "r";
+ const xOffset = pointX - (x + width);
+ baseStartX = width;
+ baseEndX = width;
+ element.style.width = width + xOffset + "px";
+ baseStartY = height * verticalBaseStartMultiplier;
+ baseEndY = baseStartY + height * verticalBaseEndMultiplier;
+ pointFinalX = width + xOffset;
+ pointFinalY = pointY - y;
+ commentRectPath = `L${baseStartX} ${height} L0 ${height} L0 0 L${baseStartX} 0 L${baseStartX} ${baseStartY}`;
+ if (textElement) {
+ textWidth = width;
+ textHeight = height;
+ textX = 0;
+ textY = 0;
+ }
+ } else if (pointX < x && pointY > y && pointY < y + height) {
+ positionStart = "l";
+ const xOffset = x - pointX;
+ baseStartX = xOffset;
+ baseEndX = xOffset;
+ element.style.left = x - xOffset + "px";
+ element.style.width = width + xOffset + "px";
+ baseStartY = height * verticalBaseStartMultiplier;
+ baseEndY = baseStartY + height * verticalBaseEndMultiplier;
+ pointFinalX = 0;
+ pointFinalY = pointY - y;
+ commentRectPath = `L${baseStartX} ${height} L${width + baseStartX} ${height} L${width + baseStartX} 0 L${baseStartX} 0 L${baseStartX} ${baseStartY}`;
+ if (textElement) {
+ textWidth = width;
+ textHeight = height;
+ textX = xOffset;
+ textY = 0;
+ }
+ } else {
+ return svg;
+ }
+ if (textElement) {
+ textElement.style.left = textX + "px";
+ textElement.style.top = textY + "px";
+ textElement.style.width = textWidth + "px";
+ textElement.style.height = textHeight + "px";
+ }
+ if (closeElement) {
+ const closeSize = parseFloat(
+ this.annotationsContainer.style.getPropertyValue(
+ "--annotation-close-size",
+ ),
+ 10,
+ );
+ if (closeSize) {
+ closeElement.style.left = textX + textWidth + closeSize / -1.8 + "px";
+ closeElement.style.top = textY + closeSize / -1.8 + "px";
+ }
+ }
+ const pathData = `M${baseStartX} ${baseStartY} L${pointFinalX} ${pointFinalY} L${baseEndX} ${baseEndY} ${commentRectPath}`;
+ path.setAttribute("d", pathData);
+ return svg;
+ }
+ getFinalAnnotationColor(annotation, hover = false) {
+ const alphaHex = hover
+ ? (230).toString(16)
+ : Math.floor(annotation.bgOpacity * 255).toString(16);
+ if (!isNaN(annotation.bgColor)) {
+ const bgColorHex = this.decimalToHex(annotation.bgColor);
+ const backgroundColor = `#${bgColorHex}${alphaHex}`;
+ return backgroundColor;
+ }
+ }
+ removeAnnotationElements() {
+ for (const annotation of this.annotations) {
+ annotation.__element.remove();
+ }
+ }
+ update(videoTime) {
+ for (const annotation of this.annotations) {
+ const el = annotation.__element;
+ if (el.hasAttribute("data-ar-closed")) continue;
+ const start = annotation.timeStart;
+ const end = annotation.timeEnd;
+ if (el.hasAttribute("hidden") && videoTime >= start && videoTime < end) {
+ el.removeAttribute("hidden");
+ if (annotation.style === "speech" && annotation.__speechBubble) {
+ annotation.__speechBubble.style.display = "block";
+ }
+ } else if (
+ !el.hasAttribute("hidden") &&
+ (videoTime < start || videoTime > end)
+ ) {
+ el.setAttribute("hidden", "");
+ if (annotation.style === "speech" && annotation.__speechBubble) {
+ annotation.__speechBubble.style.display = "none";
+ }
+ }
+ }
+ }
+ start() {
+ if (!this.playerOptions)
+ throw new Error("playerOptions must be provided to use the start method");
+ const videoTime = this.playerOptions.getVideoTime();
+ if (!this.updateIntervalId) {
+ this.update(videoTime);
+ this.updateIntervalId = setInterval(() => {
+ const videoTime = this.playerOptions.getVideoTime();
+ this.update(videoTime);
+ window.dispatchEvent(new CustomEvent("__ar_renderer_start"));
+ }, this.updateInterval);
+ }
+ }
+ stop() {
+ if (!this.playerOptions)
+ throw new Error("playerOptions must be provided to use the stop method");
+ const videoTime = this.playerOptions.getVideoTime();
+ if (this.updateIntervalId) {
+ this.update(videoTime);
+ clearInterval(this.updateIntervalId);
+ this.updateIntervalId = null;
+ window.dispatchEvent(new CustomEvent("__ar_renderer_stop"));
+ }
+ }
+ updateAnnotationTextSize(annotation, containerHeight) {
+ if (annotation.textSize) {
+ const textSize = (annotation.textSize / 100) * containerHeight;
+ annotation.__element.style.fontSize = `${textSize}px`;
+ }
+ }
+ updateTextSize() {
+ const containerHeight = this.container.getBoundingClientRect().height;
+ for (const annotation of this.annotations) {
+ this.updateAnnotationTextSize(annotation, containerHeight);
+ }
+ }
+ updateCloseSize(containerHeight) {
+ if (!containerHeight)
+ containerHeight = this.container.getBoundingClientRect().height;
+ const multiplier = 0.0423;
+ this.annotationsContainer.style.setProperty(
+ "--annotation-close-size",
+ `${containerHeight * multiplier}px`,
+ );
+ }
+ updateAnnotationDimensions(annotations, videoWidth, videoHeight) {
+ const playerWidth = this.container.getBoundingClientRect().width;
+ const playerHeight = this.container.getBoundingClientRect().height;
+ const widthDivider = playerWidth / videoWidth;
+ const heightDivider = playerHeight / videoHeight;
+ let scaledVideoWidth = playerWidth;
+ let scaledVideoHeight = playerHeight;
+ if (widthDivider % 1 !== 0 || heightDivider % 1 !== 0) {
+ if (widthDivider > heightDivider) {
+ scaledVideoWidth = (playerHeight / videoHeight) * videoWidth;
+ scaledVideoHeight = playerHeight;
+ } else if (heightDivider > widthDivider) {
+ scaledVideoWidth = playerWidth;
+ scaledVideoHeight = (playerWidth / videoWidth) * videoHeight;
+ }
+ }
+ const verticalBlackBarWidth = (playerWidth - scaledVideoWidth) / 2;
+ const horizontalBlackBarHeight = (playerHeight - scaledVideoHeight) / 2;
+ const widthOffsetPercent = (verticalBlackBarWidth / playerWidth) * 100;
+ const heightOffsetPercent = (horizontalBlackBarHeight / playerHeight) * 100;
+ const widthMultiplier = scaledVideoWidth / playerWidth;
+ const heightMultiplier = scaledVideoHeight / playerHeight;
+ for (const annotation of annotations) {
+ const el = annotation.__element;
+ let ax = widthOffsetPercent + annotation.x * widthMultiplier;
+ let ay = heightOffsetPercent + annotation.y * heightMultiplier;
+ let aw = annotation.width * widthMultiplier;
+ let ah = annotation.height * heightMultiplier;
+ el.style.left = `${ax}%`;
+ el.style.top = `${ay}%`;
+ el.style.width = `${aw}%`;
+ el.style.height = `${ah}%`;
+ let horizontalPadding = scaledVideoWidth * 0.008;
+ let verticalPadding = scaledVideoHeight * 0.008;
+ if (annotation.style === "speech" && annotation.text) {
+ const pel = annotation.__element.getElementsByTagName("span")[0];
+ horizontalPadding *= 2;
+ verticalPadding *= 2;
+ pel.style.paddingLeft = horizontalPadding + "px";
+ pel.style.paddingRight = horizontalPadding + "px";
+ pel.style.paddingBottom = verticalPadding + "px";
+ pel.style.paddingTop = verticalPadding + "px";
+ } else if (annotation.style !== "speech") {
+ el.style.paddingLeft = horizontalPadding + "px";
+ el.style.paddingRight = horizontalPadding + "px";
+ el.style.paddingBottom = verticalPadding + "px";
+ el.style.paddingTop = verticalPadding + "px";
+ }
+ if (annotation.__speechBubble) {
+ const asx = this.percentToPixels(playerWidth, ax);
+ const asy = this.percentToPixels(playerHeight, ay);
+ const asw = this.percentToPixels(playerWidth, aw);
+ const ash = this.percentToPixels(playerHeight, ah);
+ let sx = widthOffsetPercent + annotation.sx * widthMultiplier;
+ let sy = heightOffsetPercent + annotation.sy * heightMultiplier;
+ sx = this.percentToPixels(playerWidth, sx);
+ sy = this.percentToPixels(playerHeight, sy);
+ this.createSvgSpeechBubble(
+ asx,
+ asy,
+ asw,
+ ash,
+ sx,
+ sy,
+ null,
+ annotation.__element,
+ annotation.__speechBubble,
+ );
+ }
+ this.updateAnnotationTextSize(annotation, scaledVideoHeight);
+ this.updateCloseSize(scaledVideoHeight);
+ }
+ }
+ updateAllAnnotationSizes() {
+ if (
+ this.playerOptions &&
+ this.playerOptions.getOriginalVideoWidth &&
+ this.playerOptions.getOriginalVideoHeight
+ ) {
+ const videoWidth = this.playerOptions.getOriginalVideoWidth();
+ const videoHeight = this.playerOptions.getOriginalVideoHeight();
+ this.updateAnnotationDimensions(
+ this.annotations,
+ videoWidth,
+ videoHeight,
+ );
+ } else {
+ const playerWidth = this.container.getBoundingClientRect().width;
+ const playerHeight = this.container.getBoundingClientRect().height;
+ this.updateAnnotationDimensions(
+ this.annotations,
+ playerWidth,
+ playerHeight,
+ );
+ }
+ }
+ hideAll() {
+ for (const annotation of this.annotations) {
+ annotation.__element.setAttribute("hidden", "");
+ }
+ }
+ annotationClickHandler(e) {
+ let annotationElement = e.target;
+ if (
+ !annotationElement.matches(".__cxt-ar-annotation__") &&
+ !annotationElement.closest(".__cxt-ar-annotation-close__")
+ ) {
+ annotationElement = annotationElement.closest(".__cxt-ar-annotation__");
+ if (!annotationElement) return null;
+ }
+ let annotationData = annotationElement.__annotation;
+ if (!annotationElement || !annotationData) return;
+ if (annotationData.actionType === "time") {
+ const seconds = annotationData.actionSeconds;
+ if (this.playerOptions) {
+ this.playerOptions.seekTo(seconds);
+ const videoTime = this.playerOptions.getVideoTime();
+ this.update(videoTime);
+ }
+ window.dispatchEvent(
+ new CustomEvent("__ar_seek_to", { detail: { seconds: seconds } }),
+ );
+ } else if (annotationData.actionType === "url") {
+ const data = {
+ url: annotationData.actionUrl,
+ target: annotationData.actionUrlTarget || "current",
+ };
+ const timeHash = this.extractTimeHash(new URL(data.url));
+ if (timeHash && timeHash.hasOwnProperty("seconds")) {
+ data.seconds = timeHash.seconds;
+ }
+ window.dispatchEvent(
+ new CustomEvent("__ar_annotation_click", { detail: data }),
+ );
+ }
+ }
+ setUpdateInterval(ms) {
+ this.updateInterval = ms;
+ this.stop();
+ this.start();
+ }
+ decimalToHex(dec) {
+ let hex = dec.toString(16);
+ hex = "000000".substr(0, 6 - hex.length) + hex;
+ return hex;
+ }
+ extractTimeHash(url) {
+ if (!url) throw new Error("A URL must be provided");
+ const hash = url.hash;
+ if (hash && hash.startsWith("#t=")) {
+ const timeString = url.hash.split("#t=")[1];
+ const seconds = this.timeStringToSeconds(timeString);
+ return { seconds: seconds };
+ } else {
+ return false;
+ }
+ }
+ timeStringToSeconds(time) {
+ let seconds = 0;
+ const h = time.split("h");
+ const m = (h[1] || time).split("m");
+ const s = (m[1] || time).split("s");
+ if (h[0] && h.length === 2) seconds += parseInt(h[0], 10) * 60 * 60;
+ if (m[0] && m.length === 2) seconds += parseInt(m[0], 10) * 60;
+ if (s[0] && s.length === 2) seconds += parseInt(s[0], 10);
+ return seconds;
+ }
+ percentToPixels(a, b) {
+ return (a * b) / 100;
+ }
+}
+function youtubeAnnotationsPlugin(options) {
+ if (!options.annotationXml)
+ throw new Error("Annotation data must be provided");
+ if (!options.videoContainer)
+ throw new Error(
+ "A video container to overlay the data on must be provided",
+ );
+ const player = this;
+ const xml = options.annotationXml;
+ const parser = new AnnotationParser();
+ const annotationElements = parser.getAnnotationsFromXml(xml);
+ const annotations = parser.parseYoutubeAnnotationList(annotationElements);
+ const videoContainer = options.videoContainer;
+ const playerOptions = {
+ getVideoTime() {
+ return player.currentTime();
+ },
+ seekTo(seconds) {
+ player.currentTime(seconds);
+ },
+ getOriginalVideoWidth() {
+ return player.videoWidth();
+ },
+ getOriginalVideoHeight() {
+ return player.videoHeight();
+ },
+ };
+ raiseControls();
+ const renderer = new AnnotationRenderer(
+ annotations,
+ videoContainer,
+ playerOptions,
+ options.updateInterval,
+ );
+ setupEventListeners(player, renderer);
+ renderer.start();
+}
+function setupEventListeners(player, renderer) {
+ if (!player) throw new Error("A video player must be provided");
+ player.on("playerresize", (e) => {
+ renderer.updateAllAnnotationSizes(renderer.annotations);
+ });
+ player.one("loadedmetadata", (e) => {
+ renderer.updateAllAnnotationSizes(renderer.annotations);
+ });
+ player.on("pause", (e) => {
+ renderer.stop();
+ });
+ player.on("play", (e) => {
+ renderer.start();
+ });
+ player.on("seeking", (e) => {
+ renderer.update();
+ });
+ player.on("seeked", (e) => {
+ renderer.update();
+ });
+}
+function raiseControls() {
+ const styles = document.createElement("style");
+ styles.textContent = `\n\t.vjs-control-bar {\n\t\tz-index: 21;\n\t}\n\t`;
+ document.body.append(styles);
+}
diff --git a/assets/js/watch.js b/assets/js/watch.js
index 75ec99a3..d8fc61f3 100644
--- a/assets/js/watch.js
+++ b/assets/js/watch.js
@@ -1,135 +1,159 @@
-'use strict';
+"use strict";
function toggle_parent(target) {
- var body = target.parentNode.parentNode.children[1];
- if (body.style.display === 'none') {
- target.textContent = '[ − ]';
- body.style.display = '';
- } else {
- target.textContent = '[ + ]';
- body.style.display = 'none';
- }
+ var body = target.parentNode.parentNode.children[1];
+ if (body.style.display === "none") {
+ target.textContent = "[ − ]";
+ body.style.display = "";
+ } else {
+ target.textContent = "[ + ]";
+ body.style.display = "none";
+ }
}
function swap_comments(event) {
- var source = event.target.getAttribute('data-comments');
+ var source = event.target.getAttribute("data-comments");
- if (source === 'youtube') {
- get_youtube_comments();
- } else if (source === 'reddit') {
- get_reddit_comments();
- }
+ if (source === "youtube") {
+ get_youtube_comments();
+ } else if (source === "reddit") {
+ get_reddit_comments();
+ }
}
-var continue_button = document.getElementById('continue');
+var continue_button = document.getElementById("continue");
if (continue_button) {
- continue_button.onclick = continue_autoplay;
+ continue_button.onclick = continue_autoplay;
}
function next_video() {
- var url = new URL('https://example.com/watch?v=' + video_data.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)
- 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);
- url.searchParams.set('continue', '1');
+ 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);
+ url.searchParams.set("continue", "1");
- location.assign(url.pathname + url.search);
+ location.assign(url.pathname + url.search);
}
function continue_autoplay(event) {
- if (event.target.checked) {
- player.on('ended', next_video);
- } else {
- player.off('ended');
- }
+ if (event.target.checked) {
+ player.on("ended", next_video);
+ } else {
+ player.off("ended");
+ }
}
function get_playlist(plid) {
- var playlist = document.getElementById('playlist');
+ var playlist = document.getElementById("playlist");
- playlist.innerHTML = spinnerHTMLwithHR;
+ playlist.innerHTML = spinnerHTMLwithHR;
- var plid_url;
- if (plid.startsWith('RD')) {
- plid_url = '/api/v1/mixes/' + plid +
- '?continuation=' + video_data.id +
- '&format=html&hl=' + video_data.preferences.locale;
- } else {
- plid_url = '/api/v1/playlists/' + plid +
- '?index=' + video_data.index +
- '&continuation=' + video_data.id +
- '&format=html&hl=' + video_data.preferences.locale;
- }
+ var plid_url;
+ if (plid.startsWith("RD")) {
+ plid_url =
+ "/api/v1/mixes/" +
+ plid +
+ "?continuation=" +
+ video_data.id +
+ "&format=html&hl=" +
+ video_data.preferences.locale;
+ } else {
+ plid_url =
+ "/api/v1/playlists/" +
+ plid +
+ "?index=" +
+ video_data.index +
+ "&continuation=" +
+ video_data.id +
+ "&format=html&hl=" +
+ video_data.preferences.locale;
+ }
- if (video_data.params.listen) {
- plid_url += '&listen=1'
- }
+ if (video_data.params.listen) {
+ plid_url += "&listen=1";
+ }
- helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, {
- on200: function (response) {
- if (response === null) return;
+ helpers.xhr(
+ "GET",
+ plid_url,
+ { retries: 5, entity_name: "playlist" },
+ {
+ on200: function (response) {
+ if (response === null) return;
- playlist.innerHTML = response.playlistHtml;
+ playlist.innerHTML = response.playlistHtml;
- if (!response.nextVideo) return;
+ if (!response.nextVideo) return;
- var nextVideo = document.getElementById(response.nextVideo);
- nextVideo.parentNode.parentNode.scrollTop = nextVideo.offsetTop;
+ var nextVideo = document.getElementById(response.nextVideo);
+ nextVideo.parentNode.parentNode.scrollTop = nextVideo.offsetTop;
- player.on('ended', function () {
- var url = new URL('https://example.com/watch?v=' + response.nextVideo);
+ player.on("ended", function () {
+ var url = new URL(
+ "https://example.com/watch?v=" + response.nextVideo,
+ );
- 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);
+ 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);
- 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;
- }
- });
+ 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;
+ },
+ },
+ );
}
function get_reddit_comments() {
- var comments = document.getElementById('comments');
+ var comments = document.getElementById("comments");
- var fallback = comments.innerHTML;
- comments.innerHTML = spinnerHTML;
+ var fallback = comments.innerHTML;
+ comments.innerHTML = spinnerHTML;
- var url = '/api/v1/comments/' + video_data.id +
- '?source=reddit&format=html' +
- '&hl=' + video_data.preferences.locale;
+ var url =
+ "/api/v1/comments/" +
+ video_data.id +
+ "?source=reddit&format=html" +
+ "&hl=" +
+ video_data.preferences.locale;
- var onNon200 = function (xhr) { comments.innerHTML = fallback; };
- if (video_data.params.comments[1] === 'youtube')
- onNon200 = function (xhr) {};
+ 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 = ' \
+ helpers.xhr(
+ "GET",
+ url,
+ { retries: 5, entity_name: "" },
+ {
+ on200: function (response) {
+ comments.innerHTML = ' \
\
\
[ − ] \
@@ -148,52 +172,52 @@ function get_reddit_comments() {
\
{contentHtml}
\
'.supplant({
- title: response.title,
- youtubeCommentsText: video_data.youtube_comments_text,
- redditPermalinkText: video_data.reddit_permalink_text,
- permalink: response.permalink,
- contentHtml: response.contentHtml
- });
+ 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;
- },
- onNon200: onNon200, // declared above
- });
+ comments.children[0].children[0].children[0].onclick = toggle_comments;
+ comments.children[0].children[1].children[0].onclick = swap_comments;
+ },
+ onNon200: onNon200, // declared above
+ },
+ );
}
if (video_data.play_next) {
- player.on('ended', function () {
- var url = new URL('https://example.com/watch?v=' + video_data.next_video);
+ 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)
- 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);
- url.searchParams.set('continue', '1');
+ 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);
+ url.searchParams.set("continue", "1");
- location.assign(url.pathname + url.search);
- });
+ location.assign(url.pathname + url.search);
+ });
}
-addEventListener('load', function (e) {
- if (video_data.plid)
- get_playlist(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();
- } else if (video_data.params.comments[0] === 'reddit') {
- get_reddit_comments();
- } else if (video_data.params.comments[1] === 'youtube') {
- get_youtube_comments();
- } else if (video_data.params.comments[1] === 'reddit') {
- get_reddit_comments();
- } else {
- var comments = document.getElementById('comments');
- comments.innerHTML = '';
- }
+ if (video_data.params.comments[0] === "youtube") {
+ get_youtube_comments();
+ } else if (video_data.params.comments[0] === "reddit") {
+ get_reddit_comments();
+ } else if (video_data.params.comments[1] === "youtube") {
+ get_youtube_comments();
+ } else if (video_data.params.comments[1] === "reddit") {
+ get_reddit_comments();
+ } else {
+ var comments = document.getElementById("comments");
+ comments.innerHTML = "";
+ }
});
diff --git a/assets/js/watched_indicator.js b/assets/js/watched_indicator.js
index e971cd80..d598e35e 100644
--- a/assets/js/watched_indicator.js
+++ b/assets/js/watched_indicator.js
@@ -1,24 +1,24 @@
-'use strict';
-var save_player_pos_key = 'save_player_pos';
+"use strict";
+var save_player_pos_key = "save_player_pos";
function get_all_video_times() {
- return helpers.storage.get(save_player_pos_key) || {};
+ return helpers.storage.get(save_player_pos_key) || {};
}
-document.querySelectorAll('.watched-indicator').forEach(function (indicator) {
- var watched_part = get_all_video_times()[indicator.dataset.id];
- var total = parseInt(indicator.dataset.length, 10);
- if (watched_part === undefined) {
- watched_part = total;
- }
- var percentage = Math.round((watched_part / total) * 100);
+document.querySelectorAll(".watched-indicator").forEach(function (indicator) {
+ var watched_part = get_all_video_times()[indicator.dataset.id];
+ var total = parseInt(indicator.dataset.length, 10);
+ if (watched_part === undefined) {
+ watched_part = total;
+ }
+ var percentage = Math.round((watched_part / total) * 100);
- if (percentage < 5) {
- percentage = 5;
- }
- if (percentage > 90) {
- percentage = 100;
- }
+ if (percentage < 5) {
+ percentage = 5;
+ }
+ if (percentage > 90) {
+ percentage = 100;
+ }
- indicator.style.width = percentage + '%';
+ indicator.style.width = percentage + "%";
});
diff --git a/assets/js/watched_widget.js b/assets/js/watched_widget.js
index 06af62cc..4ce4ba07 100644
--- a/assets/js/watched_widget.js
+++ b/assets/js/watched_widget.js
@@ -1,34 +1,50 @@
-'use strict';
-var watched_data = JSON.parse(document.getElementById('watched_data').textContent);
-var payload = 'csrf_token=' + watched_data.csrf_token;
+"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;
- tile.style.display = 'none';
+ var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
+ tile.style.display = "none";
- var url = '/watch_ajax?action=mark_watched&redirect=false' +
- '&id=' + target.getAttribute('data-id');
+ var url =
+ "/watch_ajax?action=mark_watched&redirect=false" +
+ "&id=" +
+ target.getAttribute("data-id");
- helpers.xhr('POST', url, {payload: payload}, {
- onNon200: function (xhr) {
- tile.style.display = '';
- }
- });
+ helpers.xhr(
+ "POST",
+ url,
+ { payload: payload },
+ {
+ onNon200: function (xhr) {
+ tile.style.display = "";
+ },
+ },
+ );
}
function mark_unwatched(target) {
- var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
- tile.style.display = 'none';
- var count = document.getElementById('count');
- count.textContent--;
+ var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
+ tile.style.display = "none";
+ var count = document.getElementById("count");
+ count.textContent--;
- var url = '/watch_ajax?action=mark_unwatched&redirect=false' +
- '&id=' + target.getAttribute('data-id');
+ var url =
+ "/watch_ajax?action=mark_unwatched&redirect=false" +
+ "&id=" +
+ target.getAttribute("data-id");
- helpers.xhr('POST', url, {payload: payload}, {
- onNon200: function (xhr) {
- count.textContent++;
- tile.style.display = '';
- }
- });
+ helpers.xhr(
+ "POST",
+ url,
+ { payload: payload },
+ {
+ onNon200: function (xhr) {
+ count.textContent++;
+ tile.style.display = "";
+ },
+ },
+ );
}
diff --git a/src/invidious/frontend/pagination.cr b/src/invidious/frontend/pagination.cr
index e75d38a5..cf78c4d0 100644
--- a/src/invidious/frontend/pagination.cr
+++ b/src/invidious/frontend/pagination.cr
@@ -89,13 +89,6 @@ module Invidious::Frontend::Pagination
end
def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?, first_page : Bool, params : URI::Params)
- return String.build do |str|
- if !ctoken.nil?
- str << %(\n)
end
end
end