// ==UserScript==
// @name Youtube polymer engine fixes
// @description Some fixes for Youtube polymer engine
// @namespace bo.gd.an[at]rambler.ru
// @version 0.9.2
// @match https://www.youtube.com/*
// @compatible firefox 56
// @author Bogudan
// @grant none
// @run-at document-end
// ==/UserScript==
//debug helper:
//window.YTFixes = {};
(function() {
'use strict';
// test local storage availability (required for settings saving) and load settings
var settings = {}, ls, styles = [], intervals = [];
function lsTest (st, v) {
st.setItem ('__fix_test__', v);
return st.getItem ('__fix_test__') == v;
};
try {
var _s = window.localStorage;
if (lsTest (_s, 'qwe') && lsTest (_s, 'rty')) { // do 2 times just in case LS stored value once, but does not let change it later
ls = _s;
settings = JSON.parse (ls.getItem ('__fix__settings__')) || {};
}
}
catch (e) { }
// settings
var oldsettings = {
"default_player_640": { conv: function (X) { settings ["default_player"] = X ? 3 : 0; }, def: false },
"reduce_thumbnail": { conv: function (X) { settings ["thumbnail_size"] = X ? 2 : 0; }, def: true },
};
for (var S in oldsettings)
if (S in settings) {
var p = oldsettings [S];
p.conv (settings [S]);
delete settings [S];
}
console.log ('fix settings:', settings);
function getSetting (nm, def) { return nm in settings ? settings [nm] : def; }
function addInterval (period, func, params) {
if (!period)
period = 1;
intervals.push ({ cnt: period, period: period, call: func, params: params || [] });
}
var size_norm, size_theater;
var presettings = {
"hide_guide": {
type: "bool",
def: true,
desc: "Hide \"Guide\" menu when page opens",
act: (X) => {
if (!X) return;
var guide_button = document.getElementById ('guide-button');
if (!guide_button) return;
var tmp = guide_button.getElementsByTagName ('button');
if (!tmp.length) return;
tmp = tmp [0].attributes;
if (tmp && tmp ['aria-pressed'].value == 'true')
guide_button.click ();
}
},
"reduce_font": {
type: "bool",
def: true,
desc: "Reduce font size in video descriptions",
act: (X) => {
if (!X) return;
styles.push ('#video-title.ytd-rich-grid-video-renderer,#text.ytd-channel-name,#metadata-line.ytd-video-meta-block{font-size:14px!important;line-height:1.2em!important}');
// fix: "not interested" placeholder bigger than original element by about 40%
styles.push ('paper-button.style-blue-text{padding:0!important}');
}
},
"thumbnail_size": {
type: "list",
def: 2,
desc: "Set thumbnails size (front page)",
opts: ['default', '180px', '240px', '360px', '480px'],
act: (X) => {
if (!X) return;
var w = [0, 180, 240, 360, 480] [X];
styles.push ('ytd-rich-item-renderer{width:' + w + 'px!important}');
}
},
"default_player": {
type: "list",
def: 0,
desc: "Set player size in default mode",
opts: ['default', '256x144px', '426x240px', '640x360px', '853x480px', '1280x720px'],
act: (X) => {
if (!X) return;
var w = [0, 256, 427, 640, 854, 1280] [X];
var h = [0, 144, 240, 360, 480, 720] [X];
styles.push ('#player-container-outer{min-width:' + w + 'px!important;max-width:' + w + 'px!important}');
size_norm = [w, h];
}
},
"theater_player": {
type: "list",
def: 0,
desc: "Set player size in theater mode",
opts: ['default', '256x144px', '426x240px', '640x360px', '853x480px', '1280x720px'],
act: (X) => {
if (!X) return;
var w = [0, 256, 427, 640, 854, 1280] [X];
var h = [0, 144, 240, 360, 480, 720] [X];
styles.push ('#player-theater-container{min-width:' + w + 'px!important;max-width:' + w + 'px!important;max-height:' + h + 'px!important}');
size_theater = [w, h];
}
},
"hide_yt_suggested_blocks": {
type: "bool",
def: true,
desc: "Hide YouTube suggestions blocks like \"Recommended playlists\" or \"Latest YouTube posts\"",
act: (X) => {
if (X)
styles.push ('div#contents.ytd-rich-grid-renderer ytd-rich-section-renderer{display:none!important}');
}
},
"unfix_header": {
type: "bool",
def: true,
desc: "Unstick header bar from top of the screen",
act: (X) => {
if (X)
styles.push ('div#masthead-container.ytd-app,ytd-mini-guide-renderer.ytd-app,ytd-feed-filter-chip-bar-renderer{position:absolute!important}');
}
},
"align_player": {
type: "list",
def: 0,
desc: "Align resized player into it's container (normal and theater modes)",
opts: ['center', 'left', 'right'],
act: (X) => {
styles.push ([
'#player-theater-container{margin-left:auto!important;margin-right:auto!important}',
'#player-container-outer{margin-left:0!important}',
'#player-container-outer{margin-right:0!important}#player-theater-container{margin-left:auto!important}',
] [X]);
}
},
"logo_target": {
type: "text",
def: "",
desc: "Change YT logo target to https://www.youtube.com/...",
act: (X) => {
if (!X) return;
if (X [0] != '/')
X = '/' + X;
X = document.location.origin + X;
addInterval (1, function (X) {
var l = document.querySelectorAll ('a#logo');
for (var i = l.length; --i >= 0; )
if (l [i].href != X) {
l [i].href = X;
l [i].data.commandMetadata.webCommandMetadata.url = X;
}
}, [X]);
}
},
};
// catch "settings" page
if (document.location.pathname == '/fix-settings') {
document.title = "YouTube Polymer Fixes: Settings";
var plane = document.createElement ('div'), e1, e2, e3;
plane.setAttribute ('style', 'position:absolute;left:0;top:0;right:0;bottom:0;background:#eee;padding:3em');
function addLine () {
var q = document.createElement ('div');
for (var i = 0, L = arguments.length; i < L; ++i)
q.appendChild (arguments [i]);
q.setAttribute ('style', 'margin:1em');
plane.appendChild (q);
}
e1 = document.createElement ('b');
e1.appendChild (document.createTextNode ('YouTube Polymer Fixes: Settings'));
addLine (e1);
if (!ls) {
e1 = document.createElement ('span');
e1.style = 'color:red';
e1.appendChild (document.createTextNode ('Cannot edit settings: no access to local storage.'));
e2 = document.createElement ('span');
e2.appendChild (document.createTextNode ('If you using Firefox, allow cookies for this site.'));
addLine (e1, e2);
}
else {
for (var S in presettings) {
var p = presettings [S];
var val = getSetting (S, p.def);
if (p.type == "bool") {
e1 = document.createElement ('input');
e1.setAttribute ('type', 'checkbox');
e1.setAttribute ('style', 'margin-right: 1em;');
if (val)
e1.setAttribute ('checked', '');
e1.name = '__fix__' + S;
e2 = document.createElement ('span');
e2.appendChild (document.createTextNode (p.desc));
addLine (e1, e2);
}
if (p.type == "list") {
e1 = document.createElement ('span');
e1.appendChild (document.createTextNode (p.desc));
e1.setAttribute ('style', 'margin-right: 1em;');
e2 = document.createElement ('select');
for (var i = 0, L = p.opts.length; i < L; ++i) {
e3 = e2.appendChild (document.createElement ('option'));
e3.appendChild (document.createTextNode (p.opts [i]));
if (i == val)
e3.setAttribute ('selected', '');
}
e2.setAttribute ('style', 'padding: 0.2em;border:1px solid #888');
e2.name = '__fix__' + S;
addLine (e1, e2);
}
if (p.type == "text") {
e1 = document.createElement ('span');
e1.appendChild (document.createTextNode (p.desc));
e1.setAttribute ('style', 'margin-right: 1em;');
e2 = document.createElement ('input');
e2.value = val;
e2.setAttribute ('style', 'padding: 0.2em;border:1px solid #888');
e2.name = '__fix__' + S;
addLine (e1, e2);
}
}
e1 = document.createElement ('input');
e1.setAttribute ('type', 'button');
e1.setAttribute ('style', 'padding:0.4em;border:1px solid #888');
e1.value = 'Save settings and return to YouTube';
e1.addEventListener ('click', function () {
settings = {};
for (var S in presettings) {
var p = presettings [S];
var e = document.getElementsByName ('__fix__' + S);
if (p.type == "bool")
settings [S] = e [0].checked; // checkbox
if (p.type == "list")
settings [S] = e [0].selectedIndex; // drop-down
if (p.type == "text")
settings [S] = e [0].value; // text box
}
ls.setItem ('__fix__settings__', JSON.stringify (settings));
alert ('Settings saved');
history.back ();
});
e2 = document.createElement ('input');
e2.setAttribute ('type', 'button');
e2.setAttribute ('style', 'padding:0.4em;border:1px solid #888');
e2.value = 'Return to YouTube without saving';
e2.addEventListener ('click', function () {
history.back ();
});
addLine (e1, document.createTextNode (' '), e2);
}
document.body.appendChild (plane);
return;
}
// "settings" button
// can't store created button: Polymer overrides it's content on soft reload leaving tags in place
// but can store element that Polymer does not know how to deal with and just drops
var settingsButtonMark;
function createSettingsButton () {
if (settingsButtonMark && settingsButtonMark.parentNode)
return;
var toolBar = document.getElementsByTagName ('ytd-topbar-menu-button-renderer');
var _1st = toolBar [0];
if (!_1st)
return;
toolBar = _1st.parentNode;
var sb = document.createElement ('ytd-topbar-menu-button-renderer');
sb.className = 'style-scope ytd-masthead style-default';
sb.setAttribute ('use-keyboard-focused', '');
sb.setAttribute ('is-icon-button', '');
sb.setAttribute ('has-no-text', '');
toolBar.insertBefore (sb, toolBar.childNodes [0]);
var mark = document.createElement ('fix-settings-mark');
mark.setAttribute ('style', 'display:none');
toolBar.insertBefore (mark, sb); // must be added to parent node of buttons in order to Polymer dropped it on soft reload
var icb = document.createElement ('yt-icon-button');
icb.id = 'button';
icb.className = 'style-scope ytd-topbar-menu-button-renderer style-default';
var aa = document.createElement ('a');
aa.className = 'yt-simple-endpoint style-scope ytd-topbar-menu-button-renderer';
aa.setAttribute ('tabindex', '-1');
aa.setAttribute ('href', '/fix-settings');
aa.appendChild (icb);
sb.getElementsByTagName ('div') [0].appendChild (aa); // created by YT scripts
var bb = icb.getElementsByTagName ('button') [0]; // created by YT scripts
bb.setAttribute ('aria-label', 'fixes settings');
var ic = document.createElement ('yt-icon');
ic.className = 'style-scope ytd-topbar-menu-button-renderer';
bb.appendChild (ic);
var gpath = document.createElementNS ('http://www.w3.org/2000/svg', 'path');
gpath.className.baseVal = 'style-scope yt-icon';
gpath.setAttribute ('d', 'M1 20l6-6h2l11-11v-1l2-1 1 1-1 2h-1l-11 11v2l-6 6h-1l-2-2zM13 15l2-2 8 8v1l-1 1h-1zM9 11l2-2-2-2 1.5-3-3-3h-2l3 3-1.5 3-3 1.5-3-3v2l3 3 3-1.5z');
var svgg = document.createElementNS ('http://www.w3.org/2000/svg', 'g');
svgg.className.baseVal = 'style-scope yt-icon';
svgg.appendChild (gpath);
var svg = document.createElementNS ('http://www.w3.org/2000/svg', 'svg');
svg.className.baseVal = 'style-scope yt-icon';
svg.setAttributeNS (null, 'viewBox', '0 0 24 24');
svg.setAttributeNS (null, 'preserveAspectRatio', 'xMidYMid meet');
svg.setAttribute ('focusable', 'false');
svg.setAttribute ('style', 'pointer-events: none; display: block; width: 100%; height: 100%;');
svg.appendChild (svgg);
ic.appendChild (svg); // YT clears *ic
settingsButtonMark = mark;
}
addInterval (1, createSettingsButton, []);
// apply settings
for (var S in presettings) {
var p = presettings [S];
p.act (getSetting (S, p.def));
}
if (size_norm || size_theater)
addInterval (1, function (sn, st) {
var eq = document.getElementsByTagName ("ytd-watch-flexy");
if (!eq.length)
return;
var s = eq [0].hasAttribute ('theater') ? st : sn;
if (!s)
return;
var ep = document.getElementById ("movie_player");
if (ep && ep.setInternalSize && ep.isFullscreen && !ep.isFullscreen () && ep.getPlayerSize && ep.getPlayerSize ().width != s [0])
ep.setInternalSize (s [0], s [1]);
}, [size_norm, size_theater]);
// styles
addInterval (1, function () {
if (styles.length == 0 || !document.body) return;
var style_element = document.createElement ('style');
style_element.type = 'text/css';
style_element.innerHTML = styles.join ('');
document.body.appendChild(style_element);
styles = [];
}, []);
setInterval (function () {
for (var i = intervals.length; --i >= 0; ) {
var Q = intervals [i];
if (--Q.cnt > 0)
continue;
Q.call.apply (this, Q.params);
Q.cnt = Q.period;
}
}, 1000);
})();