").text(entry.bannedby).appendTo(tr);
tr.attr("title", "Ban Reason: " + entry.reason);
return tr;
};
flat.forEach(function (person) {
var bans = person.bans;
var name = person.name;
var first = addBanRow(bans.shift());
if (bans.length > 0) {
var showmore = $("").addClass("btn btn-xs btn-default pull-right");
$("").addClass("glyphicon glyphicon-list").appendTo(showmore);
showmore.appendTo(first.find("td")[1]);
showmore.click(function () {
if (showmore.data("elems")) {
showmore.data("elems").forEach(function (e) {
e.remove();
});
showmore.data("elems", null);
} else {
var elems = [];
bans.forEach(function (b) {
elems.push(addBanRow(b, first));
});
showmore.data("elems", elems);
}
});
}
});
}
function checkEntitiesInStr(str) {
var entities = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
"\\(": "(",
"\\)": ")"
};
var m = str.match(/([&<>"'])|(\\\()|(\\\))/);
if (m && m[1] in entities) {
return { src: m[1].replace(/^\\/, ""), replace: entities[m[1]] };
} else {
return false;
}
}
function formatCSChatFilterList() {
var tbl = $("#cs-chatfilters table");
tbl.find("tbody").remove();
tbl.find(".ui-sortable").remove();
var entries = tbl.data("entries") || [];
entries.forEach(function (f) {
var tr = $("
").appendTo(tbl);
var controlgroup = $("").addClass("btn-group")
.appendTo($("
").appendTo(tr));
var control = $("").addClass("btn btn-xs btn-default")
.attr("title", "Edit this filter")
.appendTo(controlgroup);
$("").addClass("glyphicon glyphicon-list").appendTo(control);
var del = $("").addClass("btn btn-xs btn-danger")
.appendTo(controlgroup);
$("").addClass("glyphicon glyphicon-trash").appendTo(del);
del.click(function () {
socket.emit("removeFilter", f);
});
var name = $("").text(f.name).appendTo($("
").appendTo(tr));
var activetd = $("
").appendTo(tr);
var active = $("").attr("type", "checkbox")
.prop("checked", f.active)
.appendTo(activetd)
.change(function () {
f.active = $(this).prop("checked");
socket.emit("updateFilter", f);
});
var reset = function () {
control.data("editor") && control.data("editor").remove();
control.data("editor", null);
control.parent().find(".btn-success").remove();
var tbody = $(tbl.children()[1]);
if (tbody.find(".filter-edit-row").length === 0) {
tbody.sortable("enable");
}
};
control.click(function () {
if (control.data("editor")) {
return reset();
}
$(tbl.children()[1]).sortable("disable");
var tr2 = $("
").insertAfter(tr).addClass("filter-edit-row");
var wrap = $("
").attr("colspan", "3").appendTo(tr2);
var form = $("").addClass("form-inline").attr("role", "form")
.attr("action", "javascript:void(0)")
.appendTo(wrap);
var addTextbox = function (placeholder) {
var div = $("").addClass("form-group").appendTo(form)
.css("margin-right", "10px");
var input = $("").addClass("form-control")
.attr("type", "text")
.attr("placeholder", placeholder)
.attr("title", placeholder)
.appendTo(div);
return input;
};
var regex = addTextbox("Filter regex").val(f.source);
var flags = addTextbox("Regex flags").val(f.flags);
var replace = addTextbox("Replacement text").val(f.replace);
var checkwrap = $("").addClass("checkbox").appendTo(form);
var checklbl = $("").text("Filter Links").appendTo(checkwrap);
var filterlinks = $("").attr("type", "checkbox")
.prependTo(checklbl)
.prop("checked", f.filterlinks);
var save = $("").addClass("btn btn-xs btn-success")
.attr("title", "Save changes")
.insertAfter(control);
$("").addClass("glyphicon glyphicon-floppy-save").appendTo(save);
save.click(function () {
f.source = regex.val();
var entcheck = checkEntitiesInStr(f.source);
if (entcheck) {
alert("Warning: " + entcheck.src + " will be replaced by " +
entcheck.replace + " in the message preprocessor. This " +
"regular expression may not match what you intended it to " +
"match.");
}
f.flags = flags.val();
f.replace = replace.val();
f.filterlinks = filterlinks.prop("checked");
socket.emit("updateFilter", f);
socket.once("updateFilterSuccess", function () {
reset();
});
});
control.data("editor", tr2);
});
});
$(tbl.children()[1]).sortable({
start: function(ev, ui) {
FILTER_FROM = ui.item.prevAll().length;
},
update: function(ev, ui) {
FILTER_TO = ui.item.prevAll().length;
if(FILTER_TO != FILTER_FROM) {
socket.emit("moveFilter", {
from: FILTER_FROM,
to: FILTER_TO
});
}
}
});
}
function formatTime(sec) {
var h = Math.floor(sec / 3600) + "";
var m = Math.floor((sec % 3600) / 60) + "";
var s = sec % 60 + "";
if (h.length < 2) {
h = "0" + h;
}
if (m.length < 2) {
m = "0" + m;
}
if (s.length < 2) {
s = "0" + s;
}
if (h === "00") {
return [m, s].join(":");
} else {
return [h, m, s].join(":");
}
}
function formatUserPlaylistList() {
var list = $("#userpl_list").data("entries") || [];
list.sort(function (a, b) {
var x = a.name.toLowerCase();
var y = b.name.toLowerCase();
return x == y ? 0 : (x < y ? -1 : 1);
});
$("#userpl_list").html("");
list.forEach(function (pl) {
var li = $("").addClass("queue_entry").appendTo($("#userpl_list"));
var title = $("").addClass("qe_title").appendTo(li)
.text(pl.name);
var time = $("").addClass("pull-right").appendTo(li)
.text(pl.count + " items, playtime " + formatTime(pl.duration));
var clear = $("").addClass("qe_clear").appendTo(li);
var btns = $("").addClass("btn-group pull-left").prependTo(li);
if (hasPermission("playlistadd")) {
$("").addClass("btn btn-xs btn-default")
.text("End")
.appendTo(btns)
.click(function () {
socket.emit("queuePlaylist", {
name: pl.name,
pos: "end",
temp: $(".add-temp").prop("checked")
});
});
}
if (hasPermission("playlistadd") && hasPermission("playlistnext")) {
$("").addClass("btn btn-xs btn-default")
.text("Next")
.prependTo(btns)
.click(function () {
socket.emit("queuePlaylist", {
name: pl.name,
pos: "next",
temp: $(".add-temp").prop("checked")
});
});
}
$("").addClass("btn btn-xs btn-danger")
.html("")
.attr("title", "Delete playlist")
.appendTo(btns)
.click(function () {
var really = confirm("Are you sure you want to delete" +
" this playlist? This cannot be undone.");
if (!really) {
return;
}
socket.emit("deletePlaylist", {
name: pl.name
});
});
});
}
function loadEmotes(data) {
function sanitizeText(str) {
str = str.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
return str;
}
CHANNEL.emotes = [];
CHANNEL.emoteMap = {};
CHANNEL.badEmotes = [];
data.forEach(function (e) {
if (e.image && e.name) {
e.regex = new RegExp(e.source, "gi");
CHANNEL.emotes.push(e);
if (/\s/g.test(e.name)) {
// Emotes with spaces can't be hashmapped
CHANNEL.badEmotes.push(e);
} else {
CHANNEL.emoteMap[sanitizeText(e.name)] = e;
}
} else {
console.error("Rejecting invalid emote: " + JSON.stringify(e));
}
});
}
function execEmotes(msg) {
if (USEROPTS.no_emotes) {
return msg;
}
if (CyTube.featureFlag && CyTube.featureFlag.efficientEmotes) {
return execEmotesEfficient(msg);
}
CHANNEL.emotes.forEach(function (e) {
msg = msg.replace(e.regex, '$1');
});
return msg;
}
function execEmotesEfficient(msg) {
CHANNEL.badEmotes.forEach(function (e) {
msg = msg.replace(e.regex, '$1');
});
msg = msg.replace(/[^\s]+/g, function (m) {
if (CHANNEL.emoteMap.hasOwnProperty(m)) {
var e = CHANNEL.emoteMap[m];
return '';
} else {
return m;
}
});
return msg;
}
function initPm(user) {
if ($("#pm-" + user).length > 0) {
return $("#pm-" + user);
}
var pm = $("").addClass("panel panel-default pm-panel")
.appendTo($("#pmbar"))
.data("last", { name: "" })
.attr("id", "pm-" + user);
var title = $("").addClass("panel-heading").text(user).appendTo(pm);
var close = $("").addClass("close pull-right")
.html("×")
.appendTo(title).click(function () {
pm.remove();
$("#pm-placeholder-" + user).remove();
});
var body = $("").addClass("panel-body").appendTo(pm).hide();
var placeholder;
title.click(function () {
body.toggle();
pm.removeClass("panel-primary").addClass("panel-default");
if (!body.is(":hidden")) {
placeholder = $("").addClass("pm-panel-placeholder")
.attr("id", "pm-placeholder-" + user)
.insertAfter(pm);
var left = pm.position().left;
pm.css("position", "absolute")
.css("bottom", "0px")
.css("left", left);
} else {
pm.css("position", "");
$("#pm-placeholder-" + user).remove();
}
});
var buffer = $("").addClass("pm-buffer linewrap").appendTo(body);
$("").appendTo(body);
var input = $("").addClass("form-control pm-input").attr("type", "text")
.attr("maxlength", 240)
.appendTo(body);
input.keydown(function (ev) {
if (ev.keyCode === 13) {
if (CHATTHROTTLE) {
return;
}
var meta = {};
var msg = input.val();
if (msg.trim() === "") {
return;
}
if (USEROPTS.modhat && CLIENT.rank >= Rank.Moderator) {
meta.modflair = CLIENT.rank;
}
if (CLIENT.rank >= 2 && msg.indexOf("/m ") === 0) {
meta.modflair = CLIENT.rank;
msg = msg.substring(3);
}
socket.emit("pm", {
to: user,
msg: msg,
meta: meta
});
input.val("");
}
});
return pm;
}
function killVideoUntilItIsDead(video) {
try {
video[0].volume = 0;
video[0].muted = true;
video.attr("src", "");
video.remove();
} catch (e) {
}
}
function fallbackRaw(data) {
$("").insertBefore($("#ytapiplayer")).attr("id", "ytapiplayer");
$("video").each(function () {
killVideoUntilItIsDead($(this));
});
$("audio").each(function () {
killVideoUntilItIsDead($(this));
});
data.type = "fl";
data.url = data.direct.sd.url;
PLAYER.player = undefined;
PLAYER = new FlashPlayer(data);
handleMediaUpdate(data);
}
function checkScriptAccess(source, type, cb) {
var pref = JSPREF[CHANNEL.name.toLowerCase() + "_" + type];
if (pref === "ALLOW") {
return cb("ALLOW");
} else if (pref !== "DENY") {
var div = $("#chanjs-allow-prompt");
if (div.length > 0) {
setTimeout(function () {
checkScriptAccess(source, type, cb);
}, 500);
return;
}
div = $("").attr("id", "chanjs-allow-prompt");
var close = $("").addClass("close pull-right")
.html("×")
.appendTo(div);
var form = $("")
.attr("action", "javascript:void(0)")
.attr("id", "chanjs-allow-prompt")
.attr("style", "text-align: center")
.appendTo(div);
form.append("This channel has special features that require your permission to run. ");
$("").attr("href", source)
.attr("target", "_blank")
.text(type === "embedded" ? "view embedded script" : source)
.appendTo(form);
form.append("
" +
"" +
"" +
"
");
form.append("");
var dialog = chatDialog(div);
close.click(function () {
dialog.remove();
/* Implicit denial of script access */
cb("DENY");
});
$("#chanjs-allow").click(function () {
var save = $("#chanjs-save-pref").is(":checked");
dialog.remove();
if (save) {
JSPREF[CHANNEL.name.toLowerCase() + "_" + type] = "ALLOW";
setOpt("channel_js_pref", JSPREF);
}
cb("ALLOW");
});
$("#chanjs-deny").click(function () {
var save = $("#chanjs-save-pref").is(":checked");
dialog.remove();
if (save) {
JSPREF[CHANNEL.name.toLowerCase() + "_" + type] = "DENY";
setOpt("channel_js_pref", JSPREF);
}
cb("DENY");
});
}
}
function formatScriptAccessPrefs() {
var tbl = $("#us-scriptcontrol table");
tbl.find("tbody").remove();
var channels = Object.keys(JSPREF).sort();
channels.forEach(function (channel) {
var idx = String(channel).lastIndexOf("_");
if (idx < 0) {
// Invalid
console.error("Channel JS pref: invalid key '" + channel + "', deleting it");
delete JSPREF[channel];
setOpt("channel_js_pref", JSPREF);
return;
}
var channelName = channel.substring(0, idx);
var prefType = channel.substring(idx + 1);
console.log(channelName, prefType);
if (prefType !== "external" && prefType !== "embedded") {
// Invalid
console.error("Channel JS pref: invalid key '" + channel + "', deleting it");
delete JSPREF[channel];
setOpt("channel_js_pref", JSPREF);
return;
}
var pref = JSPREF[channel];
var tr = $("
").appendTo(tbl);
$("
").text(channelName).appendTo(tr);
$("
").text(prefType).appendTo(tr);
var pref_td = $("
").appendTo(tr);
var allow_label = $("").addClass("radio-inline")
.text("Allow").appendTo(pref_td);
var allow = $("").attr("type", "radio")
.prop("checked", pref === "ALLOW").
prependTo(allow_label);
allow.change(function () {
if (allow.is(":checked")) {
JSPREF[channel] = "ALLOW";
setOpt("channel_js_pref", JSPREF);
deny.prop("checked", false);
}
});
var deny_label = $("").addClass("radio-inline")
.text("Deny").appendTo(pref_td);
var deny = $("").attr("type", "radio")
.prop("checked", pref === "DENY").
prependTo(deny_label);
deny.change(function () {
if (deny.is(":checked")) {
JSPREF[channel] = "DENY";
setOpt("channel_js_pref", JSPREF);
allow.prop("checked", false);
}
});
var clearpref = $("").addClass("btn btn-sm btn-danger")
.text("Clear Preference")
.appendTo($("
").appendTo(tr))
.click(function () {
delete JSPREF[channel];
setOpt("channel_js_pref", JSPREF);
tr.remove();
});
});
}
/*
VIMEO SIMULATOR 2014
Vimeo decided to block my domain. After repeated emails, they refused to
unblock it. Rather than give in to their demands, there is a serverside
option which extracts direct links to the h264 encoded MP4 video files.
These files can be loaded in a custom player to allow Vimeo playback without
triggering their dumb API domain block.
It's a little bit hacky, but my only other option is to keep buying new
domains every time one gets blocked. No thanks to Vimeo, who were of no help
and unwilling to compromise on the issue.
*/
function vimeoSimulator2014(data) {
/* Vimeo Simulator uses the raw file player */
data.type = "fi";
/* Convert youtube-style quality key to vimeo workaround quality */
var q = {
small: "mobile",
medium: "sd",
large: "sd",
hd720: "hd",
hd1080:"hd",
highres: "hd"
}[USEROPTS.default_quality] || "sd";
var fallback = {
hd: "sd",
sd: "mobile",
mobile: false
};
/* Pick highest quality less than or equal to user's preference from the options */
while (!(q in data.meta.direct) && q != false) {
q = fallback[q];
}
if (!q) {
q = "sd";
}
data.url = data.meta.direct[q].url;
return data;
}
function EmoteList(selector, emoteClickCallback) {
this.elem = $(selector);
this.initSearch();
this.initSortOption();
this.table = this.elem.find(".emotelist-table")[0];
this.paginatorContainer = this.elem.find(".emotelist-paginator-container");
this.cols = 5;
this.itemsPerPage = 25;
this.emotes = [];
this.page = 0;
this.emoteClickCallback = emoteClickCallback || function(){};
}
EmoteList.prototype.initSearch = function () {
this.searchbar = this.elem.find(".emotelist-search");
var self = this;
this.searchbar.keyup(function () {
var value = this.value.toLowerCase();
if (value) {
self.filter = function (emote) {
return emote.name.toLowerCase().indexOf(value) >= 0;
};
} else {
self.filter = null;
}
self.handleChange();
self.loadPage(0);
});
};
EmoteList.prototype.initSortOption = function () {
this.sortOption = this.elem.find(".emotelist-alphabetical");
this.sortAlphabetical = false;
var self = this;
this.sortOption.change(function () {
self.sortAlphabetical = this.checked;
self.handleChange();
self.loadPage(0);
});
};
EmoteList.prototype.handleChange = function () {
this.emotes = CHANNEL.emotes.slice();
if (this.sortAlphabetical) {
this.emotes.sort(function (a, b) {
var x = a.name.toLowerCase();
var y = b.name.toLowerCase();
if (x < y) {
return -1;
} else if (x > y) {
return 1;
} else {
return 0;
}
});
}
if (this.filter) {
this.emotes = this.emotes.filter(this.filter);
}
this.paginator = new NewPaginator(this.emotes.length, this.itemsPerPage,
this.loadPage.bind(this));
this.paginatorContainer.html("");
this.paginatorContainer.append(this.paginator.elem);
this.paginator.loadPage(this.page);
};
EmoteList.prototype.loadPage = function (page) {
var tbody = this.table.children[0];
tbody.innerHTML = "";
var row;
var start = page * this.itemsPerPage;
if (start >= this.emotes.length) return;
var end = Math.min(start + this.itemsPerPage, this.emotes.length);
var _this = this;
for (var i = start; i < end; i++) {
if ((i - start) % this.cols === 0) {
row = document.createElement("tr");
tbody.appendChild(row);
}
(function (emote) {
var td = document.createElement("td");
td.className = "emote-preview-container";
// Trick element to vertically align the emote within the container
var hax = document.createElement("span");
hax.className = "emote-preview-hax";
td.appendChild(hax);
var img = document.createElement("img");
img.src = emote.image;
img.className = "emote-preview";
img.title = emote.name;
img.onclick = _this.emoteClickCallback.bind(null, emote);
td.appendChild(img);
row.appendChild(td);
})(this.emotes[i]);
}
this.page = page;
};
function onEmoteClicked(emote) {
var val = chatline.value;
if (!val) {
chatline.value = emote.name;
} else {
if (!val.charAt(val.length - 1).match(/\s/)) {
chatline.value += " ";
}
chatline.value += emote.name;
}
window.EMOTELISTMODAL.modal("hide");
chatline.focus();
}
window.EMOTELIST = new EmoteList("#emotelist", onEmoteClicked);
window.EMOTELIST.sortAlphabetical = USEROPTS.emotelist_sort;
function CSEmoteList(selector) {
EmoteList.call(this, selector);
}
CSEmoteList.prototype = Object.create(EmoteList.prototype);
CSEmoteList.prototype.loadPage = function (page) {
var tbody = this.table.children[1];
tbody.innerHTML = "";
var start = page * this.itemsPerPage;
if (start >= this.emotes.length) {
return;
}
var end = Math.min(start + this.itemsPerPage, this.emotes.length);
var self = this;
this.page = page;
for (var i = start; i < end; i++) {
var row = document.createElement("tr");
tbody.appendChild(row);
(function (emote, row) {
// Add delete button
var tdDelete = document.createElement("td");
var btnDelete = document.createElement("button");
btnDelete.className = "btn btn-xs btn-danger";
var pennJillette = document.createElement("span");
pennJillette.className = "glyphicon glyphicon-trash";
btnDelete.appendChild(pennJillette);
tdDelete.appendChild(btnDelete);
row.appendChild(tdDelete);
btnDelete.onclick = function deleteEmote() {
document.getElementById("cs-emotes-newname").value = emote.name;
document.getElementById("cs-emotes-newimage").value = emote.image;
socket.emit("removeEmote", emote);
};
// Add emote name
var tdName = document.createElement("td");
var nameDisplay = document.createElement("code");
nameDisplay.textContent = emote.name;
tdName.appendChild(nameDisplay);
row.appendChild(tdName);
var $nameDisplay = $(nameDisplay);
$nameDisplay.click(function (clickEvent) {
$nameDisplay.detach();
var editInput = document.createElement("input");
editInput.className = "form-control";
editInput.type = "text";
editInput.value = emote.name;
tdName.appendChild(editInput);
editInput.focus();
function save() {
var val = editInput.value;
tdName.removeChild(editInput);
tdName.appendChild(nameDisplay);
// Nothing was changed
if(val === emote.name){ return }
// Emote name already exists
if( CHANNEL.emotes.filter(function(emote){ return emote.name === val }).length ){
/*
* Since we are already in a modal
* and Bootstrap doesn't have supermodals
* we will make a self destructing warning
* as a row in the table
*/
var wrow = document.createElement("tr");
var tdBlankDel = document.createElement("td"); wrow.appendChild(tdBlankDel);
var tdWarnMess = document.createElement("td"); wrow.appendChild(tdWarnMess);
var warnSpan = document.createElement("p"); tdWarnMess.appendChild(warnSpan);
warnSpan.className = "text-warning";
warnSpan.textContent = "An emote of that name already exists.";
tdWarnMess.colSpan = "2";
row.insertAdjacentElement("beforebegin", wrow)
$(wrow).delay(2500).fadeOut('slow', function(){ $(this).remove() });
return;
}
socket.emit("renameEmote", {
old: emote.name,
image: emote.image,
name: val
});
}
editInput.onblur = save;
editInput.onkeyup = function (event) {
if (event.keyCode === 13) {
save();
}
};
});
// Add emote image
var tdImage = document.createElement("td");
var urlDisplay = document.createElement("code");
urlDisplay.textContent = emote.image;
tdImage.appendChild(urlDisplay);
row.appendChild(tdImage);
// Add popover to display the image
var $urlDisplay = $(urlDisplay);
$urlDisplay.popover({
html: true,
trigger: "hover",
content: ''
});
// Change the image for an emote
$urlDisplay.click(function (clickEvent) {
$(tdImage).find(".popover").remove();
$urlDisplay.detach();
var editInput = document.createElement("input");
editInput.className = "form-control";
editInput.type = "text";
editInput.value = emote.image;
tdImage.appendChild(editInput);
editInput.focus();
function save() {
var val = editInput.value;
tdImage.removeChild(editInput);
tdImage.appendChild(urlDisplay);
socket.emit("updateEmote", {
name: emote.name,
image: val
});
}
editInput.onblur = save;
editInput.onkeyup = function (event) {
if (event.keyCode === 13) {
save();
}
};
});
})(this.emotes[i], row);
}
};
window.CSEMOTELIST = new CSEmoteList("#cs-emotes");
window.CSEMOTELIST.sortAlphabetical = USEROPTS.emotelist_sort;
function showChannelSettings() {
$("#channeloptions").modal();
}
// There is a point where this file needed to stop and we have clearly passed
// it but let's keep going and see what happens
function startQueueSpinner(data) {
if ($("#queueprogress").length > 0) {
return;
}
var id = data.id;
if (data.type === "yp") {
id = "$any";
}
var progress = $("").addClass("progress").attr("id", "queueprogress")
.data("queue-id", id);
var progressBar = $("").addClass("progress-bar progress-bar-striped active")
.attr({
role: "progressbar",
"aria-valuenow": "100",
"aria-valuemin": "0",
"aria-valuemax": "100",
}).css({
width: "100%"
}).appendTo(progress);
progress.appendTo($("#addfromurl"));
}
function stopQueueSpinner(data) {
var shouldRemove = (data !== null &&
typeof data === 'object' &&
$("#queueprogress").data("queue-id") === data.id);
shouldRemove = shouldRemove || data === null;
shouldRemove = shouldRemove || $("#queueprogress").data("queue-id") === "$any";
if (shouldRemove) {
$("#queueprogress").remove();
}
}
function maybePromptToUpgradeUserscript() {
if (document.getElementById('prompt-upgrade-drive-userscript')) {
return;
}
if (!window.hasDriveUserscript) {
return;
}
var currentVersion = [1, 3];
var userscriptVersion = window.driveUserscriptVersion;
if (!userscriptVersion) {
userscriptVersion = '1.0';
}
userscriptVersion = userscriptVersion.split('.').map(function (part) {
return parseInt(part, 10);
});
var older = false;
for (var i = 0; i < currentVersion.length; i++) {
if (userscriptVersion[i] < currentVersion[i]) {
older = true;
}
}
if (!older) {
return;
}
var alertBox = document.createElement('div');
alertBox.id = 'prompt-upgrade-drive-userscript';
alertBox.className = 'alert alert-info'
alertBox.innerHTML = 'A newer version of the Google Drive userscript is available.';
alertBox.appendChild(document.createElement('br'));
var infoLink = document.createElement('a');
infoLink.className = 'btn btn-info';
infoLink.href = '/google_drive_userscript';
infoLink.textContent = 'Click here for installation instructions';
infoLink.target = '_blank';
alertBox.appendChild(infoLink);
var closeButton = document.createElement('button');
closeButton.className = 'close pull-right';
closeButton.innerHTML = '×';
closeButton.onclick = function () {
alertBox.parentNode.removeChild(alertBox);
}
alertBox.insertBefore(closeButton, alertBox.firstChild)
document.getElementById('videowrap').appendChild(alertBox);
}
function backoffRetry(fn, cb, options) {
var jitter = options.jitter || 0;
var factor = options.factor || 1;
var isRetryable = options.isRetryable || function () { return true; };
var tries = 0;
function callback(error, result) {
tries++;
factor *= factor;
if (error) {
if (tries >= options.maxTries) {
console.log('Max tries exceeded');
cb(error, result);
} else if (isRetryable(error)) {
var offset = Math.random() * jitter;
var delay = options.delay * factor + offset;
console.log('Retrying on error: ' + error);
console.log('Waiting ' + delay + ' ms before retrying');
setTimeout(function () {
fn(callback);
}, delay);
}
} else {
cb(error, result);
}
}
fn(callback);
}
CyTube.ui.changeVideoWidth = function uiChangeVideoWidth(direction) {
var body = document.body;
if (/hd/.test(body.className)) {
throw new Error("ui::changeVideoWidth does not work with the 'hd' layout");
}
var videoWrap = document.getElementById("videowrap");
var leftControls = document.getElementById("leftcontrols");
var leftPane = document.getElementById("leftpane");
var chatWrap = document.getElementById("chatwrap");
var rightControls = document.getElementById("rightcontrols");
var rightPane = document.getElementById("rightpane");
var match = videoWrap.className.match(/col-md-(\d+)/);
if (!match) {
throw new Error("ui::changeVideoWidth: videowrap is missing bootstrap class!");
}
var videoWidth = parseInt(match[1], 10) + direction;
if (videoWidth < 3 || videoWidth > 9) {
return;
}
var chatWidth = 12 - videoWidth;
videoWrap.className = "col-md-" + videoWidth + " col-lg-" + videoWidth;
rightControls.className = "col-md-" + videoWidth + " col-lg-" + videoWidth;
rightPane.className = "col-md-" + videoWidth + " col-lg-" + videoWidth;
chatWrap.className = "col-md-" + chatWidth + " col-lg-" + chatWidth;
leftControls.className = "col-md-" + chatWidth + " col-lg-" + chatWidth;
leftPane.className = "col-md-" + chatWidth + " col-lg-" + chatWidth;
handleVideoResize();
};