Skip to content

Instantly share code, notes, and snippets.

@jr-codes
Last active May 2, 2024 02:45
Show Gist options
  • Save jr-codes/4088609 to your computer and use it in GitHub Desktop.
Save jr-codes/4088609 to your computer and use it in GitHub Desktop.
u.js userscript
// ==UserScript==
// @name u.js
// @namespace http://zarjay.net/
// @description Utility functions for browser console ninjas
// @include *
// @version 0.28
// ==/UserScript==
exec(function() {
// Current version of u.js
var VERSION = '0.28',
// Predefined resources to be included via u.include(name)
RESOURCES = {
// resource: { css: [], js: [], script: [] }
// Design - http://www.sprymedia.co.uk/article/Design
// Grid layout, measurement, and alignment bookmarklet
design: {
js: ['http://www.sprymedia.co.uk/design/design/media/js/design-loader.js']
},
// DOM Monster - http://mir.aculo.us/dom-monster/
// Analyzes DOM and gives performance tips
'dom-monster': {
js: ['//mir.aculo.us/dom-monster/dommonster.js']
},
// jQuery library - http://jquery.com/
// DOM manipulation, event handling, Ajax, etc.
jquery: {
js: ['//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js']
},
// jQuery UI - http://jqueryui.com/
// UI/effects library
'jquery-ui': {
css: ['//ajax.googleapis.com/ajax/libs/jqueryui/1/themes/base/jquery-ui.css'],
js: ['//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js']
},
// Katamari Hack - http://kathack.com/
// Turns a webpage into a game of Katamari Damacy. Use your katamari ball to pick up DOM elements.
katamari: {
js: ['//ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js', 'http://kathack.com/js/kh.js']
},
// Kickass - https://kickassapp.com/
// Turn a webpage into a game of Asteroids. Use your ship to destroy DOM elements.
kickass: {
js: ['//hi.kickassapp.com/kickass.js']
},
// Lo-Dash - http://lodash.com/
// Underscore alternative (utility library)
lodash: {
js: ['//cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.0-rc.3/lodash.min.js']
},
// Markup - http://markup.io
// Draw on a webpage and share with others
markup: {
script: ["(function(){window.add_js=function(s){var k=(document.getElementsByTagName('head')[0]||document.body).appendChild(document.createElement('script'));k.src=s;k.type='text/javascript';k.markup='ea33cdc7-73dd-11e2-9673-fac11f1adc9e'};window.MarkUp=window.MarkUp||{};add_js('http://api.markup.io/bootstrap.js?v=1&'+(+(new Date)))})();"]
},
// Moment.js - http://momentjs.com/
// Date library
moment: {
js: ['//cdnjs.cloudflare.com/ajax/libs/datejs/1.0/date.min.js']
},
// Raphael
// SVG library
raphael: {
js: ['//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js']
},
// Stats - https://github.com/mrdoob/stats.js/
// FPS counter
stats: {
js: ['https://github.com/mrdoob/stats.js/raw/master/build/stats.min.js'],
script: ['var interval=setInterval(function(){if(typeof Stats==\'function\'){clearInterval(interval);var stats=new Stats();stats.domElement.style.position=\'fixed\';stats.domElement.style.left=\'0px\';stats.domElement.style.top=\'0px\';stats.domElement.style.zIndex=\'10000\';document.body.appendChild(stats.domElement);setInterval(function(){stats.update();},1000/60);}},100);']
},
// Statsy Bookmarklet - http://www.phpied.com/statsy-more-data-points-for-markup-quality/
// Displays some DOM statistics
statsy: {
js: ['http://phpied.com/files/bookmarklets/somestats.js']
},
// Underscore - http://underscorejs.org/
// Utility library
underscore: {
js: ['//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js']
},
// What Font Bookmarklet - http://chengyinliu.com/whatfont.html
// Identifies fonts
whatfont: {
js: ['http://chengyinliu.com/wf.js']
}
},
// setInterval(function() { u.query('img').effect('rotate'); }, 3000);
// u.query('img').effect('spin', 'random'); // They all spin at the same time
// u.query('img').each(function(element) { element.effect('spin', 'random', Math.random() + 0.2); } ); // They all spin at different times
// u.query('img').each(function(element) { element.effect('tilt', 5, Math.random() * (5 - 0.4 + 1) + 0.4); }); // They all spin at different times
EFFECTS = {
// u.effect('flip', 'z')
flip: function(axis, speed) {
// Default to flip on y-axis if undefined
axis = axis || 'y';
// If undefined, default to 3s
// If number, assume seconds and add 's' unit
speed = speed || '3s';
if (!isNaN(speed)) speed += 's';
var transform = 'rotate' + axis.toUpperCase();
this.css('-webkit-transition', 'none');
this.css('-webkit-transform', transform + '(0deg)');
// Wait a bit before updating the same CSS properties
setTimeout(function() {
this.css('-webkit-transition', '-webkit-transform ' + speed);
this.css('-webkit-transform', transform + '(180deg)');
}.bind(this), 0);
},
// u.effect('rotate', '60deg', '4s')
rotate: function(angle, speed) {
// If number, add 'deg' unit
// If undefined, default to random angle between -1080 and 1080
angle = !isNaN(angle) && angle !== null ? angle + 'deg' : angle ? angle : ( Math.floor( Math.random() * 1081 ) - 1080 + 'deg' );
// If undefined, default to 2s
// If number, assume seconds and add 's' unit
speed = speed || '2s';
if (!isNaN(speed)) speed += 's';
this.css('-webkit-transition', '-webkit-transform ' + speed);
this.css('-webkit-transform', 'rotate(' + angle + ')');
},
// u.effect('spin')
// u.effect('spin', 'random')
// u.effect('spin', 'default', '200ms')
// u.effect('spin', 'backward', 5)
spin: function(option, speed) {
var speedParts = /(\d+\.?\d*)(\D*)/.exec(speed || '2s'),
speedMs = speedParts[2] !== 'ms' ? speedParts[1] * 1000 : speedParts[1],
angleIncrement = (option === 'backwards') ? -360 : (option === 'random') ? undefined : 360,
angle = angleIncrement;
fn = function() {
this.effect('rotate', angle, speed);
angle += angleIncrement;
},
id = setInterval(fn.bind(this), speedMs);
},
// u.effect('tilt', 5);
// u.effect('tile', '5deg');
tilt: function(range, speed) {
// If deg value (e.g., '5deg'), parse out number
// If undefined, default to 5
range = parseInt(range || 5, 10);
// If undefined, default to 3s
// If number, assume seconds and add 's' unit
speed = speed || '1s';
if (!isNaN(speed)) speed += 's';
var angleStart = 0 - range,
angleEnd = 0 + range;
var animation = addAnimation({
'0%': '-webkit-transform:rotate(' + angleStart + 'deg);',
'50%': '-webkit-transform:rotate(' + angleEnd + 'deg);',
'100%': '-webkit-transform:rotate(' + angleStart + 'deg);'
});
this.css('-webkit-animation', animation + ' infinite ' + speed);
}
},
// Converts array-like objects to arrays
slice = Function.prototype.call.bind(Array.prototype.slice),
// Copies properties from the second object into the first object
extend = function(dest, src) {
for (var prop in src) dest[prop] = src[prop];
return dest;
},
// Adds a <style> to the page
addStyle = function(css) {
style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
},
// Adds a <link> for the specified URL
addCSS = function(url) {
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
document.head.appendChild(link);
console.log('Loaded ' + url);
},
// Adds a <script> for the specified URL
addJS = function(url, handler) {
var script = document.createElement('script');
script.onload = handler;
script.async = false;
script.src = url;
document.head.appendChild(script);
console.log('Loaded ' + url);
},
// Adds a <script> for the specified JavaScript
addScript = function(fn, isImmediate) {
if (isImmediate === undefined) isImmediate = false;
var script = document.createElement('script');
script.textContent = isImmediate ? '(' + fn + ')();' : fn;
document.head.appendChild(script);
},
// Adds a CSS animation style
addAnimation = function(keyframes) {
this.counter = this.counter || 0;
var animationName = 'u-animation-' + this.counter++;
var style = '@-webkit-keyframes ' + animationName + ' { ';
for (var name in keyframes) {
style += name + ' { ' + keyframes[name] + ' } ';
}
style += '}';
addStyle(style);
return animationName;
},
Collection = {
// Sets CSS property for elements
// Or gets the CSS property value of first element
css: function(property, value) {
if (value === undefined && this.length) return window.getComputedStyle(this[0])[property];
this.forEach(function(element) {
element.style[property] = value;
});
},
each: function(callback) {
this.forEach(function(element) {
callback(extend([element], Collection));
});
},
// Make elements editable
edit: function(isEditable) {
this.forEach(function(element) {
if (isEditable === undefined) isEditable = true;
element.contentEditable = isEditable;
});
},
// Applies a CSS effect
effect: function(effect, value, speed) {
if (effect === undefined) return Object.keys(EFFECTS);
if (effect in EFFECTS) EFFECTS[effect].call(this, value, speed);
},
// Add HTML at the specified position
insert: function(position, html) {
var positions = {
append: 'beforeend',
prepend: 'afterbegin',
before: 'beforebegin',
after: 'afterend'
};
this.forEach(function(element) {
element.insertAdjacentHTML(positions[position] || position, html);
});
},
// Intercept events by executing first (during the capture phase) and stopping propagation
// Useful for sites that prevent/manipulate text selection, right-click, etc.
intercept: function() {
var events = ['keyup', 'keydown', 'keypress', 'mouseup', 'mousedown', 'selectstart', 'contextmenu', 'copy'],
stopEvents = function(e) { e.stopPropagation(); };
this.forEach(function(element) {
events.forEach(function(e) {
element.addEventListener(e, stopEvents, true);
});
});
},
// Gets the parent of each element
parent: function() {
return extend(this.map(function(element) {
return element.parentNode;
}), Collection);
},
// Calls querySelectorAll() on each element
// and combines the results into a u.js-powered array
query: function(selector) {
if (this.length) {
return extend(this.map(function(element) {
return slice(element.querySelectorAll(selector));
}).reduce(function (a, b) {
return a.concat(b);
}), Collection);
} else {
return this;
}
},
// Replaces HTML based on the given regex
// If a string is used instead of a regex, it's converted to a global regex
replace: function(regex, html) {
this.forEach(function(element) {
if (!(regex instanceof RegExp)) regex = new RegExp(regex, 'g');
element.innerHTML = element.innerHTML.replace(regex, html);
});
},
// Combine element values
// Optionally specify functions to filter out selections,
// remap values, or change how the values are summed up
sum: function(filter, map, reduce) {
return this.length && this.filter(filter || function() {
return true;
}).map(map || function(element) {
return +element.textContent.replace(/[$,%]/g, '');
}).reduce(reduce || function(a, b) {
return a + b;
});
}
},
body = extend([document.body], Collection);
// u.js
var u = {
// Current version of u.js
_VERSION: VERSION,
// Get/set a CSS property on <body>
// u.css(property)
// u.css(property, value)
css: Collection.css.bind(body),
// Makes everything editable.
// u.edit() or u.edit(true) - Turns on editing
// u.edit(false) - Turns off editing
edit: Collection.edit.bind(body),
// u.effect(property, value, speed)
// u.effect(effect, value, speed)
effect: Collection.effect.bind(body),
// Include a CSS file, JS file, or predefined resource
// u.include(url) - Adds CSS or JS file onto the page
// u.include(url, type) - Adds CSS or JS file onto the page (specifying type in second param)
// u.include(resource) - Adds resource onto the page
// u.include() - Lists available resources to include
// u.include(url, handler)
// u.include(resource, handler)
// u.include(url, type, handler)
// u.include(resource, type, handler)
include: function(resource, type, handler) {
if (resource === undefined) return Object.keys(RESOURCES);
if (typeof type === 'function') {
handler = type;
type = null;
}
if (resource in RESOURCES) {
var r = RESOURCES[resource];
if (r.css) for (var j = 0; j < r.css.length; ++j) addCSS(r.css[j]);
if (r.js) for (var k = 0; k < r.js.length; ++k) addJS(r.js[k], handler);
if (r.script) for (var l = 0; l < r.script.length; ++l) addScript(r.script[l]);
} else {
type = (type || resource.split('.').pop()).toLowerCase();
if (type === 'css') {
addCSS(resource);
} else if (type === 'js') {
addJS(resource, handler);
} else {
throw new Error('Could not load ' + resource);
}
}
},
// Insert HTML in <body> at the specified position
// u.insert(position, html)
insert: Collection.insert.bind(body),
// Stop events from manipulating/preventing user input
// u.intercept()
intercept: Collection.intercept.bind(body),
// Returns u.js-powered array of elements
// Shortcut for document.querySelectorAll()
// u.query(selector)
// Also wraps existing Node and NodeList elements into a u.js-powered arrays
query: function(selector) {
if (typeof selector === 'string') return extend(slice(document.querySelectorAll(selector)), Collection);
if (selector instanceof NodeList) return extend(slice(selector), Collection);
if (selector instanceof Node) return extend([selector], Collection);
},
// u.ready();
// u.ready(fn);
// u.ready(null);
ready: function(fn) {
var key = 'u.ready';
if (fn === undefined) {
fn = this.store(key);
if (fn) addScript(fn, true);
} else {
this.store(key, fn);
}
},
// Replace HTML in <body> usng a string or regex
// u.replace(regex, html)
replace: Collection.replace.bind(body),
// Store and retrieve data in localStorage (including functions)
// store(key, value) - Stores value in localStorage under key
// store(key) - Gets value of key in localStorage
// store() - List keys stored in localStorage
store: function(key, value) {
if (key === undefined) return Object.keys(localStorage);
if (value === undefined) {
value = localStorage[key];
// Return immediately if falsey
if (!value || value === 'undefined') return value;
value = value && JSON.parse(value);
if (value && value.indexOf && value.indexOf('function') === 0) value = eval('(' + value + ')');
return value;
} else {
if (typeof value === 'function') value = value.toString();
localStorage[key] = JSON.stringify(value);
}
}
};
/* Init */
var loadu = function(u, names) {
for (var i = 0; i < names.length; ++i) {
var name = names[i];
if (name in window) continue;
window[name] = u;
console.log('Loaded u.js as global variable ' + name);
return;
}
console.warn('Could not load u.js due to variable conflicts.');
};
loadu(u, ['u', 'u.js', 'zarjay.net/u.js']);
u.ready();
});
// Executes function in global scope (so it becomes available outside of userscript)
function exec(fn) {
var script = document.createElement('script');
script.textContent = '(' + fn + ')();';
document.head.appendChild(script);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment