(function(/*! Brunch !*/) {
'use strict';
var globals = typeof window !== 'undefined' ? window : global;
if (typeof globals.require === 'function') return;
var modules = {};
var cache = {};
var has = function(object, name) {
return ({}).hasOwnProperty.call(object, name);
};
var expand = function(root, name) {
var results = [], parts, part;
if (/^\.\.?(\/|$)/.test(name)) {
parts = [root, name].join('/').split('/');
} else {
parts = name.split('/');
}
for (var i = 0, length = parts.length; i < length; i++) {
part = parts[i];
if (part === '..') {
results.pop();
} else if (part !== '.' && part !== '') {
results.push(part);
}
}
return results.join('/');
};
var dirname = function(path) {
return path.split('/').slice(0, -1).join('/');
};
var localRequire = function(path) {
return function(name) {
var dir = dirname(path);
var absolute = expand(dir, name);
return globals.require(absolute, path);
};
};
var initModule = function(name, definition) {
var module = {id: name, exports: {}};
cache[name] = module;
definition(module.exports, localRequire(name), module);
return module.exports;
};
var require = function(name, loaderPath) {
var path = expand(name, '.');
if (loaderPath == null) loaderPath = '/';
if (has(cache, path)) return cache[path].exports;
if (has(modules, path)) return initModule(path, modules[path]);
var dirIndex = expand(path, './index');
if (has(cache, dirIndex)) return cache[dirIndex].exports;
if (has(modules, dirIndex)) return initModule(dirIndex, modules[dirIndex]);
throw new Error('Cannot find module "' + name + '" from '+ '"' + loaderPath + '"');
};
var define = function(bundle, fn) {
if (typeof bundle === 'object') {
for (var key in bundle) {
if (has(bundle, key)) {
modules[key] = bundle[key];
}
}
} else {
modules[bundle] = fn;
}
};
var list = function() {
var result = [];
for (var item in modules) {
if (has(modules, item)) {
result.push(item);
}
}
return result;
};
globals.require = require;
globals.require.define = define;
globals.require.register = define;
globals.require.list = list;
globals.require.brunch = true;
})();
require.register("scripts/default-server", function(exports, require, module) {
// Default Shutterbug server that is used by JS library.
module.exports = "//snapshot.concord.org/shutterbug";
// Note that when you work on the server-side features, you can change this
// path to localhost and all the demo pages will automatically use your local
// server, as they don't specify server explicitly. Just uncomment this line:
// module.exports = "//localhost:9292/shutterbug";
});
require.register("scripts/html-tools", function(exports, require, module) {
var $ = jQuery;
module.exports = {
cloneDomItem: function($elem, elemTag) {
var $returnElm = $(elemTag);
$returnElm.addClass($elem.attr('class'));
$returnElm.attr('style', $elem.attr('style'));
$returnElm.css('background', $elem.css('background'));
$returnElm.attr('width', $elem.width());
$returnElm.attr('height', $elem.height());
return $returnElm;
},
generateFullHtmlFromFragment: function(fragment) {
return "" +
"" +
"
" +
"" +
"" +
"content from " + fragment.base_url + "" +
fragment.css +
"" +
"" +
fragment.content +
"" +
"";
},
dataURLtoBlob: function(dataURL) {
// Convert base64/URLEncoded data component to raw binary data held in a string.
if (dataURL.split(',')[0].indexOf('base64') === -1) {
throw new Error('expected base64 data');
}
var byteString = atob(dataURL.split(',')[1]);
// Separate out the mime component.
var mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0];
// Write the bytes of the string to a typed array.
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], {type: mimeString});
}
};
});
require.register("scripts/shutterbug-worker", function(exports, require, module) {
var $ = jQuery;
var htmlTools = require('scripts/html-tools');
var DEFAULT_SERVER = require('scripts/default-server');
var MAX_TIMEOUT = 1500;
var BIN_DATA_SUPPORTED = typeof(window.Blob) === 'function' &&
typeof(window.Uint8Array) === 'function'; // IE9
// Each shutterbug instance on a single page requires unique ID (iframe-iframe communication).
var _id = 0;
function getID() {
return _id++;
}
function ShutterbugWorker(options) {
var opt = options || {};
if (!opt.selector) {
throw new Error("missing required option: selector");
}
this.element = opt.selector;
this.callback = opt.done;
this.failCallback = opt.fail;
this.alwaysCallback = opt.always;
this.imgDst = opt.dstSelector;
this.server = opt.server || DEFAULT_SERVER;
this.imageFormat = opt.format || 'png';
this.imageQuality = opt.quality || 1;
this._useIframeSizeHack = opt.useIframeSizeHack;
this.id = getID();
this.iframeReqTimeout = MAX_TIMEOUT;
// Bind and save a new function, so it works well with .add/removeEventListener().
this._postMessageHandler = this._postMessageHandler.bind(this);
};
ShutterbugWorker.prototype.enableIframeCommunication = function() {
$(document).ready(function() {
window.addEventListener('message', this._postMessageHandler, false);
}.bind(this));
};
ShutterbugWorker.prototype.disableIframeCommunication = function() {
window.removeEventListener('message', this._postMessageHandler, false);
};
ShutterbugWorker.prototype.getHtmlFragment = function(callback) {
var self = this;
var $element = $(this.element);
// .find('iframe').addBack("iframe") handles two cases:
// - element itself is an iframe - .addBack('iframe')
// - element descentands are iframes - .find('iframe')
var $iframes = $element.find('iframe').addBack("iframe");
this._iframeContentRequests = [];
$iframes.each(function(i, iframeElem) {
// Note that position of the iframe is used as its ID.
self._postHtmlFragRequestToIframe(iframeElem, i);
});
// Continue when we receive responses from all the nested iframes.
// Nested iframes descriptions will be provided as arguments.
$.when.apply($, this._iframeContentRequests).done(function() {
$element.trigger('shutterbug-saycheese');
var css = $('').append($('link[rel="stylesheet"]').clone()).append($('style').clone()).html();
var width = $element.width();
var height = $element.height();
var element = $element.clone();
// remove all script elements from the clone we don't want the html fragement
// changing itself
element.find("script").remove();
if (arguments.length > 0) {
var nestedIFrames = arguments;
// This supports two cases:
// - element itself is an iframe - .addBack('iframe')
// - element descentands are iframes - .find('iframe')
element.find("iframe").addBack("iframe").each(function(i, iframeElem) {
// When iframe doesn't support Shutterbug, request will timeout and null will be received.
// In such case just ignore this iframe, we won't be able to render it.
if (nestedIFrames[i] == null) return;
$(iframeElem).attr("src", "data:text/html," + htmlTools.generateFullHtmlFromFragment(nestedIFrames[i]));
});
}
// .addBack('canvas') handles case when the element itself is a canvas.
var replacementImgs = $element.find('canvas').addBack('canvas').map(function(i, elem) {
// Use png here, as it supports transparency and canvas can be layered on top of other elements.
var dataUrl = elem.toDataURL('image/png');
var img = htmlTools.cloneDomItem($(elem), "
![]()
");
img.attr('src', dataUrl);
return img;
});
if (element.is('canvas')) {
element = replacementImgs[0];
} else {
element.find('canvas').each(function(i, elem) {
$(elem).replaceWith(replacementImgs[i]);
});
}
element.css({
'top': 0,
'left': 0,
'margin': 0,
'width': width,
'height': height
});
// Due to a weird layout bug in PhantomJS, inner iframes sometimes don't render
// unless we set the width small. This doesn't affect the actual output at all.
if (self._useIframeSizeHack) {
width = 10;
}
var html_content = {
content: $('
').append(element).html(),
css: css,
width: width,
height: height,
base_url: window.location.href
};
$element.trigger('shutterbug-asyouwere');
callback(html_content);
});
};
ShutterbugWorker.prototype.getDomSnapshot = function() {
this.enableIframeCommunication(); // !!!
// Start timer.
var self = this;
var time = 0;
var counter = $("
");
counter.html(time);
$(self.imgDst).html("creating snapshot: ").append(counter);
this.timer = setInterval(function(t) {
time = time + 1;
counter.html(time);
}, 1000);
var tagName = $(this.element).prop("tagName");
switch(tagName) {
case "CANVAS":
this.canvasSnapshot();
break;
default:
this.basicSnapshot();
break;
}
};
ShutterbugWorker.prototype.canvasSnapshot = function() {
if (!BIN_DATA_SUPPORTED) {
return this.basicSnapshot();
}
var self = this;
$.ajax({
type: 'GET',
url: this.server + '/img_upload_url?format=' + this.imageFormat
}).done(function(data) {
self.directUpload(data);
}).fail(function() {
// Use basic snapshot as a fallback.
// Direct upload is not supported on server side (e.g. due to used storage).
self.basicSnapshot();
});
};
ShutterbugWorker.prototype.directUpload = function(options) {
var $canvas = $(this.element);
var dataURL = $canvas[0].toDataURL('image/' + this.imageFormat, this.imageQuality)
var blob = htmlTools.dataURLtoBlob(dataURL);
var self = this;
$.ajax({
type: 'PUT',
url: options.put_url,
data: blob,
processData: false,
contentType: false
}).done(function(data) {
self._successHandler("
");
}).fail(function(jqXHR, textStatus, errorThrown) {
self._failHandler(jqXHR, textStatus, errorThrown)
}).always(function() {
self._alwaysHandler();
});
}
ShutterbugWorker.prototype.basicSnapshot = function() {
var self = this;
// Ask for HTML fragment and render it on server.
this.getHtmlFragment(function(html_data) {
html_data.format = self.imageFormat;
html_data.quality = self.imageQuality;
$.ajax({
url: self.server + '/make_snapshot',
type: 'POST',
data: html_data
}).done(function(msg) {
self._successHandler(msg)
}).fail(function(jqXHR, textStatus, errorThrown) {
self._failHandler(jqXHR, textStatus, errorThrown);
}).always(function() {
self._alwaysHandler();
});
});
};
ShutterbugWorker.prototype._successHandler = function(imageTag) {
if (this.imgDst) {
$(this.imgDst).html(imageTag);
}
if (this.callback) {
// Extract the url out of the returned html fragment.
var imgUrl = imageTag.match(/src=['"]([^'"]*)['"]/)[1];
this.callback(imgUrl);
}
};
ShutterbugWorker.prototype._failHandler = function(jqXHR, textStatus, errorThrown) {
if (this.imgDst) {
$(this.imgDst).html("snapshot failed");
}
if (this.failCallback) {
this.failCallback(jqXHR, textStatus, errorThrown);
}
}
ShutterbugWorker.prototype._alwaysHandler = function() {
clearInterval(this.timer);
this.disableIframeCommunication(); // !!!
if (this.alwaysCallback) {
this.alwaysCallback();
}
};
ShutterbugWorker.prototype.htmlSnap = function() {
this.getHtmlFragment(function callback(fragment) {
// FIXME btoa is not intended to encode text it is for for 8bit per char strings
// so if you send it a UTF8 string with a special char in it, it will fail
// this SO has a note about handling this:
// http://stackoverflow.com/questions/246801/how-can-you-encode-a-string-to-base64-in-javascript
// also note that btoa is only available in IE10+
var encodedContent = btoa(htmlTools.generateFullHtmlFromFragment(fragment));
window.open("data:text/html;base64," + encodedContent);
});
};
ShutterbugWorker.prototype.imageSnap = function() {
var oldImgDst = this.imgDst,
oldCallback = this.callback,
self = this;
this.imgDst = null;
this.callback = function (imgUrl) {
window.open(imgUrl);
self.imgDst = oldImgDst;
self.callback = oldCallback;
}
this.getDomSnapshot();
};
// ### Iframe-iframe communication related methods ###
// Basic post message handler.
ShutterbugWorker.prototype._postMessageHandler = function(message) {
function handleMessage(message, type, handler) {
var data = message.data;
if (typeof data === 'string') {
try {
data = JSON.parse(data);
if (data.type === type) {
handler(data, message.source);
}
} catch(e) {
// Not a json message. Ignore it. We only speak json.
}
}
}
handleMessage(message, 'htmlFragRequest', this._htmlFragRequestHandler.bind(this));
handleMessage(message, 'htmlFragResponse', this._htmlFragResponseHandler.bind(this));
};
// Iframe receives question about its content.
ShutterbugWorker.prototype._htmlFragRequestHandler = function(data, source) {
// Update timeout. When we receive a request from parent, we have to finish nested iframes
// rendering in that time. Otherwise parent rendering will timeout.
// Backward compatibility: Shutterbug v0.1.x don't send iframeReqTimeout.
this.iframeReqTimeout = data.iframeReqTimeout != null ? data.iframeReqTimeout : MAX_TIMEOUT;
this.getHtmlFragment(function(html) {
var response = {
type: 'htmlFragResponse',
value: html,
iframeReqId: data.iframeReqId,
id: data.id // return to sender only
};
source.postMessage(JSON.stringify(response), "*");
});
};
// Parent receives content from iframes.
ShutterbugWorker.prototype._htmlFragResponseHandler = function(data) {
if (data.id === this.id) {
// Backward compatibility: Shutterbug v0.1.x don't send iframeReqId.
var iframeReqId = data.iframeReqId != null ? data.iframeReqId : 0;
this._iframeContentRequests[iframeReqId].resolve(data.value);
}
};
// Parent asks iframes about their content.
ShutterbugWorker.prototype._postHtmlFragRequestToIframe = function(iframeElem, iframeId) {
var message = {
type: 'htmlFragRequest',
id: this.id,
iframeReqId: iframeId,
// We have to provide smaller timeout while sending message to nested iframes.
// Otherwise, when one of the nested iframes timeouts, then all will do the
// same and we won't render anything - even iframes that support Shutterbug.
iframeReqTimeout: this.iframeReqTimeout * 0.6
};
iframeElem.contentWindow.postMessage(JSON.stringify(message), "*");
var requestDeffered = new $.Deferred();
this._iframeContentRequests[iframeId] = requestDeffered;
setTimeout(function() {
// It handles a situation in which iframe doesn't support Shutterbug.
// When we doesn't receive answer for some time, assume that we can't
// render this particular iframe (provide null as iframe description).
if (requestDeffered.state() !== "resolved") {
requestDeffered.resolve(null);
}
}, this.iframeReqTimeout);
};
// ###
module.exports = ShutterbugWorker;
});
require.register("scripts/shutterbug", function(exports, require, module) {
var ShutterbugWorker = require('scripts/shutterbug-worker');
function parseSnapshotArguments(arguments) {
var selector;
var doneCallback;
var dstSelector;
var options = {};
function assignSecondArgument(arg) {
if (typeof arg === 'string') { dstSelector = arg; }
else if (typeof arg === 'function') { doneCallback = arg; }
else if (typeof arg === 'object') { options = arg; }
}
if (arguments.length === 3) {
options = arguments[2];
assignSecondArgument(arguments[1]);
selector = arguments[0];
} else if (arguments.length === 2) {
assignSecondArgument(arguments[1]);
selector = arguments[0];
} else if (arguments.length === 1) {
options = arguments[0];
}
if (selector) { options.selector = selector; }
if (doneCallback) { options.done = doneCallback; }
if (dstSelector) { options.dstSelector = dstSelector; }
return options;
}
module.exports = {
snapshot: function() {
var options = parseSnapshotArguments(arguments);
var worker = new ShutterbugWorker(options);
worker.getDomSnapshot();
},
enable: function(selector) {
this.disable();
selector = selector || 'body';
this._iframeWorker = new ShutterbugWorker({selector: selector});
this._iframeWorker.enableIframeCommunication();
},
disable: function() {
if (this._iframeWorker) {
this._iframeWorker.disableIframeCommunication();
}
}
};
});
// Do not force users to call require() manually, export Shutterbug constructor.
// See: https://github.com/brunch/brunch/issues/712
window.Shutterbug = require('scripts/shutterbug');