Last active
September 16, 2017 19:41
-
-
Save adlawson/147e22f20e6f1eeb75c8e37acc19c96c to your computer and use it in GitHub Desktop.
Mixcloud Tracklist (Tampermonkey build) - Display tracklists on Mixcloud - https://github.com/adlawson/mixcloud-tracklist
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
// ==UserScript== | |
// @name Mixcloud Tracklist | |
// @namespace [email protected] | |
// @version 4.3.0 | |
// @description Display tracklists on Mixcloud | |
// @author Andrew Lawson | |
// @homepage https://github.com/adlawson/mixcloud-tracklist | |
// @supportURL https://github.com/adlawson/mixcloud-tracklist/issues | |
// @updateURL https://cdn.rawgit.com/adlawson/147e22f20e6f1eeb75c8e37acc19c96c/raw/mixcloud-tracklist.user.js | |
// @icon https://cdn.rawgit.com/adlawson/mixcloud-tracklist/master/images/mixcloud-48.png | |
// @include /^https?://.+\.mixcloud.com/.+$/ | |
// @run-at document-idle | |
// @grant none | |
// ==/UserScript== | |
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | |
/* | |
* Mixcloud Tracklist browser extension | |
* | |
* Copyright (c) 2015 Andrew Lawson <http://adlawson.com> | |
* | |
* For the full copyright and license information, please view the LICENSE | |
* file that was distributed with this source code. | |
* | |
* @link https://github.com/adlawson/mixcloud-tracklist | |
*/ | |
'use strict'; | |
module.exports = { | |
insert: insert, | |
insertBefore: insertBefore, | |
onChange: onChange, | |
querySelector: querySelector, | |
replace: replace | |
}; | |
function insert(container, html) { | |
container.insertAdjacentHTML('beforeend', html); | |
} | |
function insertBefore(container, sibling, html) { | |
var temp = document.createElement('div'); | |
temp.insertAdjacentHTML('afterbegin', html); | |
container.insertBefore(temp.firstChild, sibling); | |
} | |
function onChange(element, fn) { | |
var observer = new MutationObserver(function (mutations) { | |
mutations.forEach(function (mutation) { | |
if (mutation.addedNodes.length > 0) fn(); | |
}); | |
}); | |
observer.observe(element, { childList: true }); | |
} | |
function querySelector(query) { | |
return document.querySelector(query); | |
} | |
function replace(container, old, html) { | |
var temp = document.createElement('div'); | |
temp.insertAdjacentHTML('afterbegin', html); | |
container.replaceChild(temp.firstChild, old); | |
} | |
},{}],2:[function(require,module,exports){ | |
(function (process){ | |
(function (root, factory) { | |
if (typeof define === 'function' && define.amd && define.amd.dust === true) { | |
define('dust.core', [], factory); | |
} else if (typeof exports === 'object') { | |
module.exports = factory(); | |
} else { | |
root.dust = factory(); | |
} | |
}(this, function() { | |
var dust = { | |
"version": "2.7.5" | |
}, | |
NONE = 'NONE', ERROR = 'ERROR', WARN = 'WARN', INFO = 'INFO', DEBUG = 'DEBUG', | |
EMPTY_FUNC = function() {}; | |
dust.config = { | |
whitespace: false, | |
amd: false, | |
cjs: false, | |
cache: true | |
}; | |
// Directive aliases to minify code | |
dust._aliases = { | |
"write": "w", | |
"end": "e", | |
"map": "m", | |
"render": "r", | |
"reference": "f", | |
"section": "s", | |
"exists": "x", | |
"notexists": "nx", | |
"block": "b", | |
"partial": "p", | |
"helper": "h" | |
}; | |
(function initLogging() { | |
/*global process, console*/ | |
var loggingLevels = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, NONE: 4 }, | |
consoleLog, | |
log; | |
if (typeof console !== 'undefined' && console.log) { | |
consoleLog = console.log; | |
if(typeof consoleLog === 'function') { | |
log = function() { | |
consoleLog.apply(console, arguments); | |
}; | |
} else { | |
log = function() { | |
consoleLog(Array.prototype.slice.apply(arguments).join(' ')); | |
}; | |
} | |
} else { | |
log = EMPTY_FUNC; | |
} | |
/** | |
* Filters messages based on `dust.debugLevel`. | |
* This default implementation will print to the console if it exists. | |
* @param {String|Error} message the message to print/throw | |
* @param {String} type the severity of the message(ERROR, WARN, INFO, or DEBUG) | |
* @public | |
*/ | |
dust.log = function(message, type) { | |
type = type || INFO; | |
if (loggingLevels[type] >= loggingLevels[dust.debugLevel]) { | |
log('[DUST:' + type + ']', message); | |
} | |
}; | |
dust.debugLevel = NONE; | |
if(typeof process !== 'undefined' && process.env && /\bdust\b/.test(process.env.DEBUG)) { | |
dust.debugLevel = DEBUG; | |
} | |
}()); | |
dust.helpers = {}; | |
dust.cache = {}; | |
dust.register = function(name, tmpl) { | |
if (!name) { | |
return; | |
} | |
tmpl.templateName = name; | |
if (dust.config.cache !== false) { | |
dust.cache[name] = tmpl; | |
} | |
}; | |
dust.render = function(nameOrTemplate, context, callback) { | |
var chunk = new Stub(callback).head; | |
try { | |
load(nameOrTemplate, chunk, context).end(); | |
} catch (err) { | |
chunk.setError(err); | |
} | |
}; | |
dust.stream = function(nameOrTemplate, context) { | |
var stream = new Stream(), | |
chunk = stream.head; | |
dust.nextTick(function() { | |
try { | |
load(nameOrTemplate, chunk, context).end(); | |
} catch (err) { | |
chunk.setError(err); | |
} | |
}); | |
return stream; | |
}; | |
/** | |
* Extracts a template function (body_0) from whatever is passed. | |
* @param nameOrTemplate {*} Could be: | |
* - the name of a template to load from cache | |
* - a CommonJS-compiled template (a function with a `template` property) | |
* - a template function | |
* @param loadFromCache {Boolean} if false, don't look in the cache | |
* @return {Function} a template function, if found | |
*/ | |
function getTemplate(nameOrTemplate, loadFromCache/*=true*/) { | |
if(!nameOrTemplate) { | |
return; | |
} | |
if(typeof nameOrTemplate === 'function' && nameOrTemplate.template) { | |
// Sugar away CommonJS module templates | |
return nameOrTemplate.template; | |
} | |
if(dust.isTemplateFn(nameOrTemplate)) { | |
// Template functions passed directly | |
return nameOrTemplate; | |
} | |
if(loadFromCache !== false) { | |
// Try loading a template with this name from cache | |
return dust.cache[nameOrTemplate]; | |
} | |
} | |
function load(nameOrTemplate, chunk, context) { | |
if(!nameOrTemplate) { | |
return chunk.setError(new Error('No template or template name provided to render')); | |
} | |
var template = getTemplate(nameOrTemplate, dust.config.cache); | |
if (template) { | |
return template(chunk, Context.wrap(context, template.templateName)); | |
} else { | |
if (dust.onLoad) { | |
return chunk.map(function(chunk) { | |
// Alias just so it's easier to read that this would always be a name | |
var name = nameOrTemplate; | |
// Three possible scenarios for a successful callback: | |
// - `require(nameOrTemplate)(dust); cb()` | |
// - `src = readFile('src.dust'); cb(null, src)` | |
// - `compiledTemplate = require(nameOrTemplate)(dust); cb(null, compiledTemplate)` | |
function done(err, srcOrTemplate) { | |
var template; | |
if (err) { | |
return chunk.setError(err); | |
} | |
// Prefer a template that is passed via callback over the cached version. | |
template = getTemplate(srcOrTemplate, false) || getTemplate(name, dust.config.cache); | |
if (!template) { | |
// It's a template string, compile it and register under `name` | |
if(dust.compile) { | |
template = dust.loadSource(dust.compile(srcOrTemplate, name)); | |
} else { | |
return chunk.setError(new Error('Dust compiler not available')); | |
} | |
} | |
template(chunk, Context.wrap(context, template.templateName)).end(); | |
} | |
if(dust.onLoad.length === 3) { | |
dust.onLoad(name, context.options, done); | |
} else { | |
dust.onLoad(name, done); | |
} | |
}); | |
} | |
return chunk.setError(new Error('Template Not Found: ' + nameOrTemplate)); | |
} | |
} | |
dust.loadSource = function(source) { | |
/*jshint evil:true*/ | |
return eval(source); | |
}; | |
if (Array.isArray) { | |
dust.isArray = Array.isArray; | |
} else { | |
dust.isArray = function(arr) { | |
return Object.prototype.toString.call(arr) === '[object Array]'; | |
}; | |
} | |
dust.nextTick = (function() { | |
return function(callback) { | |
setTimeout(callback, 0); | |
}; | |
})(); | |
/** | |
* Dust has its own rules for what is "empty"-- which is not the same as falsy. | |
* Empty arrays, null, and undefined are empty | |
*/ | |
dust.isEmpty = function(value) { | |
if (value === 0) { | |
return false; | |
} | |
if (dust.isArray(value) && !value.length) { | |
return true; | |
} | |
return !value; | |
}; | |
dust.isEmptyObject = function(obj) { | |
var key; | |
if (obj === null) { | |
return false; | |
} | |
if (obj === undefined) { | |
return false; | |
} | |
if (obj.length > 0) { | |
return false; | |
} | |
for (key in obj) { | |
if (Object.prototype.hasOwnProperty.call(obj, key)) { | |
return false; | |
} | |
} | |
return true; | |
}; | |
dust.isTemplateFn = function(elem) { | |
return typeof elem === 'function' && | |
elem.__dustBody; | |
}; | |
/** | |
* Decide somewhat-naively if something is a Thenable. | |
* @param elem {*} object to inspect | |
* @return {Boolean} is `elem` a Thenable? | |
*/ | |
dust.isThenable = function(elem) { | |
return elem && | |
typeof elem === 'object' && | |
typeof elem.then === 'function'; | |
}; | |
/** | |
* Decide very naively if something is a Stream. | |
* @param elem {*} object to inspect | |
* @return {Boolean} is `elem` a Stream? | |
*/ | |
dust.isStreamable = function(elem) { | |
return elem && | |
typeof elem.on === 'function' && | |
typeof elem.pipe === 'function'; | |
}; | |
// apply the filter chain and return the output string | |
dust.filter = function(string, auto, filters, context) { | |
var i, len, name, filter; | |
if (filters) { | |
for (i = 0, len = filters.length; i < len; i++) { | |
name = filters[i]; | |
if (!name.length) { | |
continue; | |
} | |
filter = dust.filters[name]; | |
if (name === 's') { | |
auto = null; | |
} else if (typeof filter === 'function') { | |
string = filter(string, context); | |
} else { | |
dust.log('Invalid filter `' + name + '`', WARN); | |
} | |
} | |
} | |
// by default always apply the h filter, unless asked to unescape with |s | |
if (auto) { | |
string = dust.filters[auto](string, context); | |
} | |
return string; | |
}; | |
dust.filters = { | |
h: function(value) { return dust.escapeHtml(value); }, | |
j: function(value) { return dust.escapeJs(value); }, | |
u: encodeURI, | |
uc: encodeURIComponent, | |
js: function(value) { return dust.escapeJSON(value); }, | |
jp: function(value) { | |
if (!JSON) {dust.log('JSON is undefined; could not parse `' + value + '`', WARN); | |
return value; | |
} else { | |
return JSON.parse(value); | |
} | |
} | |
}; | |
function Context(stack, global, options, blocks, templateName) { | |
if(stack !== undefined && !(stack instanceof Stack)) { | |
stack = new Stack(stack); | |
} | |
this.stack = stack; | |
this.global = global; | |
this.options = options; | |
this.blocks = blocks; | |
this.templateName = templateName; | |
this._isContext = true; | |
} | |
dust.makeBase = dust.context = function(global, options) { | |
return new Context(undefined, global, options); | |
}; | |
dust.isContext = function(obj) { | |
return typeof obj === "object" && obj._isContext === true; | |
}; | |
/** | |
* Factory function that creates a closure scope around a Thenable-callback. | |
* Returns a function that can be passed to a Thenable that will resume a | |
* Context lookup once the Thenable resolves with new data, adding that new | |
* data to the lookup stack. | |
*/ | |
function getWithResolvedData(ctx, cur, down) { | |
return function(data) { | |
return ctx.push(data)._get(cur, down); | |
}; | |
} | |
Context.wrap = function(context, name) { | |
if (dust.isContext(context)) { | |
return context; | |
} | |
return new Context(context, {}, {}, null, name); | |
}; | |
/** | |
* Public API for getting a value from the context. | |
* @method get | |
* @param {string|array} path The path to the value. Supported formats are: | |
* 'key' | |
* 'path.to.key' | |
* '.path.to.key' | |
* ['path', 'to', 'key'] | |
* ['key'] | |
* @param {boolean} [cur=false] Boolean which determines if the search should be limited to the | |
* current context (true), or if get should search in parent contexts as well (false). | |
* @public | |
* @returns {string|object} | |
*/ | |
Context.prototype.get = function(path, cur) { | |
if (typeof path === 'string') { | |
if (path[0] === '.') { | |
cur = true; | |
path = path.substr(1); | |
} | |
path = path.split('.'); | |
} | |
return this._get(cur, path); | |
}; | |
/** | |
* Get a value from the context | |
* @method _get | |
* @param {boolean} cur Get only from the current context | |
* @param {array} down An array of each step in the path | |
* @private | |
* @return {string | object} | |
*/ | |
Context.prototype._get = function(cur, down) { | |
var ctx = this.stack || {}, | |
i = 1, | |
value, first, len, ctxThis, fn; | |
first = down[0]; | |
len = down.length; | |
if (cur && len === 0) { | |
ctxThis = ctx; | |
ctx = ctx.head; | |
} else { | |
if (!cur) { | |
// Search up the stack for the first value | |
while (ctx) { | |
if (ctx.isObject) { | |
ctxThis = ctx.head; | |
value = ctx.head[first]; | |
if (value !== undefined) { | |
break; | |
} | |
} | |
ctx = ctx.tail; | |
} | |
// Try looking in the global context if we haven't found anything yet | |
if (value !== undefined) { | |
ctx = value; | |
} else { | |
ctx = this.global && this.global[first]; | |
} | |
} else if (ctx) { | |
// if scope is limited by a leading dot, don't search up the tree | |
if(ctx.head) { | |
ctx = ctx.head[first]; | |
} else { | |
// context's head is empty, value we are searching for is not defined | |
ctx = undefined; | |
} | |
} | |
while (ctx && i < len) { | |
if (dust.isThenable(ctx)) { | |
// Bail early by returning a Thenable for the remainder of the search tree | |
return ctx.then(getWithResolvedData(this, cur, down.slice(i))); | |
} | |
ctxThis = ctx; | |
ctx = ctx[down[i]]; | |
i++; | |
} | |
} | |
if (typeof ctx === 'function') { | |
fn = function() { | |
try { | |
return ctx.apply(ctxThis, arguments); | |
} catch (err) { | |
dust.log(err, ERROR); | |
throw err; | |
} | |
}; | |
fn.__dustBody = !!ctx.__dustBody; | |
return fn; | |
} else { | |
if (ctx === undefined) { | |
dust.log('Cannot find reference `{' + down.join('.') + '}` in template `' + this.getTemplateName() + '`', INFO); | |
} | |
return ctx; | |
} | |
}; | |
Context.prototype.getPath = function(cur, down) { | |
return this._get(cur, down); | |
}; | |
Context.prototype.push = function(head, idx, len) { | |
if(head === undefined) { | |
dust.log("Not pushing an undefined variable onto the context", INFO); | |
return this; | |
} | |
return this.rebase(new Stack(head, this.stack, idx, len)); | |
}; | |
Context.prototype.pop = function() { | |
var head = this.current(); | |
this.stack = this.stack && this.stack.tail; | |
return head; | |
}; | |
Context.prototype.rebase = function(head) { | |
return new Context(head, this.global, this.options, this.blocks, this.getTemplateName()); | |
}; | |
Context.prototype.clone = function() { | |
var context = this.rebase(); | |
context.stack = this.stack; | |
return context; | |
}; | |
Context.prototype.current = function() { | |
return this.stack && this.stack.head; | |
}; | |
Context.prototype.getBlock = function(key) { | |
var blocks, len, fn; | |
if (typeof key === 'function') { | |
key = key(new Chunk(), this).data.join(''); | |
} | |
blocks = this.blocks; | |
if (!blocks) { | |
dust.log('No blocks for context `' + key + '` in template `' + this.getTemplateName() + '`', DEBUG); | |
return false; | |
} | |
len = blocks.length; | |
while (len--) { | |
fn = blocks[len][key]; | |
if (fn) { | |
return fn; | |
} | |
} | |
dust.log('Malformed template `' + this.getTemplateName() + '` was missing one or more blocks.'); | |
return false; | |
}; | |
Context.prototype.shiftBlocks = function(locals) { | |
var blocks = this.blocks, | |
newBlocks; | |
if (locals) { | |
if (!blocks) { | |
newBlocks = [locals]; | |
} else { | |
newBlocks = blocks.concat([locals]); | |
} | |
return new Context(this.stack, this.global, this.options, newBlocks, this.getTemplateName()); | |
} | |
return this; | |
}; | |
Context.prototype.resolve = function(body) { | |
var chunk; | |
if(typeof body !== 'function') { | |
return body; | |
} | |
chunk = new Chunk().render(body, this); | |
if(chunk instanceof Chunk) { | |
return chunk.data.join(''); // ie7 perf | |
} | |
return chunk; | |
}; | |
Context.prototype.getTemplateName = function() { | |
return this.templateName; | |
}; | |
function Stack(head, tail, idx, len) { | |
this.tail = tail; | |
this.isObject = head && typeof head === 'object'; | |
this.head = head; | |
this.index = idx; | |
this.of = len; | |
} | |
function Stub(callback) { | |
this.head = new Chunk(this); | |
this.callback = callback; | |
this.out = ''; | |
} | |
Stub.prototype.flush = function() { | |
var chunk = this.head; | |
while (chunk) { | |
if (chunk.flushable) { | |
this.out += chunk.data.join(''); //ie7 perf | |
} else if (chunk.error) { | |
this.callback(chunk.error); | |
dust.log('Rendering failed with error `' + chunk.error + '`', ERROR); | |
this.flush = EMPTY_FUNC; | |
return; | |
} else { | |
return; | |
} | |
chunk = chunk.next; | |
this.head = chunk; | |
} | |
this.callback(null, this.out); | |
}; | |
/** | |
* Creates an interface sort of like a Streams2 ReadableStream. | |
*/ | |
function Stream() { | |
this.head = new Chunk(this); | |
} | |
Stream.prototype.flush = function() { | |
var chunk = this.head; | |
while(chunk) { | |
if (chunk.flushable) { | |
this.emit('data', chunk.data.join('')); //ie7 perf | |
} else if (chunk.error) { | |
this.emit('error', chunk.error); | |
this.emit('end'); | |
dust.log('Streaming failed with error `' + chunk.error + '`', ERROR); | |
this.flush = EMPTY_FUNC; | |
return; | |
} else { | |
return; | |
} | |
chunk = chunk.next; | |
this.head = chunk; | |
} | |
this.emit('end'); | |
}; | |
/** | |
* Executes listeners for `type` by passing data. Note that this is different from a | |
* Node stream, which can pass an arbitrary number of arguments | |
* @return `true` if event had listeners, `false` otherwise | |
*/ | |
Stream.prototype.emit = function(type, data) { | |
var events = this.events || {}, | |
handlers = events[type] || [], | |
i, l; | |
if (!handlers.length) { | |
dust.log('Stream broadcasting, but no listeners for `' + type + '`', DEBUG); | |
return false; | |
} | |
handlers = handlers.slice(0); | |
for (i = 0, l = handlers.length; i < l; i++) { | |
handlers[i](data); | |
} | |
return true; | |
}; | |
Stream.prototype.on = function(type, callback) { | |
var events = this.events = this.events || {}, | |
handlers = events[type] = events[type] || []; | |
if(typeof callback !== 'function') { | |
dust.log('No callback function provided for `' + type + '` event listener', WARN); | |
} else { | |
handlers.push(callback); | |
} | |
return this; | |
}; | |
/** | |
* Pipes to a WritableStream. Note that backpressure isn't implemented, | |
* so we just write as fast as we can. | |
* @param stream {WritableStream} | |
* @return self | |
*/ | |
Stream.prototype.pipe = function(stream) { | |
if(typeof stream.write !== 'function' || | |
typeof stream.end !== 'function') { | |
dust.log('Incompatible stream passed to `pipe`', WARN); | |
return this; | |
} | |
var destEnded = false; | |
if(typeof stream.emit === 'function') { | |
stream.emit('pipe', this); | |
} | |
if(typeof stream.on === 'function') { | |
stream.on('error', function() { | |
destEnded = true; | |
}); | |
} | |
return this | |
.on('data', function(data) { | |
if(destEnded) { | |
return; | |
} | |
try { | |
stream.write(data, 'utf8'); | |
} catch (err) { | |
dust.log(err, ERROR); | |
} | |
}) | |
.on('end', function() { | |
if(destEnded) { | |
return; | |
} | |
try { | |
stream.end(); | |
destEnded = true; | |
} catch (err) { | |
dust.log(err, ERROR); | |
} | |
}); | |
}; | |
function Chunk(root, next, taps) { | |
this.root = root; | |
this.next = next; | |
this.data = []; //ie7 perf | |
this.flushable = false; | |
this.taps = taps; | |
} | |
Chunk.prototype.write = function(data) { | |
var taps = this.taps; | |
if (taps) { | |
data = taps.go(data); | |
} | |
this.data.push(data); | |
return this; | |
}; | |
Chunk.prototype.end = function(data) { | |
if (data) { | |
this.write(data); | |
} | |
this.flushable = true; | |
this.root.flush(); | |
return this; | |
}; | |
Chunk.prototype.map = function(callback) { | |
var cursor = new Chunk(this.root, this.next, this.taps), | |
branch = new Chunk(this.root, cursor, this.taps); | |
this.next = branch; | |
this.flushable = true; | |
try { | |
callback(branch); | |
} catch(err) { | |
dust.log(err, ERROR); | |
branch.setError(err); | |
} | |
return cursor; | |
}; | |
Chunk.prototype.tap = function(tap) { | |
var taps = this.taps; | |
if (taps) { | |
this.taps = taps.push(tap); | |
} else { | |
this.taps = new Tap(tap); | |
} | |
return this; | |
}; | |
Chunk.prototype.untap = function() { | |
this.taps = this.taps.tail; | |
return this; | |
}; | |
Chunk.prototype.render = function(body, context) { | |
return body(this, context); | |
}; | |
Chunk.prototype.reference = function(elem, context, auto, filters) { | |
if (typeof elem === 'function') { | |
elem = elem.apply(context.current(), [this, context, null, {auto: auto, filters: filters}]); | |
if (elem instanceof Chunk) { | |
return elem; | |
} else { | |
return this.reference(elem, context, auto, filters); | |
} | |
} | |
if (dust.isThenable(elem)) { | |
return this.await(elem, context, null, auto, filters); | |
} else if (dust.isStreamable(elem)) { | |
return this.stream(elem, context, null, auto, filters); | |
} else if (!dust.isEmpty(elem)) { | |
return this.write(dust.filter(elem, auto, filters, context)); | |
} else { | |
return this; | |
} | |
}; | |
Chunk.prototype.section = function(elem, context, bodies, params) { | |
var body = bodies.block, | |
skip = bodies['else'], | |
chunk = this, | |
i, len, head; | |
if (typeof elem === 'function' && !dust.isTemplateFn(elem)) { | |
try { | |
elem = elem.apply(context.current(), [this, context, bodies, params]); | |
} catch(err) { | |
dust.log(err, ERROR); | |
return this.setError(err); | |
} | |
// Functions that return chunks are assumed to have handled the chunk manually. | |
// Make that chunk the current one and go to the next method in the chain. | |
if (elem instanceof Chunk) { | |
return elem; | |
} | |
} | |
if (dust.isEmptyObject(bodies)) { | |
// No bodies to render, and we've already invoked any function that was available in | |
// hopes of returning a Chunk. | |
return chunk; | |
} | |
if (!dust.isEmptyObject(params)) { | |
context = context.push(params); | |
} | |
/* | |
Dust's default behavior is to enumerate over the array elem, passing each object in the array to the block. | |
When elem resolves to a value or object instead of an array, Dust sets the current context to the value | |
and renders the block one time. | |
*/ | |
if (dust.isArray(elem)) { | |
if (body) { | |
len = elem.length; | |
if (len > 0) { | |
head = context.stack && context.stack.head || {}; | |
head.$len = len; | |
for (i = 0; i < len; i++) { | |
head.$idx = i; | |
chunk = body(chunk, context.push(elem[i], i, len)); | |
} | |
head.$idx = undefined; | |
head.$len = undefined; | |
return chunk; | |
} else if (skip) { | |
return skip(this, context); | |
} | |
} | |
} else if (dust.isThenable(elem)) { | |
return this.await(elem, context, bodies); | |
} else if (dust.isStreamable(elem)) { | |
return this.stream(elem, context, bodies); | |
} else if (elem === true) { | |
// true is truthy but does not change context | |
if (body) { | |
return body(this, context); | |
} | |
} else if (elem || elem === 0) { | |
// everything that evaluates to true are truthy ( e.g. Non-empty strings and Empty objects are truthy. ) | |
// zero is truthy | |
// for anonymous functions that did not returns a chunk, truthiness is evaluated based on the return value | |
if (body) { | |
return body(this, context.push(elem)); | |
} | |
// nonexistent, scalar false value, scalar empty string, null, | |
// undefined are all falsy | |
} else if (skip) { | |
return skip(this, context); | |
} | |
dust.log('Section without corresponding key in template `' + context.getTemplateName() + '`', DEBUG); | |
return this; | |
}; | |
Chunk.prototype.exists = function(elem, context, bodies) { | |
var body = bodies.block, | |
skip = bodies['else']; | |
if (!dust.isEmpty(elem)) { | |
if (body) { | |
return body(this, context); | |
} | |
dust.log('No block for exists check in template `' + context.getTemplateName() + '`', DEBUG); | |
} else if (skip) { | |
return skip(this, context); | |
} | |
return this; | |
}; | |
Chunk.prototype.notexists = function(elem, context, bodies) { | |
var body = bodies.block, | |
skip = bodies['else']; | |
if (dust.isEmpty(elem)) { | |
if (body) { | |
return body(this, context); | |
} | |
dust.log('No block for not-exists check in template `' + context.getTemplateName() + '`', DEBUG); | |
} else if (skip) { | |
return skip(this, context); | |
} | |
return this; | |
}; | |
Chunk.prototype.block = function(elem, context, bodies) { | |
var body = elem || bodies.block; | |
if (body) { | |
return body(this, context); | |
} | |
return this; | |
}; | |
Chunk.prototype.partial = function(elem, context, partialContext, params) { | |
var head; | |
if(params === undefined) { | |
// Compatibility for < 2.7.0 where `partialContext` did not exist | |
params = partialContext; | |
partialContext = context; | |
} | |
if (!dust.isEmptyObject(params)) { | |
partialContext = partialContext.clone(); | |
head = partialContext.pop(); | |
partialContext = partialContext.push(params) | |
.push(head); | |
} | |
if (dust.isTemplateFn(elem)) { | |
// The eventual result of evaluating `elem` is a partial name | |
// Load the partial after getting its name and end the async chunk | |
return this.capture(elem, context, function(name, chunk) { | |
partialContext.templateName = name; | |
load(name, chunk, partialContext).end(); | |
}); | |
} else { | |
partialContext.templateName = elem; | |
return load(elem, this, partialContext); | |
} | |
}; | |
Chunk.prototype.helper = function(name, context, bodies, params, auto) { | |
var chunk = this, | |
filters = params.filters, | |
ret; | |
// Pre-2.7.1 compat: if auto is undefined, it's an old template. Automatically escape | |
if (auto === undefined) { | |
auto = 'h'; | |
} | |
// handle invalid helpers, similar to invalid filters | |
if(dust.helpers[name]) { | |
try { | |
ret = dust.helpers[name](chunk, context, bodies, params); | |
if (ret instanceof Chunk) { | |
return ret; | |
} | |
if(typeof filters === 'string') { | |
filters = filters.split('|'); | |
} | |
if (!dust.isEmptyObject(bodies)) { | |
return chunk.section(ret, context, bodies, params); | |
} | |
// Helpers act slightly differently from functions in context in that they will act as | |
// a reference if they are self-closing (due to grammar limitations) | |
// In the Chunk.await function we check to make sure bodies is null before acting as a reference | |
return chunk.reference(ret, context, auto, filters); | |
} catch(err) { | |
dust.log('Error in helper `' + name + '`: ' + err.message, ERROR); | |
return chunk.setError(err); | |
} | |
} else { | |
dust.log('Helper `' + name + '` does not exist', WARN); | |
return chunk; | |
} | |
}; | |
/** | |
* Reserve a chunk to be evaluated once a thenable is resolved or rejected | |
* @param thenable {Thenable} the target thenable to await | |
* @param context {Context} context to use to render the deferred chunk | |
* @param bodies {Object} must contain a "body", may contain an "error" | |
* @param auto {String} automatically apply this filter if the Thenable is a reference | |
* @param filters {Array} apply these filters if the Thenable is a reference | |
* @return {Chunk} | |
*/ | |
Chunk.prototype.await = function(thenable, context, bodies, auto, filters) { | |
return this.map(function(chunk) { | |
thenable.then(function(data) { | |
if (bodies) { | |
chunk = chunk.section(data, context, bodies); | |
} else { | |
// Actually a reference. Self-closing sections don't render | |
chunk = chunk.reference(data, context, auto, filters); | |
} | |
chunk.end(); | |
}, function(err) { | |
var errorBody = bodies && bodies.error; | |
if(errorBody) { | |
chunk.render(errorBody, context.push(err)).end(); | |
} else { | |
dust.log('Unhandled promise rejection in `' + context.getTemplateName() + '`', INFO); | |
chunk.end(); | |
} | |
}); | |
}); | |
}; | |
/** | |
* Reserve a chunk to be evaluated with the contents of a streamable. | |
* Currently an error event will bomb out the stream. Once an error | |
* is received, we push it to an {:error} block if one exists, and log otherwise, | |
* then stop listening to the stream. | |
* @param streamable {Streamable} the target streamable that will emit events | |
* @param context {Context} context to use to render each thunk | |
* @param bodies {Object} must contain a "body", may contain an "error" | |
* @return {Chunk} | |
*/ | |
Chunk.prototype.stream = function(stream, context, bodies, auto, filters) { | |
var body = bodies && bodies.block, | |
errorBody = bodies && bodies.error; | |
return this.map(function(chunk) { | |
var ended = false; | |
stream | |
.on('data', function data(thunk) { | |
if(ended) { | |
return; | |
} | |
if(body) { | |
// Fork a new chunk out of the blockstream so that we can flush it independently | |
chunk = chunk.map(function(chunk) { | |
chunk.render(body, context.push(thunk)).end(); | |
}); | |
} else if(!bodies) { | |
// When actually a reference, don't fork, just write into the master async chunk | |
chunk = chunk.reference(thunk, context, auto, filters); | |
} | |
}) | |
.on('error', function error(err) { | |
if(ended) { | |
return; | |
} | |
if(errorBody) { | |
chunk.render(errorBody, context.push(err)); | |
} else { | |
dust.log('Unhandled stream error in `' + context.getTemplateName() + '`', INFO); | |
} | |
if(!ended) { | |
ended = true; | |
chunk.end(); | |
} | |
}) | |
.on('end', function end() { | |
if(!ended) { | |
ended = true; | |
chunk.end(); | |
} | |
}); | |
}); | |
}; | |
Chunk.prototype.capture = function(body, context, callback) { | |
return this.map(function(chunk) { | |
var stub = new Stub(function(err, out) { | |
if (err) { | |
chunk.setError(err); | |
} else { | |
callback(out, chunk); | |
} | |
}); | |
body(stub.head, context).end(); | |
}); | |
}; | |
Chunk.prototype.setError = function(err) { | |
this.error = err; | |
this.root.flush(); | |
return this; | |
}; | |
// Chunk aliases | |
for(var f in Chunk.prototype) { | |
if(dust._aliases[f]) { | |
Chunk.prototype[dust._aliases[f]] = Chunk.prototype[f]; | |
} | |
} | |
function Tap(head, tail) { | |
this.head = head; | |
this.tail = tail; | |
} | |
Tap.prototype.push = function(tap) { | |
return new Tap(tap, this); | |
}; | |
Tap.prototype.go = function(value) { | |
var tap = this; | |
while(tap) { | |
value = tap.head(value); | |
tap = tap.tail; | |
} | |
return value; | |
}; | |
var HCHARS = /[&<>"']/, | |
AMP = /&/g, | |
LT = /</g, | |
GT = />/g, | |
QUOT = /\"/g, | |
SQUOT = /\'/g; | |
dust.escapeHtml = function(s) { | |
if (typeof s === "string" || (s && typeof s.toString === "function")) { | |
if (typeof s !== "string") { | |
s = s.toString(); | |
} | |
if (!HCHARS.test(s)) { | |
return s; | |
} | |
return s.replace(AMP,'&').replace(LT,'<').replace(GT,'>').replace(QUOT,'"').replace(SQUOT, '''); | |
} | |
return s; | |
}; | |
var BS = /\\/g, | |
FS = /\//g, | |
CR = /\r/g, | |
LS = /\u2028/g, | |
PS = /\u2029/g, | |
NL = /\n/g, | |
LF = /\f/g, | |
SQ = /'/g, | |
DQ = /"/g, | |
TB = /\t/g; | |
dust.escapeJs = function(s) { | |
if (typeof s === 'string') { | |
return s | |
.replace(BS, '\\\\') | |
.replace(FS, '\\/') | |
.replace(DQ, '\\"') | |
.replace(SQ, '\\\'') | |
.replace(CR, '\\r') | |
.replace(LS, '\\u2028') | |
.replace(PS, '\\u2029') | |
.replace(NL, '\\n') | |
.replace(LF, '\\f') | |
.replace(TB, '\\t'); | |
} | |
return s; | |
}; | |
dust.escapeJSON = function(o) { | |
if (!JSON) { | |
dust.log('JSON is undefined; could not escape `' + o + '`', WARN); | |
return o; | |
} else { | |
return JSON.stringify(o) | |
.replace(LS, '\\u2028') | |
.replace(PS, '\\u2029') | |
.replace(LT, '\\u003c'); | |
} | |
}; | |
return dust; | |
})); | |
}).call(this,require('_process')) | |
},{"_process":3}],3:[function(require,module,exports){ | |
// shim for using process in browser | |
var process = module.exports = {}; | |
// cached from whatever global is present so that test runners that stub it | |
// don't break things. But we need to wrap it in a try catch in case it is | |
// wrapped in strict mode code which doesn't define any globals. It's inside a | |
// function because try/catches deoptimize in certain engines. | |
var cachedSetTimeout; | |
var cachedClearTimeout; | |
function defaultSetTimout() { | |
throw new Error('setTimeout has not been defined'); | |
} | |
function defaultClearTimeout () { | |
throw new Error('clearTimeout has not been defined'); | |
} | |
(function () { | |
try { | |
if (typeof setTimeout === 'function') { | |
cachedSetTimeout = setTimeout; | |
} else { | |
cachedSetTimeout = defaultSetTimout; | |
} | |
} catch (e) { | |
cachedSetTimeout = defaultSetTimout; | |
} | |
try { | |
if (typeof clearTimeout === 'function') { | |
cachedClearTimeout = clearTimeout; | |
} else { | |
cachedClearTimeout = defaultClearTimeout; | |
} | |
} catch (e) { | |
cachedClearTimeout = defaultClearTimeout; | |
} | |
} ()) | |
function runTimeout(fun) { | |
if (cachedSetTimeout === setTimeout) { | |
//normal enviroments in sane situations | |
return setTimeout(fun, 0); | |
} | |
// if setTimeout wasn't available but was latter defined | |
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { | |
cachedSetTimeout = setTimeout; | |
return setTimeout(fun, 0); | |
} | |
try { | |
// when when somebody has screwed with setTimeout but no I.E. maddness | |
return cachedSetTimeout(fun, 0); | |
} catch(e){ | |
try { | |
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally | |
return cachedSetTimeout.call(null, fun, 0); | |
} catch(e){ | |
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error | |
return cachedSetTimeout.call(this, fun, 0); | |
} | |
} | |
} | |
function runClearTimeout(marker) { | |
if (cachedClearTimeout === clearTimeout) { | |
//normal enviroments in sane situations | |
return clearTimeout(marker); | |
} | |
// if clearTimeout wasn't available but was latter defined | |
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { | |
cachedClearTimeout = clearTimeout; | |
return clearTimeout(marker); | |
} | |
try { | |
// when when somebody has screwed with setTimeout but no I.E. maddness | |
return cachedClearTimeout(marker); | |
} catch (e){ | |
try { | |
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally | |
return cachedClearTimeout.call(null, marker); | |
} catch (e){ | |
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. | |
// Some versions of I.E. have different rules for clearTimeout vs setTimeout | |
return cachedClearTimeout.call(this, marker); | |
} | |
} | |
} | |
var queue = []; | |
var draining = false; | |
var currentQueue; | |
var queueIndex = -1; | |
function cleanUpNextTick() { | |
if (!draining || !currentQueue) { | |
return; | |
} | |
draining = false; | |
if (currentQueue.length) { | |
queue = currentQueue.concat(queue); | |
} else { | |
queueIndex = -1; | |
} | |
if (queue.length) { | |
drainQueue(); | |
} | |
} | |
function drainQueue() { | |
if (draining) { | |
return; | |
} | |
var timeout = runTimeout(cleanUpNextTick); | |
draining = true; | |
var len = queue.length; | |
while(len) { | |
currentQueue = queue; | |
queue = []; | |
while (++queueIndex < len) { | |
if (currentQueue) { | |
currentQueue[queueIndex].run(); | |
} | |
} | |
queueIndex = -1; | |
len = queue.length; | |
} | |
currentQueue = null; | |
draining = false; | |
runClearTimeout(timeout); | |
} | |
process.nextTick = function (fun) { | |
var args = new Array(arguments.length - 1); | |
if (arguments.length > 1) { | |
for (var i = 1; i < arguments.length; i++) { | |
args[i - 1] = arguments[i]; | |
} | |
} | |
queue.push(new Item(fun, args)); | |
if (queue.length === 1 && !draining) { | |
runTimeout(drainQueue); | |
} | |
}; | |
// v8 likes predictible objects | |
function Item(fun, array) { | |
this.fun = fun; | |
this.array = array; | |
} | |
Item.prototype.run = function () { | |
this.fun.apply(null, this.array); | |
}; | |
process.title = 'browser'; | |
process.browser = true; | |
process.env = {}; | |
process.argv = []; | |
process.version = ''; // empty string to avoid regexp issues | |
process.versions = {}; | |
function noop() {} | |
process.on = noop; | |
process.addListener = noop; | |
process.once = noop; | |
process.off = noop; | |
process.removeListener = noop; | |
process.removeAllListeners = noop; | |
process.emit = noop; | |
process.binding = function (name) { | |
throw new Error('process.binding is not supported'); | |
}; | |
process.cwd = function () { return '/' }; | |
process.chdir = function (dir) { | |
throw new Error('process.chdir is not supported'); | |
}; | |
process.umask = function() { return 0; }; | |
},{}],4:[function(require,module,exports){ | |
(function (root) { | |
// Store setTimeout reference so promise-polyfill will be unaffected by | |
// other code modifying setTimeout (like sinon.useFakeTimers()) | |
var setTimeoutFunc = setTimeout; | |
function noop() {} | |
// Polyfill for Function.prototype.bind | |
function bind(fn, thisArg) { | |
return function () { | |
fn.apply(thisArg, arguments); | |
}; | |
} | |
function Promise(fn) { | |
if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); | |
if (typeof fn !== 'function') throw new TypeError('not a function'); | |
this._state = 0; | |
this._handled = false; | |
this._value = undefined; | |
this._deferreds = []; | |
doResolve(fn, this); | |
} | |
function handle(self, deferred) { | |
while (self._state === 3) { | |
self = self._value; | |
} | |
if (self._state === 0) { | |
self._deferreds.push(deferred); | |
return; | |
} | |
self._handled = true; | |
Promise._immediateFn(function () { | |
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; | |
if (cb === null) { | |
(self._state === 1 ? resolve : reject)(deferred.promise, self._value); | |
return; | |
} | |
var ret; | |
try { | |
ret = cb(self._value); | |
} catch (e) { | |
reject(deferred.promise, e); | |
return; | |
} | |
resolve(deferred.promise, ret); | |
}); | |
} | |
function resolve(self, newValue) { | |
try { | |
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure | |
if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.'); | |
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { | |
var then = newValue.then; | |
if (newValue instanceof Promise) { | |
self._state = 3; | |
self._value = newValue; | |
finale(self); | |
return; | |
} else if (typeof then === 'function') { | |
doResolve(bind(then, newValue), self); | |
return; | |
} | |
} | |
self._state = 1; | |
self._value = newValue; | |
finale(self); | |
} catch (e) { | |
reject(self, e); | |
} | |
} | |
function reject(self, newValue) { | |
self._state = 2; | |
self._value = newValue; | |
finale(self); | |
} | |
function finale(self) { | |
if (self._state === 2 && self._deferreds.length === 0) { | |
Promise._immediateFn(function() { | |
if (!self._handled) { | |
Promise._unhandledRejectionFn(self._value); | |
} | |
}); | |
} | |
for (var i = 0, len = self._deferreds.length; i < len; i++) { | |
handle(self, self._deferreds[i]); | |
} | |
self._deferreds = null; | |
} | |
function Handler(onFulfilled, onRejected, promise) { | |
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; | |
this.onRejected = typeof onRejected === 'function' ? onRejected : null; | |
this.promise = promise; | |
} | |
/** | |
* Take a potentially misbehaving resolver function and make sure | |
* onFulfilled and onRejected are only called once. | |
* | |
* Makes no guarantees about asynchrony. | |
*/ | |
function doResolve(fn, self) { | |
var done = false; | |
try { | |
fn(function (value) { | |
if (done) return; | |
done = true; | |
resolve(self, value); | |
}, function (reason) { | |
if (done) return; | |
done = true; | |
reject(self, reason); | |
}); | |
} catch (ex) { | |
if (done) return; | |
done = true; | |
reject(self, ex); | |
} | |
} | |
Promise.prototype['catch'] = function (onRejected) { | |
return this.then(null, onRejected); | |
}; | |
Promise.prototype.then = function (onFulfilled, onRejected) { | |
var prom = new (this.constructor)(noop); | |
handle(this, new Handler(onFulfilled, onRejected, prom)); | |
return prom; | |
}; | |
Promise.all = function (arr) { | |
var args = Array.prototype.slice.call(arr); | |
return new Promise(function (resolve, reject) { | |
if (args.length === 0) return resolve([]); | |
var remaining = args.length; | |
function res(i, val) { | |
try { | |
if (val && (typeof val === 'object' || typeof val === 'function')) { | |
var then = val.then; | |
if (typeof then === 'function') { | |
then.call(val, function (val) { | |
res(i, val); | |
}, reject); | |
return; | |
} | |
} | |
args[i] = val; | |
if (--remaining === 0) { | |
resolve(args); | |
} | |
} catch (ex) { | |
reject(ex); | |
} | |
} | |
for (var i = 0; i < args.length; i++) { | |
res(i, args[i]); | |
} | |
}); | |
}; | |
Promise.resolve = function (value) { | |
if (value && typeof value === 'object' && value.constructor === Promise) { | |
return value; | |
} | |
return new Promise(function (resolve) { | |
resolve(value); | |
}); | |
}; | |
Promise.reject = function (value) { | |
return new Promise(function (resolve, reject) { | |
reject(value); | |
}); | |
}; | |
Promise.race = function (values) { | |
return new Promise(function (resolve, reject) { | |
for (var i = 0, len = values.length; i < len; i++) { | |
values[i].then(resolve, reject); | |
} | |
}); | |
}; | |
// Use polyfill for setImmediate for performance gains | |
Promise._immediateFn = (typeof setImmediate === 'function' && function (fn) { setImmediate(fn); }) || | |
function (fn) { | |
setTimeoutFunc(fn, 0); | |
}; | |
Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) { | |
if (typeof console !== 'undefined' && console) { | |
console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console | |
} | |
}; | |
/** | |
* Set the immediate function to execute callbacks | |
* @param fn {function} Function to execute | |
* @deprecated | |
*/ | |
Promise._setImmediateFn = function _setImmediateFn(fn) { | |
Promise._immediateFn = fn; | |
}; | |
/** | |
* Change the function to execute on unhandled rejection | |
* @param {function} fn Function to execute on unhandled rejection | |
* @deprecated | |
*/ | |
Promise._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) { | |
Promise._unhandledRejectionFn = fn; | |
}; | |
if (typeof module !== 'undefined' && module.exports) { | |
module.exports = Promise; | |
} else if (!root.Promise) { | |
root.Promise = Promise; | |
} | |
})(this); | |
},{}],5:[function(require,module,exports){ | |
(function (global){ | |
var g = (typeof global !== 'undefined') ? global : | |
(typeof window !== 'undefined') ? window : | |
(typeof self !== 'undefined') ? self : this; | |
(function(global) { | |
/** | |
* Polyfill URLSearchParams | |
* | |
* Inspired from : https://github.com/WebReflection/url-search-params/blob/master/src/url-search-params.js | |
*/ | |
var checkIfIteratorIsSupported = function() { | |
try { | |
return !!Symbol.iterator; | |
} catch(error) { | |
return false; | |
} | |
}; | |
var iteratorSupported = checkIfIteratorIsSupported(); | |
var createIterator = function(items) { | |
var iterator = { | |
next: function() { | |
var value = items.shift(); | |
return { done: value === void 0, value: value }; | |
} | |
}; | |
if(iteratorSupported) { | |
iterator[Symbol.iterator] = function() { | |
return iterator; | |
}; | |
} | |
return iterator; | |
}; | |
var polyfillURLSearchParams= function() { | |
var URLSearchParams = function(searchString) { | |
Object.defineProperty(this, '_entries', { value: {} }); | |
if(typeof searchString === 'string') { | |
if(searchString !== '') { | |
searchString = searchString.replace(/^\?/, ''); | |
var attributes = searchString.split('&'); | |
var attribute; | |
for(var i = 0; i < attributes.length; i++) { | |
attribute = attributes[i].split('='); | |
this.append( | |
decodeURIComponent(attribute[0]), | |
(attribute.length > 1) ? decodeURIComponent(attribute[1]) : '' | |
); | |
} | |
} | |
} else if(searchString instanceof URLSearchParams) { | |
var _this = this; | |
searchString.forEach(function(value, name) { | |
_this.append(value, name); | |
}); | |
} | |
}; | |
var proto = URLSearchParams.prototype; | |
proto.append = function(name, value) { | |
if(name in this._entries) { | |
this._entries[name].push(value.toString()); | |
} else { | |
this._entries[name] = [value.toString()]; | |
} | |
}; | |
proto.delete = function(name) { | |
delete this._entries[name]; | |
}; | |
proto.get = function(name) { | |
return (name in this._entries) ? this._entries[name][0] : null; | |
}; | |
proto.getAll = function(name) { | |
return (name in this._entries) ? this._entries[name].slice(0) : []; | |
}; | |
proto.has = function(name) { | |
return (name in this._entries); | |
}; | |
proto.set = function(name, value) { | |
this._entries[name] = [value.toString()]; | |
}; | |
proto.forEach = function(callback, thisArg) { | |
var entries; | |
for(var name in this._entries) { | |
if(this._entries.hasOwnProperty(name)) { | |
entries = this._entries[name]; | |
for(var i = 0; i < entries.length; i++) { | |
callback.call(thisArg, entries[i], name, this); | |
} | |
} | |
} | |
}; | |
proto.keys = function() { | |
var items = []; | |
this.forEach(function(value, name) { items.push(name); }); | |
return createIterator(items); | |
}; | |
proto.values = function() { | |
var items = []; | |
this.forEach(function(value) { items.push(value); }); | |
return createIterator(items); | |
}; | |
proto.entries = function() { | |
var items = []; | |
this.forEach(function(value, name) { items.push([name, value]); }); | |
return createIterator(items); | |
}; | |
if(iteratorSupported) { | |
proto[Symbol.iterator] = proto.entries; | |
} | |
proto.toString = function() { | |
var searchString = ''; | |
this.forEach(function(value, name) { | |
if(searchString.length > 0) searchString+= '&'; | |
searchString += encodeURIComponent(name) + '=' + encodeURIComponent(value); | |
}); | |
return searchString; | |
}; | |
global.URLSearchParams = URLSearchParams; | |
}; | |
if(!('URLSearchParams' in global)) { | |
polyfillURLSearchParams(); | |
} | |
// HTMLAnchorElement | |
})(g); | |
(function(global) { | |
/** | |
* Polyfill URL | |
* | |
* Inspired from : https://github.com/arv/DOM-URL-Polyfill/blob/master/src/url.js | |
*/ | |
var checkIfURLIsSupported = function() { | |
try { | |
var u = new URL('b', 'http://a'); | |
u.pathname = 'c%20d'; | |
return (u.href === 'http://a/c%20d') && u.searchParams; | |
} catch(e) { | |
return false; | |
} | |
}; | |
var polyfillURL = function() { | |
var _URL = global.URL; | |
var URL = function(url, base) { | |
if(typeof url !== 'string') throw new TypeError('Failed to construct \'URL\': Invalid URL'); | |
var doc = document.implementation.createHTMLDocument(''); | |
window.doc = doc; | |
if(base) { | |
var baseElement = doc.createElement('base'); | |
baseElement.href = base; | |
doc.head.appendChild(baseElement); | |
} | |
var anchorElement = doc.createElement('a'); | |
anchorElement.href = url; | |
doc.body.appendChild(anchorElement); | |
anchorElement.href = anchorElement.href; // force href to refresh | |
if(anchorElement.protocol === ':' || !/:/.test(anchorElement.href)) { | |
throw new TypeError('Invalid URL'); | |
} | |
Object.defineProperty(this, '_anchorElement', { | |
value: anchorElement | |
}); | |
}; | |
var proto = URL.prototype; | |
var linkURLWithAnchorAttribute = function(attributeName) { | |
Object.defineProperty(proto, attributeName, { | |
get: function() { | |
return this._anchorElement[attributeName]; | |
}, | |
set: function(value) { | |
this._anchorElement[attributeName] = value; | |
}, | |
enumerable: true | |
}); | |
}; | |
['hash', 'host', 'hostname', 'port', 'protocol', 'search'] | |
.forEach(function(attributeName) { | |
linkURLWithAnchorAttribute(attributeName); | |
}); | |
Object.defineProperties(proto, { | |
'toString': { | |
get: function() { | |
var _this = this; | |
return function() { | |
return _this.href; | |
}; | |
} | |
}, | |
'href' : { | |
get: function() { | |
return this._anchorElement.href.replace(/\?$/,''); | |
}, | |
set: function(value) { | |
this._anchorElement.href = value; | |
}, | |
enumerable: true | |
}, | |
'pathname' : { | |
get: function() { | |
return this._anchorElement.pathname.replace(/(^\/?)/,'/'); | |
}, | |
set: function(value) { | |
this._anchorElement.pathname = value; | |
}, | |
enumerable: true | |
}, | |
'origin': { | |
get: function() { | |
return this._anchorElement.protocol + '//' + this._anchorElement.hostname + (this._anchorElement.port ? (':' + this._anchorElement.port) : ''); | |
}, | |
enumerable: true | |
}, | |
'password': { // TODO | |
get: function() { | |
return ''; | |
}, | |
set: function(value) { | |
}, | |
enumerable: true | |
}, | |
'username': { // TODO | |
get: function() { | |
return ''; | |
}, | |
set: function(value) { | |
}, | |
enumerable: true | |
}, | |
'searchParams': { | |
get: function() { | |
var searchParams = new URLSearchParams(this.search); | |
var _this = this; | |
['append', 'delete', 'set'].forEach(function(methodName) { | |
var method = searchParams[methodName]; | |
searchParams[methodName] = function() { | |
method.apply(searchParams, arguments); | |
_this.search = searchParams.toString(); | |
}; | |
}); | |
return searchParams; | |
}, | |
enumerable: true | |
} | |
}); | |
URL.createObjectURL = function(blob) { | |
return _URL.createObjectURL.apply(_URL, arguments); | |
}; | |
URL.revokeObjectURL = function(url) { | |
return _URL.revokeObjectURL.apply(_URL, arguments); | |
}; | |
global.URL = URL; | |
}; | |
if(!checkIfURLIsSupported()) { | |
polyfillURL(); | |
} | |
if((global.location !== void 0) && !('origin' in global.location)) { | |
var getOrigin = function() { | |
return global.location.protocol + '//' + global.location.hostname + (global.location.port ? (':' + global.location.port) : ''); | |
}; | |
try { | |
Object.defineProperty(global.location, 'origin', { | |
get: getOrigin, | |
enumerable: true | |
}); | |
} catch(e) { | |
setInterval(function() { | |
global.location.origin = getOrigin(); | |
}, 100); | |
} | |
} | |
})(g); | |
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{}],6:[function(require,module,exports){ | |
(function(self) { | |
'use strict'; | |
if (self.fetch) { | |
return | |
} | |
var support = { | |
searchParams: 'URLSearchParams' in self, | |
iterable: 'Symbol' in self && 'iterator' in Symbol, | |
blob: 'FileReader' in self && 'Blob' in self && (function() { | |
try { | |
new Blob() | |
return true | |
} catch(e) { | |
return false | |
} | |
})(), | |
formData: 'FormData' in self, | |
arrayBuffer: 'ArrayBuffer' in self | |
} | |
if (support.arrayBuffer) { | |
var viewClasses = [ | |
'[object Int8Array]', | |
'[object Uint8Array]', | |
'[object Uint8ClampedArray]', | |
'[object Int16Array]', | |
'[object Uint16Array]', | |
'[object Int32Array]', | |
'[object Uint32Array]', | |
'[object Float32Array]', | |
'[object Float64Array]' | |
] | |
var isDataView = function(obj) { | |
return obj && DataView.prototype.isPrototypeOf(obj) | |
} | |
var isArrayBufferView = ArrayBuffer.isView || function(obj) { | |
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 | |
} | |
} | |
function normalizeName(name) { | |
if (typeof name !== 'string') { | |
name = String(name) | |
} | |
if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { | |
throw new TypeError('Invalid character in header field name') | |
} | |
return name.toLowerCase() | |
} | |
function normalizeValue(value) { | |
if (typeof value !== 'string') { | |
value = String(value) | |
} | |
return value | |
} | |
// Build a destructive iterator for the value list | |
function iteratorFor(items) { | |
var iterator = { | |
next: function() { | |
var value = items.shift() | |
return {done: value === undefined, value: value} | |
} | |
} | |
if (support.iterable) { | |
iterator[Symbol.iterator] = function() { | |
return iterator | |
} | |
} | |
return iterator | |
} | |
function Headers(headers) { | |
this.map = {} | |
if (headers instanceof Headers) { | |
headers.forEach(function(value, name) { | |
this.append(name, value) | |
}, this) | |
} else if (Array.isArray(headers)) { | |
headers.forEach(function(header) { | |
this.append(header[0], header[1]) | |
}, this) | |
} else if (headers) { | |
Object.getOwnPropertyNames(headers).forEach(function(name) { | |
this.append(name, headers[name]) | |
}, this) | |
} | |
} | |
Headers.prototype.append = function(name, value) { | |
name = normalizeName(name) | |
value = normalizeValue(value) | |
var oldValue = this.map[name] | |
this.map[name] = oldValue ? oldValue+','+value : value | |
} | |
Headers.prototype['delete'] = function(name) { | |
delete this.map[normalizeName(name)] | |
} | |
Headers.prototype.get = function(name) { | |
name = normalizeName(name) | |
return this.has(name) ? this.map[name] : null | |
} | |
Headers.prototype.has = function(name) { | |
return this.map.hasOwnProperty(normalizeName(name)) | |
} | |
Headers.prototype.set = function(name, value) { | |
this.map[normalizeName(name)] = normalizeValue(value) | |
} | |
Headers.prototype.forEach = function(callback, thisArg) { | |
for (var name in this.map) { | |
if (this.map.hasOwnProperty(name)) { | |
callback.call(thisArg, this.map[name], name, this) | |
} | |
} | |
} | |
Headers.prototype.keys = function() { | |
var items = [] | |
this.forEach(function(value, name) { items.push(name) }) | |
return iteratorFor(items) | |
} | |
Headers.prototype.values = function() { | |
var items = [] | |
this.forEach(function(value) { items.push(value) }) | |
return iteratorFor(items) | |
} | |
Headers.prototype.entries = function() { | |
var items = [] | |
this.forEach(function(value, name) { items.push([name, value]) }) | |
return iteratorFor(items) | |
} | |
if (support.iterable) { | |
Headers.prototype[Symbol.iterator] = Headers.prototype.entries | |
} | |
function consumed(body) { | |
if (body.bodyUsed) { | |
return Promise.reject(new TypeError('Already read')) | |
} | |
body.bodyUsed = true | |
} | |
function fileReaderReady(reader) { | |
return new Promise(function(resolve, reject) { | |
reader.onload = function() { | |
resolve(reader.result) | |
} | |
reader.onerror = function() { | |
reject(reader.error) | |
} | |
}) | |
} | |
function readBlobAsArrayBuffer(blob) { | |
var reader = new FileReader() | |
var promise = fileReaderReady(reader) | |
reader.readAsArrayBuffer(blob) | |
return promise | |
} | |
function readBlobAsText(blob) { | |
var reader = new FileReader() | |
var promise = fileReaderReady(reader) | |
reader.readAsText(blob) | |
return promise | |
} | |
function readArrayBufferAsText(buf) { | |
var view = new Uint8Array(buf) | |
var chars = new Array(view.length) | |
for (var i = 0; i < view.length; i++) { | |
chars[i] = String.fromCharCode(view[i]) | |
} | |
return chars.join('') | |
} | |
function bufferClone(buf) { | |
if (buf.slice) { | |
return buf.slice(0) | |
} else { | |
var view = new Uint8Array(buf.byteLength) | |
view.set(new Uint8Array(buf)) | |
return view.buffer | |
} | |
} | |
function Body() { | |
this.bodyUsed = false | |
this._initBody = function(body) { | |
this._bodyInit = body | |
if (!body) { | |
this._bodyText = '' | |
} else if (typeof body === 'string') { | |
this._bodyText = body | |
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) { | |
this._bodyBlob = body | |
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) { | |
this._bodyFormData = body | |
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { | |
this._bodyText = body.toString() | |
} else if (support.arrayBuffer && support.blob && isDataView(body)) { | |
this._bodyArrayBuffer = bufferClone(body.buffer) | |
// IE 10-11 can't handle a DataView body. | |
this._bodyInit = new Blob([this._bodyArrayBuffer]) | |
} else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { | |
this._bodyArrayBuffer = bufferClone(body) | |
} else { | |
throw new Error('unsupported BodyInit type') | |
} | |
if (!this.headers.get('content-type')) { | |
if (typeof body === 'string') { | |
this.headers.set('content-type', 'text/plain;charset=UTF-8') | |
} else if (this._bodyBlob && this._bodyBlob.type) { | |
this.headers.set('content-type', this._bodyBlob.type) | |
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { | |
this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8') | |
} | |
} | |
} | |
if (support.blob) { | |
this.blob = function() { | |
var rejected = consumed(this) | |
if (rejected) { | |
return rejected | |
} | |
if (this._bodyBlob) { | |
return Promise.resolve(this._bodyBlob) | |
} else if (this._bodyArrayBuffer) { | |
return Promise.resolve(new Blob([this._bodyArrayBuffer])) | |
} else if (this._bodyFormData) { | |
throw new Error('could not read FormData body as blob') | |
} else { | |
return Promise.resolve(new Blob([this._bodyText])) | |
} | |
} | |
this.arrayBuffer = function() { | |
if (this._bodyArrayBuffer) { | |
return consumed(this) || Promise.resolve(this._bodyArrayBuffer) | |
} else { | |
return this.blob().then(readBlobAsArrayBuffer) | |
} | |
} | |
} | |
this.text = function() { | |
var rejected = consumed(this) | |
if (rejected) { | |
return rejected | |
} | |
if (this._bodyBlob) { | |
return readBlobAsText(this._bodyBlob) | |
} else if (this._bodyArrayBuffer) { | |
return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) | |
} else if (this._bodyFormData) { | |
throw new Error('could not read FormData body as text') | |
} else { | |
return Promise.resolve(this._bodyText) | |
} | |
} | |
if (support.formData) { | |
this.formData = function() { | |
return this.text().then(decode) | |
} | |
} | |
this.json = function() { | |
return this.text().then(JSON.parse) | |
} | |
return this | |
} | |
// HTTP methods whose capitalization should be normalized | |
var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] | |
function normalizeMethod(method) { | |
var upcased = method.toUpperCase() | |
return (methods.indexOf(upcased) > -1) ? upcased : method | |
} | |
function Request(input, options) { | |
options = options || {} | |
var body = options.body | |
if (input instanceof Request) { | |
if (input.bodyUsed) { | |
throw new TypeError('Already read') | |
} | |
this.url = input.url | |
this.credentials = input.credentials | |
if (!options.headers) { | |
this.headers = new Headers(input.headers) | |
} | |
this.method = input.method | |
this.mode = input.mode | |
if (!body && input._bodyInit != null) { | |
body = input._bodyInit | |
input.bodyUsed = true | |
} | |
} else { | |
this.url = String(input) | |
} | |
this.credentials = options.credentials || this.credentials || 'omit' | |
if (options.headers || !this.headers) { | |
this.headers = new Headers(options.headers) | |
} | |
this.method = normalizeMethod(options.method || this.method || 'GET') | |
this.mode = options.mode || this.mode || null | |
this.referrer = null | |
if ((this.method === 'GET' || this.method === 'HEAD') && body) { | |
throw new TypeError('Body not allowed for GET or HEAD requests') | |
} | |
this._initBody(body) | |
} | |
Request.prototype.clone = function() { | |
return new Request(this, { body: this._bodyInit }) | |
} | |
function decode(body) { | |
var form = new FormData() | |
body.trim().split('&').forEach(function(bytes) { | |
if (bytes) { | |
var split = bytes.split('=') | |
var name = split.shift().replace(/\+/g, ' ') | |
var value = split.join('=').replace(/\+/g, ' ') | |
form.append(decodeURIComponent(name), decodeURIComponent(value)) | |
} | |
}) | |
return form | |
} | |
function parseHeaders(rawHeaders) { | |
var headers = new Headers() | |
rawHeaders.split(/\r?\n/).forEach(function(line) { | |
var parts = line.split(':') | |
var key = parts.shift().trim() | |
if (key) { | |
var value = parts.join(':').trim() | |
headers.append(key, value) | |
} | |
}) | |
return headers | |
} | |
Body.call(Request.prototype) | |
function Response(bodyInit, options) { | |
if (!options) { | |
options = {} | |
} | |
this.type = 'default' | |
this.status = 'status' in options ? options.status : 200 | |
this.ok = this.status >= 200 && this.status < 300 | |
this.statusText = 'statusText' in options ? options.statusText : 'OK' | |
this.headers = new Headers(options.headers) | |
this.url = options.url || '' | |
this._initBody(bodyInit) | |
} | |
Body.call(Response.prototype) | |
Response.prototype.clone = function() { | |
return new Response(this._bodyInit, { | |
status: this.status, | |
statusText: this.statusText, | |
headers: new Headers(this.headers), | |
url: this.url | |
}) | |
} | |
Response.error = function() { | |
var response = new Response(null, {status: 0, statusText: ''}) | |
response.type = 'error' | |
return response | |
} | |
var redirectStatuses = [301, 302, 303, 307, 308] | |
Response.redirect = function(url, status) { | |
if (redirectStatuses.indexOf(status) === -1) { | |
throw new RangeError('Invalid status code') | |
} | |
return new Response(null, {status: status, headers: {location: url}}) | |
} | |
self.Headers = Headers | |
self.Request = Request | |
self.Response = Response | |
self.fetch = function(input, init) { | |
return new Promise(function(resolve, reject) { | |
var request = new Request(input, init) | |
var xhr = new XMLHttpRequest() | |
xhr.onload = function() { | |
var options = { | |
status: xhr.status, | |
statusText: xhr.statusText, | |
headers: parseHeaders(xhr.getAllResponseHeaders() || '') | |
} | |
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') | |
var body = 'response' in xhr ? xhr.response : xhr.responseText | |
resolve(new Response(body, options)) | |
} | |
xhr.onerror = function() { | |
reject(new TypeError('Network request failed')) | |
} | |
xhr.ontimeout = function() { | |
reject(new TypeError('Network request failed')) | |
} | |
xhr.open(request.method, request.url, true) | |
if (request.credentials === 'include') { | |
xhr.withCredentials = true | |
} | |
if ('responseType' in xhr && support.blob) { | |
xhr.responseType = 'blob' | |
} | |
request.headers.forEach(function(value, name) { | |
xhr.setRequestHeader(name, value) | |
}) | |
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) | |
}) | |
} | |
self.fetch.polyfill = true | |
})(typeof self !== 'undefined' ? self : this); | |
},{}],7:[function(require,module,exports){ | |
"use strict"; | |
module.exports = function (dust) { | |
var tmpl = function (dust) { | |
dust.register("templates\/toggle", body_0);function body_0(chk, ctx) { | |
return chk.w("<a class=\"btn btn-small btn-inverse tracklist-toggle-text\" m-tooltip=\"Toggle tracklist\" m-click=\"tracklistShown=!tracklistShown\" ng-class=\"{'btn-toggled': tracklistShown}\"><svg class=\"\" xmlns=\"http://www.w3.org/2000/svg\" width=\"19px\" height=\"14px\" viewBox=\"0 0 19 14\" version=\"1.1\"><path class=\"st0\" d=\"M6,2h12c0.6,0,1-0.4,1-1s-0.4-1-1-1H6C5.4,0,5,0.4,5,1S5.4,2,6,2z M18,12H6c-0.6,0-1,0.4-1,1s0.4,1,1,1h12c0.6,0,1-0.4,1-1S18.6,12,18,12z M1.5,0H1C0.4,0,0,0.4,0,1s0.4,1,1,1h0.5c0.6,0,1-0.4,1-1S2.1,0,1.5,0z M1.5,12H1c-0.6,0-1,0.4-1,1s0.4,1,1,1h0.5c0.6,0,1-0.4,1-1S2.1,12,1.5,12z M18,6H6C5.4,6,5,6.4,5,7l0,0c0,0.6,0.4,1,1,1h12c0.6,0,1-0.4,1-1l0,0C19,6.4,18.6,6,18,6z M1.5,6H1C0.4,6,0,6.4,0,7s0.4,1,1,1h0.5c0.6,0,1-0.4,1-1S2.1,6,1.5,6z\"></path></svg><span ng-show=\"tracklistShown\" class=\"ng-hide\">Hide </span><span ng-show=\"!tracklistShown\" class=\"\">Show </span> tracklist</a>"); | |
}body_0.__dustBody = !0;return body_0; | |
}(dust);var f = function load(ctx, cb) { | |
var fn = cb ? 'render' : 'stream'; | |
return dust[fn](tmpl, ctx, cb); | |
};f.template = tmpl;return f; | |
}; | |
},{}],8:[function(require,module,exports){ | |
"use strict"; | |
module.exports = function (dust) { | |
var tmpl = function (dust) { | |
dust.register("templates\/tracklist", body_0);function body_0(chk, ctx) { | |
return chk.w("<div class=\"tracklist-wrap cloudcast-tracklist ng-hide\" ng-show=\"!tracklistShown\"><div class=\"inner-container\"><div class=\"content\"><h1>Tracklist</h1><ul class=\"show-tracklist\" ng-init=\"tracklistShown=false;audioLength=").f(ctx.get(["audio_length"], false), ctx, "h").w(";sectionStartTimes=[]\">").s(ctx.get(["sections"], false), ctx, { "block": body_1 }, {}).w("</ul></div></div></div>"); | |
}body_0.__dustBody = !0;function body_1(chk, ctx) { | |
return chk.w("<li ng-hide=\"juno.sections.length\"><em>").f(ctx.get(["track_number"], false), ctx, "h").w("</em>").x(ctx.get(["chapter"], false), ctx, { "else": body_2, "block": body_3 }, {}).x(ctx.get(["artist"], false), ctx, { "block": body_4 }, {}).w("</li>"); | |
}body_1.__dustBody = !0;function body_2(chk, ctx) { | |
return chk.w("<b title=\"").f(ctx.get(["title"], false), ctx, "h").w("\">").f(ctx.get(["title"], false), ctx, "h").w("</b>"); | |
}body_2.__dustBody = !0;function body_3(chk, ctx) { | |
return chk.w("<b title=\"").f(ctx.get(["chapter"], false), ctx, "h").w("\">").f(ctx.get(["chapter"], false), ctx, "h").w("</b>"); | |
}body_3.__dustBody = !0;function body_4(chk, ctx) { | |
return chk.w("<small> by <span>").f(ctx.get(["artist"], false), ctx, "h").w("</span></small>"); | |
}body_4.__dustBody = !0;return body_0; | |
}(dust);var f = function load(ctx, cb) { | |
var fn = cb ? 'render' : 'stream'; | |
return dust[fn](tmpl, ctx, cb); | |
};f.template = tmpl;return f; | |
}; | |
},{}],9:[function(require,module,exports){ | |
/* | |
* Mixcloud Tracklist browser extension | |
* | |
* Copyright (c) 2015 Andrew Lawson <http://adlawson.com> | |
* | |
* For the full copyright and license information, please view the LICENSE | |
* file that was distributed with this source code. | |
* | |
* @link https://github.com/adlawson/mixcloud-tracklist | |
*/ | |
'use strict'; | |
require('whatwg-fetch'); // Patches window.fetch | |
require('url-polyfill'); // Patches window.URL | |
var dust = require('dustjs-linkedin'); | |
var Promise = require('promise-polyfill'); | |
var browser = require('./browser'); | |
main(); | |
browser.onChange(browser.querySelector('[m-contents="maincontent"]'), main); | |
function main() { | |
var parent = browser.querySelector('[ng-controller="CloudcastHeaderCtrl"]'); | |
if (parent !== null) { | |
fetchData(window.location, function (data) { | |
var tracklistTemplate = require('./templates/tracklist')(dust); // Required by both new and legacy | |
var empty = parent.querySelector('[ng-init]'); | |
var toggleContainer = browser.querySelector('footer.actions'); | |
var moreButton = toggleContainer.querySelector('[ng-controller="DropdownCtrl"]'); | |
var existingButton = toggleContainer.querySelector('[m-click="tracklistShown=!tracklistShown"]'); | |
if (existingButton === null) { | |
// If looking at your own mix | |
render(tracklistTemplate, data.cloudcast, function (tracklistHtml) { | |
browser.insert(empty, tracklistHtml); | |
render(require('./templates/toggle')(dust), {}, function (toggleHtml) { | |
browser.insertBefore(toggleContainer, moreButton, toggleHtml); | |
toggleEvents(empty, toggleContainer); | |
}); | |
}); | |
} | |
}); | |
} | |
} | |
function fetchData(location, fn) { | |
var url = new URL(location.protocol + '//' + location.hostname + '/player/details/'); | |
url.searchParams.append('key', location.pathname); | |
fetch(url, { credentials: 'include' }).then(rejectFailed).then(function (response) { | |
return response.json(); | |
}).then(rejectEmpty).then(function (data) { | |
return fn(insertTrackNumber(data)); | |
}).catch(function (err) { | |
return console.warn("Tracklist unavailable", err); | |
}); | |
} | |
function rejectEmpty(data) { | |
if (data.cloudcast.sections.length > 0) { | |
return Promise.resolve(data); | |
} else { | |
return Promise.reject(new ReferenceError("No tracklist returned")); | |
} | |
} | |
function rejectFailed(response) { | |
if (response.status >= 200 && response.status < 300) { | |
return Promise.resolve(response); | |
} else { | |
return Promise.reject(new Error(response.statusText)); | |
} | |
} | |
function insertTrackNumber(data) { | |
data.cloudcast.sections.forEach(function (section, i) { | |
section.track_number = i + 1; | |
}); | |
return data; | |
} | |
function render(source, data, fn) { | |
dust.render(source, data, function (error, html) { | |
if (!error) fn(html);else console.error(error); | |
}); | |
} | |
function toggleEvents(tracklistContainer, toggleContainer) { | |
var button = toggleContainer.querySelector('.tracklist-toggle-text'); | |
var tracklist = tracklistContainer.querySelector('.cloudcast-tracklist'); | |
button.addEventListener('click', function (event) { | |
var hide = button.querySelector('[ng-show="tracklistShown"]'); | |
var show = button.querySelector('[ng-show="!tracklistShown"]'); | |
hide.classList.toggle('ng-hide'); | |
show.classList.toggle('ng-hide'); | |
button.classList.toggle('btn-toggled'); | |
tracklist.classList.toggle('ng-hide'); | |
}); | |
} | |
},{"./browser":1,"./templates/toggle":7,"./templates/tracklist":8,"dustjs-linkedin":2,"promise-polyfill":4,"url-polyfill":5,"whatwg-fetch":6}]},{},[9]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment