See brucebentley/userscripts
for more information.
Last active
January 6, 2022 05:02
-
-
Save brucebentley/b29ef2abf93f05b9bf1bd7fe1a2a2638 to your computer and use it in GitHub Desktop.
Utilities used by my ever growing library of userscripts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function () { | |
const version = '1.5'; | |
const base = 'https://cdn.flickriver.com/js/gm/viewonflickriver.js'; | |
const scriptURL = base + '?v=' + version; | |
if (typeof window.Flickriver === 'undefined') { | |
window.Flickriver = {}; | |
} else { | |
return; | |
} | |
function loadScript(url) { | |
const s = document.createElement('script'); | |
s.setAttribute('type', 'text/javascript'); | |
s.setAttribute('src', url); | |
document.getElementsByTagName('head')[0].appendChild(s); | |
} | |
loadScript(scriptURL); | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* GitHub mutations observer library script v0.4.4 | |
* Detect changes to various elements and trigger an event | |
* This script is meant to be used as a library for GitHub-based userscripts | |
* Copyright © 2021 Rob Garrison | |
* License: MIT | |
*/ | |
(() => { | |
"use strict"; | |
// prefix for event & document body class name, e.g. "ghmo:container" | |
const prefix = "ghmo", | |
disableAttr = `data-${prefix}-disable`, | |
debounceInterval = 200, | |
targets = { | |
// pjax container (covers general, repo & gists) | |
// .news = newsfeed layout | |
// .repository-content = file code (code folding) | |
"[data-pjax-container], .news, .repository-content": { | |
count: 0, | |
name: "container" | |
}, | |
// comment preview active | |
".js-preview-body": { | |
count: 0, | |
name: "preview" | |
}, | |
// .js-discussion = wrapper for progressively loaded comments; | |
// "# items not shown" example: https://github.com/isaacs/github/issues/18 | |
// .discussion-item = issue status changed (github-issue-show-status) | |
// #progressive-timeline-item-container = load hidden items (old?) | |
// #js-progressive-timeline-item-container = load hidden items | |
".js-discussion, .discussion-item, .toolbar-item, #progressive-timeline-item-container, #js-progressive-timeline-item-container": { | |
count: 0, | |
name: "comments" | |
}, | |
// progressively loaded content (diff files) | |
".js-diff-progressive-container, .data.blob-wrapper, .js-diff-load-container, .diff-table tbody": { | |
count: 0, | |
name: "diff" | |
}, | |
// issues/pr sidebar & timeline sections: e.g. form actions, commit | |
// references, deployment state & PR checks container | |
".js-updatable-content, .js-updatable-content-preserve-scroll-position": { | |
count: 0, | |
name: "updatable" | |
}, | |
// user profile menu (loads on hover) | |
"details-menu": { | |
count: 0, | |
name: "menu" | |
} | |
}, | |
list = Object.keys(targets); | |
function fireEvents() { | |
list.forEach(selector => { | |
if (targets[selector].count > 0) { | |
// event => "ghmo:container", "ghmo:comments" | |
const event = new Event(prefix + ":" + targets[selector].name); | |
document.dispatchEvent(event); | |
} | |
targets[selector].count = 0; | |
}); | |
} | |
function init() { | |
// prevent error when library is loaded at document-start & no body exists | |
const container = document.querySelector("body"); | |
let timer; | |
// prevent script from installing more than once | |
if (container && !container.classList.contains(prefix + "-enabled")) { | |
container.classList.add(prefix + "-enabled"); | |
// bound to document.body... this may be bad for performance | |
// http://stackoverflow.com/a/39332340/145346 | |
new MutationObserver(mutations => { | |
clearTimeout(timer); | |
/* document.body attribute used to disable updates; it *should not* | |
* be used regularly as multiple scripts may enable or disable the | |
* observers at inappropriate times. It is best that each script handles | |
* the mutation events triggered by this library on its own | |
*/ | |
if (container.getAttribute(disableAttr)) { | |
return; | |
} | |
let mindx, target, lindx, | |
llen = list.length, | |
mlen = mutations.length; | |
// avoiding use of forEach loops for performance reasons | |
for (mindx = 0; mindx < mlen; mindx++) { | |
target = mutations[mindx].target; | |
if (target) { | |
for (lindx = 0; lindx < llen; lindx++) { | |
if (target.matches(list[lindx])) { | |
targets[list[lindx]].count++; | |
} | |
} | |
} | |
timer = setTimeout(() => { | |
fireEvents(); | |
}, debounceInterval); | |
} | |
}).observe(container, { | |
childList: true, | |
subtree: true | |
}); | |
} | |
} | |
if (document.readyState === "loading") { | |
document.addEventListener("DOMContentLoaded", () => init); | |
} else { | |
init(); | |
} | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* URI.js - Mutating URLs | |
* | |
* Version: 1.19.7 | |
* | |
* Author: Rodney Rehm | |
* Web: http://medialize.github.io/URI.js/ | |
* | |
* Licensed under | |
* MIT License http://www.opensource.org/licenses/mit-license | |
* | |
*/ | |
(function (root, factory) { | |
'use strict'; | |
// https://github.com/umdjs/umd/blob/master/returnExports.js | |
if (typeof module === 'object' && module.exports) { | |
// Node | |
module.exports = factory(require('./punycode'), require('./IPv6'), require('./SecondLevelDomains')); | |
} else if (typeof define === 'function' && define.amd) { | |
// AMD. Register as an anonymous module. | |
define(['./punycode', './IPv6', './SecondLevelDomains'], factory); | |
} else { | |
// Browser globals (root is window) | |
root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root); | |
} | |
}(this, function (punycode, IPv6, SLD, root) { | |
'use strict'; | |
/*global location, escape, unescape */ | |
// FIXME: v2.0.0 renamce non-camelCase properties to uppercase | |
/*jshint camelcase: false */ | |
// save current URI variable, if any | |
var _URI = root && root.URI; | |
function URI(url, base) { | |
var _urlSupplied = arguments.length >= 1; | |
var _baseSupplied = arguments.length >= 2; | |
// Allow instantiation without the 'new' keyword | |
if (!(this instanceof URI)) { | |
if (_urlSupplied) { | |
if (_baseSupplied) { | |
return new URI(url, base); | |
} | |
return new URI(url); | |
} | |
return new URI(); | |
} | |
if (url === undefined) { | |
if (_urlSupplied) { | |
throw new TypeError('undefined is not a valid argument for URI'); | |
} | |
if (typeof location !== 'undefined') { | |
url = location.href + ''; | |
} else { | |
url = ''; | |
} | |
} | |
if (url === null) { | |
if (_urlSupplied) { | |
throw new TypeError('null is not a valid argument for URI'); | |
} | |
} | |
this.href(url); | |
// resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor | |
if (base !== undefined) { | |
return this.absoluteTo(base); | |
} | |
return this; | |
} | |
function isInteger(value) { | |
return /^[0-9]+$/.test(value); | |
} | |
URI.version = '1.19.7'; | |
var p = URI.prototype; | |
var hasOwn = Object.prototype.hasOwnProperty; | |
function escapeRegEx(string) { | |
// https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963 | |
return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); | |
} | |
function getType(value) { | |
// IE8 doesn't return [Object Undefined] but [Object Object] for undefined value | |
if (value === undefined) { | |
return 'Undefined'; | |
} | |
return String(Object.prototype.toString.call(value)).slice(8, -1); | |
} | |
function isArray(obj) { | |
return getType(obj) === 'Array'; | |
} | |
function filterArrayValues(data, value) { | |
var lookup = {}; | |
var i, length; | |
if (getType(value) === 'RegExp') { | |
lookup = null; | |
} else if (isArray(value)) { | |
for (i = 0, length = value.length; i < length; i++) { | |
lookup[value[i]] = true; | |
} | |
} else { | |
lookup[value] = true; | |
} | |
for (i = 0, length = data.length; i < length; i++) { | |
/*jshint laxbreak: true */ | |
var _match = lookup && lookup[data[i]] !== undefined | |
|| !lookup && value.test(data[i]); | |
/*jshint laxbreak: false */ | |
if (_match) { | |
data.splice(i, 1); | |
length--; | |
i--; | |
} | |
} | |
return data; | |
} | |
function arrayContains(list, value) { | |
var i, length; | |
// value may be string, number, array, regexp | |
if (isArray(value)) { | |
// Note: this can be optimized to O(n) (instead of current O(m * n)) | |
for (i = 0, length = value.length; i < length; i++) { | |
if (!arrayContains(list, value[i])) { | |
return false; | |
} | |
} | |
return true; | |
} | |
var _type = getType(value); | |
for (i = 0, length = list.length; i < length; i++) { | |
if (_type === 'RegExp') { | |
if (typeof list[i] === 'string' && list[i].match(value)) { | |
return true; | |
} | |
} else if (list[i] === value) { | |
return true; | |
} | |
} | |
return false; | |
} | |
function arraysEqual(one, two) { | |
if (!isArray(one) || !isArray(two)) { | |
return false; | |
} | |
// arrays can't be equal if they have different amount of content | |
if (one.length !== two.length) { | |
return false; | |
} | |
one.sort(); | |
two.sort(); | |
for (var i = 0, l = one.length; i < l; i++) { | |
if (one[i] !== two[i]) { | |
return false; | |
} | |
} | |
return true; | |
} | |
function trimSlashes(text) { | |
var trim_expression = /^\/+|\/+$/g; | |
return text.replace(trim_expression, ''); | |
} | |
URI._parts = function() { | |
return { | |
protocol: null, | |
username: null, | |
password: null, | |
hostname: null, | |
urn: null, | |
port: null, | |
path: null, | |
query: null, | |
fragment: null, | |
// state | |
preventInvalidHostname: URI.preventInvalidHostname, | |
duplicateQueryParameters: URI.duplicateQueryParameters, | |
escapeQuerySpace: URI.escapeQuerySpace | |
}; | |
}; | |
// state: throw on invalid hostname | |
// see https://github.com/medialize/URI.js/pull/345 | |
// and https://github.com/medialize/URI.js/issues/354 | |
URI.preventInvalidHostname = false; | |
// state: allow duplicate query parameters (a=1&a=1) | |
URI.duplicateQueryParameters = false; | |
// state: replaces + with %20 (space in query strings) | |
URI.escapeQuerySpace = true; | |
// static properties | |
URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i; | |
URI.idn_expression = /[^a-z0-9\._-]/i; | |
URI.punycode_expression = /(xn--)/i; | |
// well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care? | |
URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; | |
// credits to Rich Brown | |
// source: http://forums.intermapper.com/viewtopic.php?p=1096#1096 | |
// specification: http://www.ietf.org/rfc/rfc4291.txt | |
URI.ip6_expression = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/; | |
// expression used is "gruber revised" (@gruber v2) determined to be the | |
// best solution in a regex-golf we did a couple of ages ago at | |
// * http://mathiasbynens.be/demo/url-regex | |
// * http://rodneyrehm.de/t/url-regex.html | |
URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig; | |
URI.findUri = { | |
// valid "scheme://" or "www." | |
start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi, | |
// everything up to the next whitespace | |
end: /[\s\r\n]|$/, | |
// trim trailing punctuation captured by end RegExp | |
trim: /[`!()\[\]{};:'".,<>?«»“”„‘’]+$/, | |
// balanced parens inclusion (), [], {}, <> | |
parens: /(\([^\)]*\)|\[[^\]]*\]|\{[^}]*\}|<[^>]*>)/g, | |
}; | |
// http://www.iana.org/assignments/uri-schemes.html | |
// http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports | |
URI.defaultPorts = { | |
http: '80', | |
https: '443', | |
ftp: '21', | |
gopher: '70', | |
ws: '80', | |
wss: '443' | |
}; | |
// list of protocols which always require a hostname | |
URI.hostProtocols = [ | |
'http', | |
'https' | |
]; | |
// allowed hostname characters according to RFC 3986 | |
// ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded | |
// I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - _ | |
URI.invalid_hostname_characters = /[^a-zA-Z0-9\.\-:_]/; | |
// map DOM Elements to their URI attribute | |
URI.domAttributes = { | |
'a': 'href', | |
'blockquote': 'cite', | |
'link': 'href', | |
'base': 'href', | |
'script': 'src', | |
'form': 'action', | |
'img': 'src', | |
'area': 'href', | |
'iframe': 'src', | |
'embed': 'src', | |
'source': 'src', | |
'track': 'src', | |
'input': 'src', // but only if type="image" | |
'audio': 'src', | |
'video': 'src' | |
}; | |
URI.getDomAttribute = function(node) { | |
if (!node || !node.nodeName) { | |
return undefined; | |
} | |
var nodeName = node.nodeName.toLowerCase(); | |
// <input> should only expose src for type="image" | |
if (nodeName === 'input' && node.type !== 'image') { | |
return undefined; | |
} | |
return URI.domAttributes[nodeName]; | |
}; | |
function escapeForDumbFirefox36(value) { | |
// https://github.com/medialize/URI.js/issues/91 | |
return escape(value); | |
} | |
// encoding / decoding according to RFC3986 | |
function strictEncodeURIComponent(string) { | |
// see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent | |
return encodeURIComponent(string) | |
.replace(/[!'()*]/g, escapeForDumbFirefox36) | |
.replace(/\*/g, '%2A'); | |
} | |
URI.encode = strictEncodeURIComponent; | |
URI.decode = decodeURIComponent; | |
URI.iso8859 = function() { | |
URI.encode = escape; | |
URI.decode = unescape; | |
}; | |
URI.unicode = function() { | |
URI.encode = strictEncodeURIComponent; | |
URI.decode = decodeURIComponent; | |
}; | |
URI.characters = { | |
pathname: { | |
encode: { | |
// RFC3986 2.1: For consistency, URI producers and normalizers should | |
// use uppercase hexadecimal digits for all percent-encodings. | |
expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig, | |
map: { | |
// -._~!'()* | |
'%24': '$', | |
'%26': '&', | |
'%2B': '+', | |
'%2C': ',', | |
'%3B': ';', | |
'%3D': '=', | |
'%3A': ':', | |
'%40': '@' | |
} | |
}, | |
decode: { | |
expression: /[\/\?#]/g, | |
map: { | |
'/': '%2F', | |
'?': '%3F', | |
'#': '%23' | |
} | |
} | |
}, | |
reserved: { | |
encode: { | |
// RFC3986 2.1: For consistency, URI producers and normalizers should | |
// use uppercase hexadecimal digits for all percent-encodings. | |
expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig, | |
map: { | |
// gen-delims | |
'%3A': ':', | |
'%2F': '/', | |
'%3F': '?', | |
'%23': '#', | |
'%5B': '[', | |
'%5D': ']', | |
'%40': '@', | |
// sub-delims | |
'%21': '!', | |
'%24': '$', | |
'%26': '&', | |
'%27': '\'', | |
'%28': '(', | |
'%29': ')', | |
'%2A': '*', | |
'%2B': '+', | |
'%2C': ',', | |
'%3B': ';', | |
'%3D': '=' | |
} | |
} | |
}, | |
urnpath: { | |
// The characters under `encode` are the characters called out by RFC 2141 as being acceptable | |
// for usage in a URN. RFC2141 also calls out "-", ".", and "_" as acceptable characters, but | |
// these aren't encoded by encodeURIComponent, so we don't have to call them out here. Also | |
// note that the colon character is not featured in the encoding map; this is because URI.js | |
// gives the colons in URNs semantic meaning as the delimiters of path segements, and so it | |
// should not appear unencoded in a segment itself. | |
// See also the note above about RFC3986 and capitalalized hex digits. | |
encode: { | |
expression: /%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig, | |
map: { | |
'%21': '!', | |
'%24': '$', | |
'%27': '\'', | |
'%28': '(', | |
'%29': ')', | |
'%2A': '*', | |
'%2B': '+', | |
'%2C': ',', | |
'%3B': ';', | |
'%3D': '=', | |
'%40': '@' | |
} | |
}, | |
// These characters are the characters called out by RFC2141 as "reserved" characters that | |
// should never appear in a URN, plus the colon character (see note above). | |
decode: { | |
expression: /[\/\?#:]/g, | |
map: { | |
'/': '%2F', | |
'?': '%3F', | |
'#': '%23', | |
':': '%3A' | |
} | |
} | |
} | |
}; | |
URI.encodeQuery = function(string, escapeQuerySpace) { | |
var escaped = URI.encode(string + ''); | |
if (escapeQuerySpace === undefined) { | |
escapeQuerySpace = URI.escapeQuerySpace; | |
} | |
return escapeQuerySpace ? escaped.replace(/%20/g, '+') : escaped; | |
}; | |
URI.decodeQuery = function(string, escapeQuerySpace) { | |
string += ''; | |
if (escapeQuerySpace === undefined) { | |
escapeQuerySpace = URI.escapeQuerySpace; | |
} | |
try { | |
return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string); | |
} catch(e) { | |
// we're not going to mess with weird encodings, | |
// give up and return the undecoded original string | |
// see https://github.com/medialize/URI.js/issues/87 | |
// see https://github.com/medialize/URI.js/issues/92 | |
return string; | |
} | |
}; | |
// generate encode/decode path functions | |
var _parts = {'encode':'encode', 'decode':'decode'}; | |
var _part; | |
var generateAccessor = function(_group, _part) { | |
return function(string) { | |
try { | |
return URI[_part](string + '').replace(URI.characters[_group][_part].expression, function(c) { | |
return URI.characters[_group][_part].map[c]; | |
}); | |
} catch (e) { | |
// we're not going to mess with weird encodings, | |
// give up and return the undecoded original string | |
// see https://github.com/medialize/URI.js/issues/87 | |
// see https://github.com/medialize/URI.js/issues/92 | |
return string; | |
} | |
}; | |
}; | |
for (_part in _parts) { | |
URI[_part + 'PathSegment'] = generateAccessor('pathname', _parts[_part]); | |
URI[_part + 'UrnPathSegment'] = generateAccessor('urnpath', _parts[_part]); | |
} | |
var generateSegmentedPathFunction = function(_sep, _codingFuncName, _innerCodingFuncName) { | |
return function(string) { | |
// Why pass in names of functions, rather than the function objects themselves? The | |
// definitions of some functions (but in particular, URI.decode) will occasionally change due | |
// to URI.js having ISO8859 and Unicode modes. Passing in the name and getting it will ensure | |
// that the functions we use here are "fresh". | |
var actualCodingFunc; | |
if (!_innerCodingFuncName) { | |
actualCodingFunc = URI[_codingFuncName]; | |
} else { | |
actualCodingFunc = function(string) { | |
return URI[_codingFuncName](URI[_innerCodingFuncName](string)); | |
}; | |
} | |
var segments = (string + '').split(_sep); | |
for (var i = 0, length = segments.length; i < length; i++) { | |
segments[i] = actualCodingFunc(segments[i]); | |
} | |
return segments.join(_sep); | |
}; | |
}; | |
// This takes place outside the above loop because we don't want, e.g., encodeUrnPath functions. | |
URI.decodePath = generateSegmentedPathFunction('/', 'decodePathSegment'); | |
URI.decodeUrnPath = generateSegmentedPathFunction(':', 'decodeUrnPathSegment'); | |
URI.recodePath = generateSegmentedPathFunction('/', 'encodePathSegment', 'decode'); | |
URI.recodeUrnPath = generateSegmentedPathFunction(':', 'encodeUrnPathSegment', 'decode'); | |
URI.encodeReserved = generateAccessor('reserved', 'encode'); | |
URI.parse = function(string, parts) { | |
var pos; | |
if (!parts) { | |
parts = { | |
preventInvalidHostname: URI.preventInvalidHostname | |
}; | |
} | |
// [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment] | |
// extract fragment | |
pos = string.indexOf('#'); | |
if (pos > -1) { | |
// escaping? | |
parts.fragment = string.substring(pos + 1) || null; | |
string = string.substring(0, pos); | |
} | |
// extract query | |
pos = string.indexOf('?'); | |
if (pos > -1) { | |
// escaping? | |
parts.query = string.substring(pos + 1) || null; | |
string = string.substring(0, pos); | |
} | |
// slashes and backslashes have lost all meaning for the web protocols (https, http, wss, ws) | |
string = string.replace(/^(https?|ftp|wss?)?:[/\\]*/, '$1://'); | |
// extract protocol | |
if (string.substring(0, 2) === '//') { | |
// relative-scheme | |
parts.protocol = null; | |
string = string.substring(2); | |
// extract "user:pass@host:port" | |
string = URI.parseAuthority(string, parts); | |
} else { | |
pos = string.indexOf(':'); | |
if (pos > -1) { | |
parts.protocol = string.substring(0, pos) || null; | |
if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) { | |
// : may be within the path | |
parts.protocol = undefined; | |
} else if (string.substring(pos + 1, pos + 3).replace(/\\/g, '/') === '//') { | |
string = string.substring(pos + 3); | |
// extract "user:pass@host:port" | |
string = URI.parseAuthority(string, parts); | |
} else { | |
string = string.substring(pos + 1); | |
parts.urn = true; | |
} | |
} | |
} | |
// what's left must be the path | |
parts.path = string; | |
// and we're done | |
return parts; | |
}; | |
URI.parseHost = function(string, parts) { | |
if (!string) { | |
string = ''; | |
} | |
// Copy chrome, IE, opera backslash-handling behavior. | |
// Back slashes before the query string get converted to forward slashes | |
// See: https://github.com/joyent/node/blob/386fd24f49b0e9d1a8a076592a404168faeecc34/lib/url.js#L115-L124 | |
// See: https://code.google.com/p/chromium/issues/detail?id=25916 | |
// https://github.com/medialize/URI.js/pull/233 | |
string = string.replace(/\\/g, '/'); | |
// extract host:port | |
var pos = string.indexOf('/'); | |
var bracketPos; | |
var t; | |
if (pos === -1) { | |
pos = string.length; | |
} | |
if (string.charAt(0) === '[') { | |
// IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6 | |
// I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts | |
// IPv6+port in the format [2001:db8::1]:80 (for the time being) | |
bracketPos = string.indexOf(']'); | |
parts.hostname = string.substring(1, bracketPos) || null; | |
parts.port = string.substring(bracketPos + 2, pos) || null; | |
if (parts.port === '/') { | |
parts.port = null; | |
} | |
} else { | |
var firstColon = string.indexOf(':'); | |
var firstSlash = string.indexOf('/'); | |
var nextColon = string.indexOf(':', firstColon + 1); | |
if (nextColon !== -1 && (firstSlash === -1 || nextColon < firstSlash)) { | |
// IPv6 host contains multiple colons - but no port | |
// this notation is actually not allowed by RFC 3986, but we're a liberal parser | |
parts.hostname = string.substring(0, pos) || null; | |
parts.port = null; | |
} else { | |
t = string.substring(0, pos).split(':'); | |
parts.hostname = t[0] || null; | |
parts.port = t[1] || null; | |
} | |
} | |
if (parts.hostname && string.substring(pos).charAt(0) !== '/') { | |
pos++; | |
string = '/' + string; | |
} | |
if (parts.preventInvalidHostname) { | |
URI.ensureValidHostname(parts.hostname, parts.protocol); | |
} | |
if (parts.port) { | |
URI.ensureValidPort(parts.port); | |
} | |
return string.substring(pos) || '/'; | |
}; | |
URI.parseAuthority = function(string, parts) { | |
string = URI.parseUserinfo(string, parts); | |
return URI.parseHost(string, parts); | |
}; | |
URI.parseUserinfo = function(string, parts) { | |
// extract username:password | |
var _string = string | |
var firstBackSlash = string.indexOf('\\'); | |
if (firstBackSlash !== -1) { | |
string = string.replace(/\\/g, '/') | |
} | |
var firstSlash = string.indexOf('/'); | |
var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1); | |
var t; | |
// authority@ must come before /path or \path | |
if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) { | |
t = string.substring(0, pos).split(':'); | |
parts.username = t[0] ? URI.decode(t[0]) : null; | |
t.shift(); | |
parts.password = t[0] ? URI.decode(t.join(':')) : null; | |
string = _string.substring(pos + 1); | |
} else { | |
parts.username = null; | |
parts.password = null; | |
} | |
return string; | |
}; | |
URI.parseQuery = function(string, escapeQuerySpace) { | |
if (!string) { | |
return {}; | |
} | |
// throw out the funky business - "?"[name"="value"&"]+ | |
string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, ''); | |
if (!string) { | |
return {}; | |
} | |
var items = {}; | |
var splits = string.split('&'); | |
var length = splits.length; | |
var v, name, value; | |
for (var i = 0; i < length; i++) { | |
v = splits[i].split('='); | |
name = URI.decodeQuery(v.shift(), escapeQuerySpace); | |
// no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters | |
value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null; | |
if (name === '__proto__') { | |
// ignore attempt at exploiting JavaScript internals | |
continue; | |
} else if (hasOwn.call(items, name)) { | |
if (typeof items[name] === 'string' || items[name] === null) { | |
items[name] = [items[name]]; | |
} | |
items[name].push(value); | |
} else { | |
items[name] = value; | |
} | |
} | |
return items; | |
}; | |
URI.build = function(parts) { | |
var t = ''; | |
var requireAbsolutePath = false | |
if (parts.protocol) { | |
t += parts.protocol + ':'; | |
} | |
if (!parts.urn && (t || parts.hostname)) { | |
t += '//'; | |
requireAbsolutePath = true | |
} | |
t += (URI.buildAuthority(parts) || ''); | |
if (typeof parts.path === 'string') { | |
if (parts.path.charAt(0) !== '/' && requireAbsolutePath) { | |
t += '/'; | |
} | |
t += parts.path; | |
} | |
if (typeof parts.query === 'string' && parts.query) { | |
t += '?' + parts.query; | |
} | |
if (typeof parts.fragment === 'string' && parts.fragment) { | |
t += '#' + parts.fragment; | |
} | |
return t; | |
}; | |
URI.buildHost = function(parts) { | |
var t = ''; | |
if (!parts.hostname) { | |
return ''; | |
} else if (URI.ip6_expression.test(parts.hostname)) { | |
t += '[' + parts.hostname + ']'; | |
} else { | |
t += parts.hostname; | |
} | |
if (parts.port) { | |
t += ':' + parts.port; | |
} | |
return t; | |
}; | |
URI.buildAuthority = function(parts) { | |
return URI.buildUserinfo(parts) + URI.buildHost(parts); | |
}; | |
URI.buildUserinfo = function(parts) { | |
var t = ''; | |
if (parts.username) { | |
t += URI.encode(parts.username); | |
} | |
if (parts.password) { | |
t += ':' + URI.encode(parts.password); | |
} | |
if (t) { | |
t += '@'; | |
} | |
return t; | |
}; | |
URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) { | |
// according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html | |
// being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed | |
// the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax! | |
// URI.js treats the query string as being application/x-www-form-urlencoded | |
// see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type | |
var t = ''; | |
var unique, key, i, length; | |
for (key in data) { | |
if (key === '__proto__') { | |
// ignore attempt at exploiting JavaScript internals | |
continue; | |
} else if (hasOwn.call(data, key)) { | |
if (isArray(data[key])) { | |
unique = {}; | |
for (i = 0, length = data[key].length; i < length; i++) { | |
if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) { | |
t += '&' + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace); | |
if (duplicateQueryParameters !== true) { | |
unique[data[key][i] + ''] = true; | |
} | |
} | |
} | |
} else if (data[key] !== undefined) { | |
t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace); | |
} | |
} | |
} | |
return t.substring(1); | |
}; | |
URI.buildQueryParameter = function(name, value, escapeQuerySpace) { | |
// http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded | |
// don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization | |
return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? '=' + URI.encodeQuery(value, escapeQuerySpace) : ''); | |
}; | |
URI.addQuery = function(data, name, value) { | |
if (typeof name === 'object') { | |
for (var key in name) { | |
if (hasOwn.call(name, key)) { | |
URI.addQuery(data, key, name[key]); | |
} | |
} | |
} else if (typeof name === 'string') { | |
if (data[name] === undefined) { | |
data[name] = value; | |
return; | |
} else if (typeof data[name] === 'string') { | |
data[name] = [data[name]]; | |
} | |
if (!isArray(value)) { | |
value = [value]; | |
} | |
data[name] = (data[name] || []).concat(value); | |
} else { | |
throw new TypeError('URI.addQuery() accepts an object, string as the name parameter'); | |
} | |
}; | |
URI.setQuery = function(data, name, value) { | |
if (typeof name === 'object') { | |
for (var key in name) { | |
if (hasOwn.call(name, key)) { | |
URI.setQuery(data, key, name[key]); | |
} | |
} | |
} else if (typeof name === 'string') { | |
data[name] = value === undefined ? null : value; | |
} else { | |
throw new TypeError('URI.setQuery() accepts an object, string as the name parameter'); | |
} | |
}; | |
URI.removeQuery = function(data, name, value) { | |
var i, length, key; | |
if (isArray(name)) { | |
for (i = 0, length = name.length; i < length; i++) { | |
data[name[i]] = undefined; | |
} | |
} else if (getType(name) === 'RegExp') { | |
for (key in data) { | |
if (name.test(key)) { | |
data[key] = undefined; | |
} | |
} | |
} else if (typeof name === 'object') { | |
for (key in name) { | |
if (hasOwn.call(name, key)) { | |
URI.removeQuery(data, key, name[key]); | |
} | |
} | |
} else if (typeof name === 'string') { | |
if (value !== undefined) { | |
if (getType(value) === 'RegExp') { | |
if (!isArray(data[name]) && value.test(data[name])) { | |
data[name] = undefined; | |
} else { | |
data[name] = filterArrayValues(data[name], value); | |
} | |
} else if (data[name] === String(value) && (!isArray(value) || value.length === 1)) { | |
data[name] = undefined; | |
} else if (isArray(data[name])) { | |
data[name] = filterArrayValues(data[name], value); | |
} | |
} else { | |
data[name] = undefined; | |
} | |
} else { | |
throw new TypeError('URI.removeQuery() accepts an object, string, RegExp as the first parameter'); | |
} | |
}; | |
URI.hasQuery = function(data, name, value, withinArray) { | |
switch (getType(name)) { | |
case 'String': | |
// Nothing to do here | |
break; | |
case 'RegExp': | |
for (var key in data) { | |
if (hasOwn.call(data, key)) { | |
if (name.test(key) && (value === undefined || URI.hasQuery(data, key, value))) { | |
return true; | |
} | |
} | |
} | |
return false; | |
case 'Object': | |
for (var _key in name) { | |
if (hasOwn.call(name, _key)) { | |
if (!URI.hasQuery(data, _key, name[_key])) { | |
return false; | |
} | |
} | |
} | |
return true; | |
default: | |
throw new TypeError('URI.hasQuery() accepts a string, regular expression or object as the name parameter'); | |
} | |
switch (getType(value)) { | |
case 'Undefined': | |
// true if exists (but may be empty) | |
return name in data; // data[name] !== undefined; | |
case 'Boolean': | |
// true if exists and non-empty | |
var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]); | |
return value === _booly; | |
case 'Function': | |
// allow complex comparison | |
return !!value(data[name], name, data); | |
case 'Array': | |
if (!isArray(data[name])) { | |
return false; | |
} | |
var op = withinArray ? arrayContains : arraysEqual; | |
return op(data[name], value); | |
case 'RegExp': | |
if (!isArray(data[name])) { | |
return Boolean(data[name] && data[name].match(value)); | |
} | |
if (!withinArray) { | |
return false; | |
} | |
return arrayContains(data[name], value); | |
case 'Number': | |
value = String(value); | |
/* falls through */ | |
case 'String': | |
if (!isArray(data[name])) { | |
return data[name] === value; | |
} | |
if (!withinArray) { | |
return false; | |
} | |
return arrayContains(data[name], value); | |
default: | |
throw new TypeError('URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter'); | |
} | |
}; | |
URI.joinPaths = function() { | |
var input = []; | |
var segments = []; | |
var nonEmptySegments = 0; | |
for (var i = 0; i < arguments.length; i++) { | |
var url = new URI(arguments[i]); | |
input.push(url); | |
var _segments = url.segment(); | |
for (var s = 0; s < _segments.length; s++) { | |
if (typeof _segments[s] === 'string') { | |
segments.push(_segments[s]); | |
} | |
if (_segments[s]) { | |
nonEmptySegments++; | |
} | |
} | |
} | |
if (!segments.length || !nonEmptySegments) { | |
return new URI(''); | |
} | |
var uri = new URI('').segment(segments); | |
if (input[0].path() === '' || input[0].path().slice(0, 1) === '/') { | |
uri.path('/' + uri.path()); | |
} | |
return uri.normalize(); | |
}; | |
URI.commonPath = function(one, two) { | |
var length = Math.min(one.length, two.length); | |
var pos; | |
// find first non-matching character | |
for (pos = 0; pos < length; pos++) { | |
if (one.charAt(pos) !== two.charAt(pos)) { | |
pos--; | |
break; | |
} | |
} | |
if (pos < 1) { | |
return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : ''; | |
} | |
// revert to last / | |
if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') { | |
pos = one.substring(0, pos).lastIndexOf('/'); | |
} | |
return one.substring(0, pos + 1); | |
}; | |
URI.withinString = function(string, callback, options) { | |
options || (options = {}); | |
var _start = options.start || URI.findUri.start; | |
var _end = options.end || URI.findUri.end; | |
var _trim = options.trim || URI.findUri.trim; | |
var _parens = options.parens || URI.findUri.parens; | |
var _attributeOpen = /[a-z0-9-]=["']?$/i; | |
_start.lastIndex = 0; | |
while (true) { | |
var match = _start.exec(string); | |
if (!match) { | |
break; | |
} | |
var start = match.index; | |
if (options.ignoreHtml) { | |
// attribut(e=["']?$) | |
var attributeOpen = string.slice(Math.max(start - 3, 0), start); | |
if (attributeOpen && _attributeOpen.test(attributeOpen)) { | |
continue; | |
} | |
} | |
var end = start + string.slice(start).search(_end); | |
var slice = string.slice(start, end); | |
// make sure we include well balanced parens | |
var parensEnd = -1; | |
while (true) { | |
var parensMatch = _parens.exec(slice); | |
if (!parensMatch) { | |
break; | |
} | |
var parensMatchEnd = parensMatch.index + parensMatch[0].length; | |
parensEnd = Math.max(parensEnd, parensMatchEnd); | |
} | |
if (parensEnd > -1) { | |
slice = slice.slice(0, parensEnd) + slice.slice(parensEnd).replace(_trim, ''); | |
} else { | |
slice = slice.replace(_trim, ''); | |
} | |
if (slice.length <= match[0].length) { | |
// the extract only contains the starting marker of a URI, | |
// e.g. "www" or "http://" | |
continue; | |
} | |
if (options.ignore && options.ignore.test(slice)) { | |
continue; | |
} | |
end = start + slice.length; | |
var result = callback(slice, start, end, string); | |
if (result === undefined) { | |
_start.lastIndex = end; | |
continue; | |
} | |
result = String(result); | |
string = string.slice(0, start) + result + string.slice(end); | |
_start.lastIndex = start + result.length; | |
} | |
_start.lastIndex = 0; | |
return string; | |
}; | |
URI.ensureValidHostname = function(v, protocol) { | |
// Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986) | |
// they are not part of DNS and therefore ignored by URI.js | |
var hasHostname = !!v; // not null and not an empty string | |
var hasProtocol = !!protocol; | |
var rejectEmptyHostname = false; | |
if (hasProtocol) { | |
rejectEmptyHostname = arrayContains(URI.hostProtocols, protocol); | |
} | |
if (rejectEmptyHostname && !hasHostname) { | |
throw new TypeError('Hostname cannot be empty, if protocol is ' + protocol); | |
} else if (v && v.match(URI.invalid_hostname_characters)) { | |
// test punycode | |
if (!punycode) { | |
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_] and Punycode.js is not available'); | |
} | |
if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) { | |
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_]'); | |
} | |
} | |
}; | |
URI.ensureValidPort = function (v) { | |
if (!v) { | |
return; | |
} | |
var port = Number(v); | |
if (isInteger(port) && (port > 0) && (port < 65536)) { | |
return; | |
} | |
throw new TypeError('Port "' + v + '" is not a valid port'); | |
}; | |
// noConflict | |
URI.noConflict = function(removeAll) { | |
if (removeAll) { | |
var unconflicted = { | |
URI: this.noConflict() | |
}; | |
if (root.URITemplate && typeof root.URITemplate.noConflict === 'function') { | |
unconflicted.URITemplate = root.URITemplate.noConflict(); | |
} | |
if (root.IPv6 && typeof root.IPv6.noConflict === 'function') { | |
unconflicted.IPv6 = root.IPv6.noConflict(); | |
} | |
if (root.SecondLevelDomains && typeof root.SecondLevelDomains.noConflict === 'function') { | |
unconflicted.SecondLevelDomains = root.SecondLevelDomains.noConflict(); | |
} | |
return unconflicted; | |
} else if (root.URI === this) { | |
root.URI = _URI; | |
} | |
return this; | |
}; | |
p.build = function(deferBuild) { | |
if (deferBuild === true) { | |
this._deferred_build = true; | |
} else if (deferBuild === undefined || this._deferred_build) { | |
this._string = URI.build(this._parts); | |
this._deferred_build = false; | |
} | |
return this; | |
}; | |
p.clone = function() { | |
return new URI(this); | |
}; | |
p.valueOf = p.toString = function() { | |
return this.build(false)._string; | |
}; | |
function generateSimpleAccessor(_part){ | |
return function(v, build) { | |
if (v === undefined) { | |
return this._parts[_part] || ''; | |
} else { | |
this._parts[_part] = v || null; | |
this.build(!build); | |
return this; | |
} | |
}; | |
} | |
function generatePrefixAccessor(_part, _key){ | |
return function(v, build) { | |
if (v === undefined) { | |
return this._parts[_part] || ''; | |
} else { | |
if (v !== null) { | |
v = v + ''; | |
if (v.charAt(0) === _key) { | |
v = v.substring(1); | |
} | |
} | |
this._parts[_part] = v; | |
this.build(!build); | |
return this; | |
} | |
}; | |
} | |
p.protocol = generateSimpleAccessor('protocol'); | |
p.username = generateSimpleAccessor('username'); | |
p.password = generateSimpleAccessor('password'); | |
p.hostname = generateSimpleAccessor('hostname'); | |
p.port = generateSimpleAccessor('port'); | |
p.query = generatePrefixAccessor('query', '?'); | |
p.fragment = generatePrefixAccessor('fragment', '#'); | |
p.search = function(v, build) { | |
var t = this.query(v, build); | |
return typeof t === 'string' && t.length ? ('?' + t) : t; | |
}; | |
p.hash = function(v, build) { | |
var t = this.fragment(v, build); | |
return typeof t === 'string' && t.length ? ('#' + t) : t; | |
}; | |
p.pathname = function(v, build) { | |
if (v === undefined || v === true) { | |
var res = this._parts.path || (this._parts.hostname ? '/' : ''); | |
return v ? (this._parts.urn ? URI.decodeUrnPath : URI.decodePath)(res) : res; | |
} else { | |
if (this._parts.urn) { | |
this._parts.path = v ? URI.recodeUrnPath(v) : ''; | |
} else { | |
this._parts.path = v ? URI.recodePath(v) : '/'; | |
} | |
this.build(!build); | |
return this; | |
} | |
}; | |
p.path = p.pathname; | |
p.href = function(href, build) { | |
var key; | |
if (href === undefined) { | |
return this.toString(); | |
} | |
this._string = ''; | |
this._parts = URI._parts(); | |
var _URI = href instanceof URI; | |
var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname); | |
if (href.nodeName) { | |
var attribute = URI.getDomAttribute(href); | |
href = href[attribute] || ''; | |
_object = false; | |
} | |
// window.location is reported to be an object, but it's not the sort | |
// of object we're looking for: | |
// * location.protocol ends with a colon | |
// * location.query != object.search | |
// * location.hash != object.fragment | |
// simply serializing the unknown object should do the trick | |
// (for location, not for everything...) | |
if (!_URI && _object && href.pathname !== undefined) { | |
href = href.toString(); | |
} | |
if (typeof href === 'string' || href instanceof String) { | |
this._parts = URI.parse(String(href), this._parts); | |
} else if (_URI || _object) { | |
var src = _URI ? href._parts : href; | |
for (key in src) { | |
if (key === 'query') { continue; } | |
if (hasOwn.call(this._parts, key)) { | |
this._parts[key] = src[key]; | |
} | |
} | |
if (src.query) { | |
this.query(src.query, false); | |
} | |
} else { | |
throw new TypeError('invalid input'); | |
} | |
this.build(!build); | |
return this; | |
}; | |
// identification accessors | |
p.is = function(what) { | |
var ip = false; | |
var ip4 = false; | |
var ip6 = false; | |
var name = false; | |
var sld = false; | |
var idn = false; | |
var punycode = false; | |
var relative = !this._parts.urn; | |
if (this._parts.hostname) { | |
relative = false; | |
ip4 = URI.ip4_expression.test(this._parts.hostname); | |
ip6 = URI.ip6_expression.test(this._parts.hostname); | |
ip = ip4 || ip6; | |
name = !ip; | |
sld = name && SLD && SLD.has(this._parts.hostname); | |
idn = name && URI.idn_expression.test(this._parts.hostname); | |
punycode = name && URI.punycode_expression.test(this._parts.hostname); | |
} | |
switch (what.toLowerCase()) { | |
case 'relative': | |
return relative; | |
case 'absolute': | |
return !relative; | |
// hostname identification | |
case 'domain': | |
case 'name': | |
return name; | |
case 'sld': | |
return sld; | |
case 'ip': | |
return ip; | |
case 'ip4': | |
case 'ipv4': | |
case 'inet4': | |
return ip4; | |
case 'ip6': | |
case 'ipv6': | |
case 'inet6': | |
return ip6; | |
case 'idn': | |
return idn; | |
case 'url': | |
return !this._parts.urn; | |
case 'urn': | |
return !!this._parts.urn; | |
case 'punycode': | |
return punycode; | |
} | |
return null; | |
}; | |
// component specific input validation | |
var _protocol = p.protocol; | |
var _port = p.port; | |
var _hostname = p.hostname; | |
p.protocol = function(v, build) { | |
if (v) { | |
// accept trailing :// | |
v = v.replace(/:(\/\/)?$/, ''); | |
if (!v.match(URI.protocol_expression)) { | |
throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]'); | |
} | |
} | |
return _protocol.call(this, v, build); | |
}; | |
p.scheme = p.protocol; | |
p.port = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
if (v !== undefined) { | |
if (v === 0) { | |
v = null; | |
} | |
if (v) { | |
v += ''; | |
if (v.charAt(0) === ':') { | |
v = v.substring(1); | |
} | |
URI.ensureValidPort(v); | |
} | |
} | |
return _port.call(this, v, build); | |
}; | |
p.hostname = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
if (v !== undefined) { | |
var x = { preventInvalidHostname: this._parts.preventInvalidHostname }; | |
var res = URI.parseHost(v, x); | |
if (res !== '/') { | |
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); | |
} | |
v = x.hostname; | |
if (this._parts.preventInvalidHostname) { | |
URI.ensureValidHostname(v, this._parts.protocol); | |
} | |
} | |
return _hostname.call(this, v, build); | |
}; | |
// compound accessors | |
p.origin = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
if (v === undefined) { | |
var protocol = this.protocol(); | |
var authority = this.authority(); | |
if (!authority) { | |
return ''; | |
} | |
return (protocol ? protocol + '://' : '') + this.authority(); | |
} else { | |
var origin = URI(v); | |
this | |
.protocol(origin.protocol()) | |
.authority(origin.authority()) | |
.build(!build); | |
return this; | |
} | |
}; | |
p.host = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
if (v === undefined) { | |
return this._parts.hostname ? URI.buildHost(this._parts) : ''; | |
} else { | |
var res = URI.parseHost(v, this._parts); | |
if (res !== '/') { | |
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); | |
} | |
this.build(!build); | |
return this; | |
} | |
}; | |
p.authority = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
if (v === undefined) { | |
return this._parts.hostname ? URI.buildAuthority(this._parts) : ''; | |
} else { | |
var res = URI.parseAuthority(v, this._parts); | |
if (res !== '/') { | |
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); | |
} | |
this.build(!build); | |
return this; | |
} | |
}; | |
p.userinfo = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
if (v === undefined) { | |
var t = URI.buildUserinfo(this._parts); | |
return t ? t.substring(0, t.length -1) : t; | |
} else { | |
if (v[v.length-1] !== '@') { | |
v += '@'; | |
} | |
URI.parseUserinfo(v, this._parts); | |
this.build(!build); | |
return this; | |
} | |
}; | |
p.resource = function(v, build) { | |
var parts; | |
if (v === undefined) { | |
return this.path() + this.search() + this.hash(); | |
} | |
parts = URI.parse(v); | |
this._parts.path = parts.path; | |
this._parts.query = parts.query; | |
this._parts.fragment = parts.fragment; | |
this.build(!build); | |
return this; | |
}; | |
// fraction accessors | |
p.subdomain = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
// convenience, return "www" from "www.example.org" | |
if (v === undefined) { | |
if (!this._parts.hostname || this.is('IP')) { | |
return ''; | |
} | |
// grab domain and add another segment | |
var end = this._parts.hostname.length - this.domain().length - 1; | |
return this._parts.hostname.substring(0, end) || ''; | |
} else { | |
var e = this._parts.hostname.length - this.domain().length; | |
var sub = this._parts.hostname.substring(0, e); | |
var replace = new RegExp('^' + escapeRegEx(sub)); | |
if (v && v.charAt(v.length - 1) !== '.') { | |
v += '.'; | |
} | |
if (v.indexOf(':') !== -1) { | |
throw new TypeError('Domains cannot contain colons'); | |
} | |
if (v) { | |
URI.ensureValidHostname(v, this._parts.protocol); | |
} | |
this._parts.hostname = this._parts.hostname.replace(replace, v); | |
this.build(!build); | |
return this; | |
} | |
}; | |
p.domain = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
if (typeof v === 'boolean') { | |
build = v; | |
v = undefined; | |
} | |
// convenience, return "example.org" from "www.example.org" | |
if (v === undefined) { | |
if (!this._parts.hostname || this.is('IP')) { | |
return ''; | |
} | |
// if hostname consists of 1 or 2 segments, it must be the domain | |
var t = this._parts.hostname.match(/\./g); | |
if (t && t.length < 2) { | |
return this._parts.hostname; | |
} | |
// grab tld and add another segment | |
var end = this._parts.hostname.length - this.tld(build).length - 1; | |
end = this._parts.hostname.lastIndexOf('.', end -1) + 1; | |
return this._parts.hostname.substring(end) || ''; | |
} else { | |
if (!v) { | |
throw new TypeError('cannot set domain empty'); | |
} | |
if (v.indexOf(':') !== -1) { | |
throw new TypeError('Domains cannot contain colons'); | |
} | |
URI.ensureValidHostname(v, this._parts.protocol); | |
if (!this._parts.hostname || this.is('IP')) { | |
this._parts.hostname = v; | |
} else { | |
var replace = new RegExp(escapeRegEx(this.domain()) + '$'); | |
this._parts.hostname = this._parts.hostname.replace(replace, v); | |
} | |
this.build(!build); | |
return this; | |
} | |
}; | |
p.tld = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
if (typeof v === 'boolean') { | |
build = v; | |
v = undefined; | |
} | |
// return "org" from "www.example.org" | |
if (v === undefined) { | |
if (!this._parts.hostname || this.is('IP')) { | |
return ''; | |
} | |
var pos = this._parts.hostname.lastIndexOf('.'); | |
var tld = this._parts.hostname.substring(pos + 1); | |
if (build !== true && SLD && SLD.list[tld.toLowerCase()]) { | |
return SLD.get(this._parts.hostname) || tld; | |
} | |
return tld; | |
} else { | |
var replace; | |
if (!v) { | |
throw new TypeError('cannot set TLD empty'); | |
} else if (v.match(/[^a-zA-Z0-9-]/)) { | |
if (SLD && SLD.is(v)) { | |
replace = new RegExp(escapeRegEx(this.tld()) + '$'); | |
this._parts.hostname = this._parts.hostname.replace(replace, v); | |
} else { | |
throw new TypeError('TLD "' + v + '" contains characters other than [A-Z0-9]'); | |
} | |
} else if (!this._parts.hostname || this.is('IP')) { | |
throw new ReferenceError('cannot set TLD on non-domain host'); | |
} else { | |
replace = new RegExp(escapeRegEx(this.tld()) + '$'); | |
this._parts.hostname = this._parts.hostname.replace(replace, v); | |
} | |
this.build(!build); | |
return this; | |
} | |
}; | |
p.directory = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
if (v === undefined || v === true) { | |
if (!this._parts.path && !this._parts.hostname) { | |
return ''; | |
} | |
if (this._parts.path === '/') { | |
return '/'; | |
} | |
var end = this._parts.path.length - this.filename().length - 1; | |
var res = this._parts.path.substring(0, end) || (this._parts.hostname ? '/' : ''); | |
return v ? URI.decodePath(res) : res; | |
} else { | |
var e = this._parts.path.length - this.filename().length; | |
var directory = this._parts.path.substring(0, e); | |
var replace = new RegExp('^' + escapeRegEx(directory)); | |
// fully qualifier directories begin with a slash | |
if (!this.is('relative')) { | |
if (!v) { | |
v = '/'; | |
} | |
if (v.charAt(0) !== '/') { | |
v = '/' + v; | |
} | |
} | |
// directories always end with a slash | |
if (v && v.charAt(v.length - 1) !== '/') { | |
v += '/'; | |
} | |
v = URI.recodePath(v); | |
this._parts.path = this._parts.path.replace(replace, v); | |
this.build(!build); | |
return this; | |
} | |
}; | |
p.filename = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
if (typeof v !== 'string') { | |
if (!this._parts.path || this._parts.path === '/') { | |
return ''; | |
} | |
var pos = this._parts.path.lastIndexOf('/'); | |
var res = this._parts.path.substring(pos+1); | |
return v ? URI.decodePathSegment(res) : res; | |
} else { | |
var mutatedDirectory = false; | |
if (v.charAt(0) === '/') { | |
v = v.substring(1); | |
} | |
if (v.match(/\.?\//)) { | |
mutatedDirectory = true; | |
} | |
var replace = new RegExp(escapeRegEx(this.filename()) + '$'); | |
v = URI.recodePath(v); | |
this._parts.path = this._parts.path.replace(replace, v); | |
if (mutatedDirectory) { | |
this.normalizePath(build); | |
} else { | |
this.build(!build); | |
} | |
return this; | |
} | |
}; | |
p.suffix = function(v, build) { | |
if (this._parts.urn) { | |
return v === undefined ? '' : this; | |
} | |
if (v === undefined || v === true) { | |
if (!this._parts.path || this._parts.path === '/') { | |
return ''; | |
} | |
var filename = this.filename(); | |
var pos = filename.lastIndexOf('.'); | |
var s, res; | |
if (pos === -1) { | |
return ''; | |
} | |
// suffix may only contain alnum characters (yup, I made this up.) | |
s = filename.substring(pos+1); | |
res = (/^[a-z0-9%]+$/i).test(s) ? s : ''; | |
return v ? URI.decodePathSegment(res) : res; | |
} else { | |
if (v.charAt(0) === '.') { | |
v = v.substring(1); | |
} | |
var suffix = this.suffix(); | |
var replace; | |
if (!suffix) { | |
if (!v) { | |
return this; | |
} | |
this._parts.path += '.' + URI.recodePath(v); | |
} else if (!v) { | |
replace = new RegExp(escapeRegEx('.' + suffix) + '$'); | |
} else { | |
replace = new RegExp(escapeRegEx(suffix) + '$'); | |
} | |
if (replace) { | |
v = URI.recodePath(v); | |
this._parts.path = this._parts.path.replace(replace, v); | |
} | |
this.build(!build); | |
return this; | |
} | |
}; | |
p.segment = function(segment, v, build) { | |
var separator = this._parts.urn ? ':' : '/'; | |
var path = this.path(); | |
var absolute = path.substring(0, 1) === '/'; | |
var segments = path.split(separator); | |
if (segment !== undefined && typeof segment !== 'number') { | |
build = v; | |
v = segment; | |
segment = undefined; | |
} | |
if (segment !== undefined && typeof segment !== 'number') { | |
throw new Error('Bad segment "' + segment + '", must be 0-based integer'); | |
} | |
if (absolute) { | |
segments.shift(); | |
} | |
if (segment < 0) { | |
// allow negative indexes to address from the end | |
segment = Math.max(segments.length + segment, 0); | |
} | |
if (v === undefined) { | |
/*jshint laxbreak: true */ | |
return segment === undefined | |
? segments | |
: segments[segment]; | |
/*jshint laxbreak: false */ | |
} else if (segment === null || segments[segment] === undefined) { | |
if (isArray(v)) { | |
segments = []; | |
// collapse empty elements within array | |
for (var i=0, l=v.length; i < l; i++) { | |
if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) { | |
continue; | |
} | |
if (segments.length && !segments[segments.length -1].length) { | |
segments.pop(); | |
} | |
segments.push(trimSlashes(v[i])); | |
} | |
} else if (v || typeof v === 'string') { | |
v = trimSlashes(v); | |
if (segments[segments.length -1] === '') { | |
// empty trailing elements have to be overwritten | |
// to prevent results such as /foo//bar | |
segments[segments.length -1] = v; | |
} else { | |
segments.push(v); | |
} | |
} | |
} else { | |
if (v) { | |
segments[segment] = trimSlashes(v); | |
} else { | |
segments.splice(segment, 1); | |
} | |
} | |
if (absolute) { | |
segments.unshift(''); | |
} | |
return this.path(segments.join(separator), build); | |
}; | |
p.segmentCoded = function(segment, v, build) { | |
var segments, i, l; | |
if (typeof segment !== 'number') { | |
build = v; | |
v = segment; | |
segment = undefined; | |
} | |
if (v === undefined) { | |
segments = this.segment(segment, v, build); | |
if (!isArray(segments)) { | |
segments = segments !== undefined ? URI.decode(segments) : undefined; | |
} else { | |
for (i = 0, l = segments.length; i < l; i++) { | |
segments[i] = URI.decode(segments[i]); | |
} | |
} | |
return segments; | |
} | |
if (!isArray(v)) { | |
v = (typeof v === 'string' || v instanceof String) ? URI.encode(v) : v; | |
} else { | |
for (i = 0, l = v.length; i < l; i++) { | |
v[i] = URI.encode(v[i]); | |
} | |
} | |
return this.segment(segment, v, build); | |
}; | |
// mutating query string | |
var q = p.query; | |
p.query = function(v, build) { | |
if (v === true) { | |
return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); | |
} else if (typeof v === 'function') { | |
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); | |
var result = v.call(this, data); | |
this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); | |
this.build(!build); | |
return this; | |
} else if (v !== undefined && typeof v !== 'string') { | |
this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); | |
this.build(!build); | |
return this; | |
} else { | |
return q.call(this, v, build); | |
} | |
}; | |
p.setQuery = function(name, value, build) { | |
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); | |
if (typeof name === 'string' || name instanceof String) { | |
data[name] = value !== undefined ? value : null; | |
} else if (typeof name === 'object') { | |
for (var key in name) { | |
if (hasOwn.call(name, key)) { | |
data[key] = name[key]; | |
} | |
} | |
} else { | |
throw new TypeError('URI.addQuery() accepts an object, string as the name parameter'); | |
} | |
this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); | |
if (typeof name !== 'string') { | |
build = value; | |
} | |
this.build(!build); | |
return this; | |
}; | |
p.addQuery = function(name, value, build) { | |
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); | |
URI.addQuery(data, name, value === undefined ? null : value); | |
this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); | |
if (typeof name !== 'string') { | |
build = value; | |
} | |
this.build(!build); | |
return this; | |
}; | |
p.removeQuery = function(name, value, build) { | |
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); | |
URI.removeQuery(data, name, value); | |
this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); | |
if (typeof name !== 'string') { | |
build = value; | |
} | |
this.build(!build); | |
return this; | |
}; | |
p.hasQuery = function(name, value, withinArray) { | |
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); | |
return URI.hasQuery(data, name, value, withinArray); | |
}; | |
p.setSearch = p.setQuery; | |
p.addSearch = p.addQuery; | |
p.removeSearch = p.removeQuery; | |
p.hasSearch = p.hasQuery; | |
// sanitizing URLs | |
p.normalize = function() { | |
if (this._parts.urn) { | |
return this | |
.normalizeProtocol(false) | |
.normalizePath(false) | |
.normalizeQuery(false) | |
.normalizeFragment(false) | |
.build(); | |
} | |
return this | |
.normalizeProtocol(false) | |
.normalizeHostname(false) | |
.normalizePort(false) | |
.normalizePath(false) | |
.normalizeQuery(false) | |
.normalizeFragment(false) | |
.build(); | |
}; | |
p.normalizeProtocol = function(build) { | |
if (typeof this._parts.protocol === 'string') { | |
this._parts.protocol = this._parts.protocol.toLowerCase(); | |
this.build(!build); | |
} | |
return this; | |
}; | |
p.normalizeHostname = function(build) { | |
if (this._parts.hostname) { | |
if (this.is('IDN') && punycode) { | |
this._parts.hostname = punycode.toASCII(this._parts.hostname); | |
} else if (this.is('IPv6') && IPv6) { | |
this._parts.hostname = IPv6.best(this._parts.hostname); | |
} | |
this._parts.hostname = this._parts.hostname.toLowerCase(); | |
this.build(!build); | |
} | |
return this; | |
}; | |
p.normalizePort = function(build) { | |
// remove port of it's the protocol's default | |
if (typeof this._parts.protocol === 'string' && this._parts.port === URI.defaultPorts[this._parts.protocol]) { | |
this._parts.port = null; | |
this.build(!build); | |
} | |
return this; | |
}; | |
p.normalizePath = function(build) { | |
var _path = this._parts.path; | |
if (!_path) { | |
return this; | |
} | |
if (this._parts.urn) { | |
this._parts.path = URI.recodeUrnPath(this._parts.path); | |
this.build(!build); | |
return this; | |
} | |
if (this._parts.path === '/') { | |
return this; | |
} | |
_path = URI.recodePath(_path); | |
var _was_relative; | |
var _leadingParents = ''; | |
var _parent, _pos; | |
// handle relative paths | |
if (_path.charAt(0) !== '/') { | |
_was_relative = true; | |
_path = '/' + _path; | |
} | |
// handle relative files (as opposed to directories) | |
if (_path.slice(-3) === '/..' || _path.slice(-2) === '/.') { | |
_path += '/'; | |
} | |
// resolve simples | |
_path = _path | |
.replace(/(\/(\.\/)+)|(\/\.$)/g, '/') | |
.replace(/\/{2,}/g, '/'); | |
// remember leading parents | |
if (_was_relative) { | |
_leadingParents = _path.substring(1).match(/^(\.\.\/)+/) || ''; | |
if (_leadingParents) { | |
_leadingParents = _leadingParents[0]; | |
} | |
} | |
// resolve parents | |
while (true) { | |
_parent = _path.search(/\/\.\.(\/|$)/); | |
if (_parent === -1) { | |
// no more ../ to resolve | |
break; | |
} else if (_parent === 0) { | |
// top level cannot be relative, skip it | |
_path = _path.substring(3); | |
continue; | |
} | |
_pos = _path.substring(0, _parent).lastIndexOf('/'); | |
if (_pos === -1) { | |
_pos = _parent; | |
} | |
_path = _path.substring(0, _pos) + _path.substring(_parent + 3); | |
} | |
// revert to relative | |
if (_was_relative && this.is('relative')) { | |
_path = _leadingParents + _path.substring(1); | |
} | |
this._parts.path = _path; | |
this.build(!build); | |
return this; | |
}; | |
p.normalizePathname = p.normalizePath; | |
p.normalizeQuery = function(build) { | |
if (typeof this._parts.query === 'string') { | |
if (!this._parts.query.length) { | |
this._parts.query = null; | |
} else { | |
this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace)); | |
} | |
this.build(!build); | |
} | |
return this; | |
}; | |
p.normalizeFragment = function(build) { | |
if (!this._parts.fragment) { | |
this._parts.fragment = null; | |
this.build(!build); | |
} | |
return this; | |
}; | |
p.normalizeSearch = p.normalizeQuery; | |
p.normalizeHash = p.normalizeFragment; | |
p.iso8859 = function() { | |
// expect unicode input, iso8859 output | |
var e = URI.encode; | |
var d = URI.decode; | |
URI.encode = escape; | |
URI.decode = decodeURIComponent; | |
try { | |
this.normalize(); | |
} finally { | |
URI.encode = e; | |
URI.decode = d; | |
} | |
return this; | |
}; | |
p.unicode = function() { | |
// expect iso8859 input, unicode output | |
var e = URI.encode; | |
var d = URI.decode; | |
URI.encode = strictEncodeURIComponent; | |
URI.decode = unescape; | |
try { | |
this.normalize(); | |
} finally { | |
URI.encode = e; | |
URI.decode = d; | |
} | |
return this; | |
}; | |
p.readable = function() { | |
var uri = this.clone(); | |
// removing username, password, because they shouldn't be displayed according to RFC 3986 | |
uri.username('').password('').normalize(); | |
var t = ''; | |
if (uri._parts.protocol) { | |
t += uri._parts.protocol + '://'; | |
} | |
if (uri._parts.hostname) { | |
if (uri.is('punycode') && punycode) { | |
t += punycode.toUnicode(uri._parts.hostname); | |
if (uri._parts.port) { | |
t += ':' + uri._parts.port; | |
} | |
} else { | |
t += uri.host(); | |
} | |
} | |
if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') { | |
t += '/'; | |
} | |
t += uri.path(true); | |
if (uri._parts.query) { | |
var q = ''; | |
for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) { | |
var kv = (qp[i] || '').split('='); | |
q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace) | |
.replace(/&/g, '%26'); | |
if (kv[1] !== undefined) { | |
q += '=' + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace) | |
.replace(/&/g, '%26'); | |
} | |
} | |
t += '?' + q.substring(1); | |
} | |
t += URI.decodeQuery(uri.hash(), true); | |
return t; | |
}; | |
// resolving relative and absolute URLs | |
p.absoluteTo = function(base) { | |
var resolved = this.clone(); | |
var properties = ['protocol', 'username', 'password', 'hostname', 'port']; | |
var basedir, i, p; | |
if (this._parts.urn) { | |
throw new Error('URNs do not have any generally defined hierarchical components'); | |
} | |
if (!(base instanceof URI)) { | |
base = new URI(base); | |
} | |
if (resolved._parts.protocol) { | |
// Directly returns even if this._parts.hostname is empty. | |
return resolved; | |
} else { | |
resolved._parts.protocol = base._parts.protocol; | |
} | |
if (this._parts.hostname) { | |
return resolved; | |
} | |
for (i = 0; (p = properties[i]); i++) { | |
resolved._parts[p] = base._parts[p]; | |
} | |
if (!resolved._parts.path) { | |
resolved._parts.path = base._parts.path; | |
if (!resolved._parts.query) { | |
resolved._parts.query = base._parts.query; | |
} | |
} else { | |
if (resolved._parts.path.substring(-2) === '..') { | |
resolved._parts.path += '/'; | |
} | |
if (resolved.path().charAt(0) !== '/') { | |
basedir = base.directory(); | |
basedir = basedir ? basedir : base.path().indexOf('/') === 0 ? '/' : ''; | |
resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path; | |
resolved.normalizePath(); | |
} | |
} | |
resolved.build(); | |
return resolved; | |
}; | |
p.relativeTo = function(base) { | |
var relative = this.clone().normalize(); | |
var relativeParts, baseParts, common, relativePath, basePath; | |
if (relative._parts.urn) { | |
throw new Error('URNs do not have any generally defined hierarchical components'); | |
} | |
base = new URI(base).normalize(); | |
relativeParts = relative._parts; | |
baseParts = base._parts; | |
relativePath = relative.path(); | |
basePath = base.path(); | |
if (relativePath.charAt(0) !== '/') { | |
throw new Error('URI is already relative'); | |
} | |
if (basePath.charAt(0) !== '/') { | |
throw new Error('Cannot calculate a URI relative to another relative URI'); | |
} | |
if (relativeParts.protocol === baseParts.protocol) { | |
relativeParts.protocol = null; | |
} | |
if (relativeParts.username !== baseParts.username || relativeParts.password !== baseParts.password) { | |
return relative.build(); | |
} | |
if (relativeParts.protocol !== null || relativeParts.username !== null || relativeParts.password !== null) { | |
return relative.build(); | |
} | |
if (relativeParts.hostname === baseParts.hostname && relativeParts.port === baseParts.port) { | |
relativeParts.hostname = null; | |
relativeParts.port = null; | |
} else { | |
return relative.build(); | |
} | |
if (relativePath === basePath) { | |
relativeParts.path = ''; | |
return relative.build(); | |
} | |
// determine common sub path | |
common = URI.commonPath(relativePath, basePath); | |
// If the paths have nothing in common, return a relative URL with the absolute path. | |
if (!common) { | |
return relative.build(); | |
} | |
var parents = baseParts.path | |
.substring(common.length) | |
.replace(/[^\/]*$/, '') | |
.replace(/.*?\//g, '../'); | |
relativeParts.path = (parents + relativeParts.path.substring(common.length)) || './'; | |
return relative.build(); | |
}; | |
// comparing URIs | |
p.equals = function(uri) { | |
var one = this.clone(); | |
var two = new URI(uri); | |
var one_map = {}; | |
var two_map = {}; | |
var checked = {}; | |
var one_query, two_query, key; | |
one.normalize(); | |
two.normalize(); | |
// exact match | |
if (one.toString() === two.toString()) { | |
return true; | |
} | |
// extract query string | |
one_query = one.query(); | |
two_query = two.query(); | |
one.query(''); | |
two.query(''); | |
// definitely not equal if not even non-query parts match | |
if (one.toString() !== two.toString()) { | |
return false; | |
} | |
// query parameters have the same length, even if they're permuted | |
if (one_query.length !== two_query.length) { | |
return false; | |
} | |
one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace); | |
two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace); | |
for (key in one_map) { | |
if (hasOwn.call(one_map, key)) { | |
if (!isArray(one_map[key])) { | |
if (one_map[key] !== two_map[key]) { | |
return false; | |
} | |
} else if (!arraysEqual(one_map[key], two_map[key])) { | |
return false; | |
} | |
checked[key] = true; | |
} | |
} | |
for (key in two_map) { | |
if (hasOwn.call(two_map, key)) { | |
if (!checked[key]) { | |
// two contains a parameter not present in one | |
return false; | |
} | |
} | |
} | |
return true; | |
}; | |
// state | |
p.preventInvalidHostname = function(v) { | |
this._parts.preventInvalidHostname = !!v; | |
return this; | |
}; | |
p.duplicateQueryParameters = function(v) { | |
this._parts.duplicateQueryParameters = !!v; | |
return this; | |
}; | |
p.escapeQuerySpace = function(v) { | |
this._parts.escapeQuerySpace = !!v; | |
return this; | |
}; | |
return URI; | |
})); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 'View on Flickriver' Greasemonkey script v1.4 | |
// | |
// Written by Alex Sirota (http://iosart.com/) | |
// | |
// Copyright Alex Sirota (c) 2007-2010 All Rights Reserved | |
// | |
// | |
(function () { | |
function g(t, e) { | |
if (typeof e === 'undefined') { | |
e = document; | |
} | |
const u = document.evaluate("//*[@class='" + t + "']", e, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); | |
return u.snapshotLength > 0 ? u.snapshotItem(0) : null; | |
} | |
function m(e) { | |
if ( | |
e.match(/photos\/([^/]+)\/?$/) || | |
e.match(/photos\/([^/]+)\/page[0-9]+\/?$/) || | |
e.match(/people\/([^/]+)\/?$/) || | |
e.match(/photos\/([^/]+)\/popular-interesting\/?$/) || | |
e.match(/photos\/([^/]+)\/sets\/?$/) || | |
e.match(/photos\/([^/]+)\/tags\/?$/) || | |
e.match(/photos\/([^/]+)\/sets\/([^/]+)\/?$/) || | |
e.match(/photos\/([^/]+)\/tags\/([^/]+)\/?$/) || | |
e.match(/photos\/([^/]+)\/favorites\/?$/) || | |
e.match(/photos\/([^/]+)\/friends\/?$/) | |
) { | |
return true; | |
} | |
return false; | |
} | |
function k(e) { | |
if (e.match(/photos\/?$/) || e.match(/photos\/tags\/([^/]+)\/?$/) || e.match(/photos\/tags\/interesting\/?$/)) { | |
return true; | |
} | |
return false; | |
} | |
function n(e) { | |
if (e.match(/photos\/([^/]+)\/([0-9]+)\/?$/) || e.match(/photos\/([^/]+)\/([0-9]+)\/in\/[^/]+\/?$/)) { | |
return true; | |
} | |
return false; | |
} | |
function c(e) { | |
if (e.match(/groups\/([^/]+)\/?$/) || e.match(/groups\/([^/]+)\/pool\/page[0-9]+\/?$/) || e.match(/groups\/([^/]+)\/pool\/?$/)) { | |
return true; | |
} | |
return false; | |
} | |
function s(e) { | |
if (e.match(/explore\/?$/)) { | |
return true; | |
} | |
return false; | |
} | |
function b(e) { | |
const u = g('Extras'); | |
e = e.cloneNode(true); | |
if (u) { | |
const t = document.createElement('style'); | |
t.innerHTML = | |
'.flickriver-extras-link, .flickriver-extras-link:visited, .flickriver-extras-link:link { font-size: 11px; font-weight: bold; color: #A1A1A1;}\n.flickriver-extras-link:hover { background: none; color: #0259C4 }'; | |
e.className = 'flickriver-extras-link'; | |
const v = document.createElement('div'); | |
if (document.getElementById('SlideShowButton') && document.getElementById('ShareButton')) { | |
v.style.textAlign = 'center'; | |
} else { | |
v.style.textAlign = 'right'; | |
if (document.getElementById('ShareButton')) { | |
v.style.paddingRight = '2px'; | |
} else { | |
v.style.paddingRight = '11px'; | |
} | |
} | |
u.appendChild(t); | |
v.appendChild(e); | |
u.appendChild(v); | |
return true; | |
} | |
return false; | |
} | |
function p() { | |
const e = g('photoDescription'); | |
if (!e) { | |
return true; | |
} | |
if (e.innerHTML.match(/flickriver\.com/)) { | |
return false; | |
} | |
return true; | |
} | |
function j(t) { | |
const e = document.getElementById('DiscussPhoto'); | |
if (!e) { | |
return false; | |
} | |
const u = document.createElement('p'); | |
u.appendChild(t); | |
e.parentNode.insertBefore(u, e); | |
return true; | |
} | |
function l(t) { | |
const e = document.getElementById('Hint'); | |
if (!e) { | |
return false; | |
} | |
const u = document.createElement('p'); | |
t.style.fontWeight = 'bold'; | |
u.appendChild(t); | |
e.appendChild(u); | |
return true; | |
} | |
function i(v) { | |
const e = document.getElementById('SubNav'); | |
if (!e) { | |
return false; | |
} | |
const u = g('Links', e); | |
if (!u) { | |
return false; | |
} | |
const t = u.getElementsByTagName('img'); | |
if (t && t.length) { | |
u.appendChild(t[0].cloneNode(true)); | |
} | |
u.appendChild(v); | |
return true; | |
} | |
function a(t) { | |
const e = g('ExploreChoose'); | |
if (!e) { | |
return false; | |
} | |
const u = e.getElementsByTagName('ul'); | |
if (!u || !u.length) { | |
return false; | |
} | |
u = u[0]; | |
const v = document.createElement('li'); | |
t.className = 'Plain'; | |
v.appendChild(t); | |
u.insertBefore(v, u.firstChild); | |
return true; | |
} | |
function f(w) { | |
const x = w ? 'person_menu_you_div' : 'person_menu_other_div'; | |
const v = document.getElementById(x); | |
const e = document.createElement('div'); | |
e.className = 'menu_item_line_above'; | |
const y = document.createElement('a'); | |
y.className = 'block'; | |
y.id = w ? 'personmenu_your_flickriver_link' : 'personmenu_flickriver_link'; | |
y.href = '#'; | |
y.innerHTML = 'View on Flickriver'; | |
e.appendChild(y); | |
v.appendChild(e); | |
if (w) { | |
const z = document.getElementById('personmenu_your_photos_link'); | |
const u = z.href; | |
if (u.match(/^http:\/\//)) { | |
u = u.replace(/flickr.com/, 'flickriver.com'); | |
} else { | |
u = 'https://www.flickriver.com' + u; | |
} | |
y.href = u; | |
return; | |
} | |
const t = document.getElementById('personmenu_photos_link'); | |
function A() { | |
const B = t.href; | |
B = B.replace(/flickr.com/, 'flickriver.com'); | |
B += 'popular-interesting/'; | |
y.href = B; | |
} | |
t.addEventListener( | |
'DOMAttrModified', | |
function () { | |
A(); | |
}, | |
false, | |
); | |
} | |
function q(e) { | |
const u = g('Paginator'); | |
if (!u) { | |
return; | |
} | |
const t = document.createElement('a'); | |
t.setAttribute('href', e); | |
t.innerHTML = 'view on flickriver'; | |
t.setAttribute('style', 'margin-left: 20px'); | |
u.appendChild(t); | |
} | |
try { | |
f(true); | |
} catch (r) {} | |
try { | |
f(false); | |
} catch (r) {} | |
const d = location.href; | |
const o = d.replace(/\?.*$/, ''); | |
o = o.replace(/flickr.com/, 'flickriver.com'); | |
const h = document.createElement('a'); | |
h.setAttribute('href', o); | |
h.innerHTML = 'View on Flickriver'; | |
if (m(d)) { | |
q(h.cloneNode(true)); | |
b(h); | |
return; | |
} | |
if (n(d)) { | |
if (p()) { | |
j(h); | |
} | |
return; | |
} | |
if (k(d)) { | |
q(h.cloneNode(true)); | |
l(h); | |
return; | |
} | |
if (c(d)) { | |
q(h.cloneNode(true)); | |
if (!b(h)) { | |
i(h); | |
} | |
return; | |
} | |
if (s(d)) { | |
a(h); | |
return; | |
} | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// "View on Flickriver" Greasemonkey script v1.4 | |
// | |
// Written by Alex Sirota (http://iosart.com/) | |
// | |
// Copyright Alex Sirota (c) 2007-2010 All Rights Reserved | |
// | |
// | |
(function(){function g(t,e){if(typeof e=="undefined"){e=document}var u=document.evaluate("//*[@class='"+t+"']",e,null,XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,null);return u.snapshotLength>0?u.snapshotItem(0):null}function m(e){if(e.match(/photos\/([^/]+)\/?$/)||e.match(/photos\/([^/]+)\/page[0-9]+\/?$/)||e.match(/people\/([^/]+)\/?$/)||e.match(/photos\/([^/]+)\/popular-interesting\/?$/)||e.match(/photos\/([^/]+)\/sets\/?$/)||e.match(/photos\/([^/]+)\/tags\/?$/)||e.match(/photos\/([^/]+)\/sets\/([^/]+)\/?$/)||e.match(/photos\/([^/]+)\/tags\/([^/]+)\/?$/)||e.match(/photos\/([^/]+)\/favorites\/?$/)||e.match(/photos\/([^/]+)\/friends\/?$/)){return true}return false}function k(e){if(e.match(/photos\/?$/)||e.match(/photos\/tags\/([^/]+)\/?$/)||e.match(/photos\/tags\/interesting\/?$/)){return true}return false}function n(e){if(e.match(/photos\/([^/]+)\/([0-9]+)\/?$/)||e.match(/photos\/([^/]+)\/([0-9]+)\/in\/[^/]+\/?$/)){return true}return false}function c(e){if(e.match(/groups\/([^/]+)\/?$/)||e.match(/groups\/([^/]+)\/pool\/page[0-9]+\/?$/)||e.match(/groups\/([^/]+)\/pool\/?$/)){return true}return false}function s(e){if(e.match(/explore\/?$/)){return true}return false}function b(e){var u=g("Extras");e=e.cloneNode(true);if(u){var t=document.createElement("style");t.innerHTML=".flickriver-extras-link, .flickriver-extras-link:visited, .flickriver-extras-link:link { font-size: 11px; font-weight: bold; color: #A1A1A1;}\n.flickriver-extras-link:hover { background: none; color: #0259C4 }";e.className="flickriver-extras-link";var v=document.createElement("div");if(document.getElementById("SlideShowButton")&&document.getElementById("ShareButton")){v.style.textAlign="center"}else{v.style.textAlign="right";if(document.getElementById("ShareButton")){v.style.paddingRight="2px"}else{v.style.paddingRight="11px"}}u.appendChild(t);v.appendChild(e);u.appendChild(v);return true}return false}function p(){var e=g("photoDescription");if(!e){return true}if(e.innerHTML.match(/flickriver\.com/)){return false}return true}function j(t){var e=document.getElementById("DiscussPhoto");if(!e){return false}var u=document.createElement("p");u.appendChild(t);e.parentNode.insertBefore(u,e);return true}function l(t){var e=document.getElementById("Hint");if(!e){return false}var u=document.createElement("p");t.style.fontWeight="bold";u.appendChild(t);e.appendChild(u);return true}function i(v){var e=document.getElementById("SubNav");if(!e){return false}var u=g("Links",e);if(!u){return false}var t=u.getElementsByTagName("img");if(t&&t.length){u.appendChild(t[0].cloneNode(true))}u.appendChild(v);return true}function a(t){var e=g("ExploreChoose");if(!e){return false}var u=e.getElementsByTagName("ul");if(!u||!u.length){return false}u=u[0];var v=document.createElement("li");t.className="Plain";v.appendChild(t);u.insertBefore(v,u.firstChild);return true}function f(w){var x=w?"person_menu_you_div":"person_menu_other_div";var v=document.getElementById(x);var e=document.createElement("div");e.className="menu_item_line_above";var y=document.createElement("a");y.className="block";y.id=w?"personmenu_your_flickriver_link":"personmenu_flickriver_link";y.href="#";y.innerHTML="View on Flickriver";e.appendChild(y);v.appendChild(e);if(w){var z=document.getElementById("personmenu_your_photos_link");var u=z.href;if(u.match(/^http:\/\//)){u=u.replace(/flickr.com/,"flickriver.com")}else{u="https://www.flickriver.com"+u}y.href=u;return}var t=document.getElementById("personmenu_photos_link");function A(){var B=t.href;B=B.replace(/flickr.com/,"flickriver.com");B+="popular-interesting/";y.href=B}t.addEventListener("DOMAttrModified",function(){A()},false)}function q(e){var u=g("Paginator");if(!u){return}var t=document.createElement("a");t.setAttribute("href",e);t.innerHTML="view on flickriver";t.setAttribute("style","margin-left: 20px");u.appendChild(t)}try{f(true)}catch(r){}try{f(false)}catch(r){}var d=location.href;var o=d.replace(/\?.*$/,"");o=o.replace(/flickr.com/,"flickriver.com");var h=document.createElement("a");h.setAttribute("href",o);h.innerHTML="View on Flickriver";if(m(d)){q(h.cloneNode(true));b(h);return}if(n(d)){if(p()){j(h)}return}if(k(d)){q(h.cloneNode(true));l(h);return}if(c(d)){q(h.cloneNode(true));if(!b(h)){i(h)}return}if(s(d)){a(h);return}})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* --- waitForKeyElements(): A utility function, for Greasemonkey scripts, | |
that detects and handles AJAXed content. | |
Usage example: | |
waitForKeyElements ( | |
"div.comments" | |
, commentCallbackFunction | |
); | |
//--- Page-specific function to do what we want when the node is found. | |
function commentCallbackFunction (jNode) { | |
jNode.text ("This comment changed by waitForKeyElements()."); | |
} | |
IMPORTANT: This function requires your script to have loaded jQuery. | |
*/ | |
function waitForKeyElements( | |
selectorTxt /* Required: The jQuery selector string that | |
specifies the desired element(s). | |
*/, | |
actionFunction /* Required: The code to run when elements are | |
found. It is passed a jNode to the matched | |
element. | |
*/, | |
bWaitOnce /* Optional: If false, will continue to scan for | |
new elements even after the first match is | |
found. | |
*/, | |
iframeSelector /* Optional: If set, identifies the iframe to | |
search. | |
*/, | |
) { | |
let targetNodes, btargetsFound; | |
if (typeof iframeSelector === 'undefined') { | |
targetNodes = $(selectorTxt); | |
} else { | |
targetNodes = $(iframeSelector).contents().find(selectorTxt); | |
} | |
if (targetNodes && targetNodes.length > 0) { | |
btargetsFound = true; | |
/* --- Found target node(s). Go through each and act if they | |
are new. | |
*/ | |
targetNodes.each(function () { | |
let jThis = $(this); | |
let alreadyFound = jThis.data('alreadyFound') || false; | |
if (!alreadyFound) { | |
// --- Call the payload function. | |
let cancelFound = actionFunction(jThis); | |
if (cancelFound) { | |
btargetsFound = false; | |
} else { | |
jThis.data('alreadyFound', true); | |
} | |
} | |
}); | |
} else { | |
btargetsFound = false; | |
} | |
// --- Get the timer-control variable for this selector. | |
let controlObj = waitForKeyElements.controlObj || {}; | |
let controlKey = selectorTxt.replace(/[^\w]/g, '_'); | |
let timeControl = controlObj[controlKey]; | |
// --- Now set or clear the timer as appropriate. | |
if (btargetsFound && bWaitOnce && timeControl) { | |
// --- The only condition where we need to clear the timer. | |
clearInterval(timeControl); | |
delete controlObj[controlKey]; | |
} else { | |
// --- Set a timer, if needed. | |
if (!timeControl) { | |
timeControl = setInterval(function () { | |
waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector); | |
}, 300); | |
controlObj[controlKey] = timeControl; | |
} | |
} | |
waitForKeyElements.controlObj = controlObj; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment