move ssb-patchwork-api and ssb-patchwork-ui into this repo
This commit is contained in:
633
ui/vendor/behave.js
vendored
Normal file
633
ui/vendor/behave.js
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
/*
|
||||
* Behave.js
|
||||
*
|
||||
* Copyright 2013, Jacob Kelley - http://jakiestfu.com/
|
||||
* Released under the MIT Licence
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Github: http://github.com/jakiestfu/Behave.js/
|
||||
* Version: 1.5
|
||||
*/
|
||||
|
||||
|
||||
(function(undefined){
|
||||
|
||||
'use strict';
|
||||
|
||||
var BehaveHooks = BehaveHooks || (function(){
|
||||
var hooks = {};
|
||||
|
||||
return {
|
||||
add: function(hookName, fn){
|
||||
if(typeof hookName == "object"){
|
||||
var i;
|
||||
for(i=0; i<hookName.length; i++){
|
||||
var theHook = hookName[i];
|
||||
if(!hooks[theHook]){
|
||||
hooks[theHook] = [];
|
||||
}
|
||||
hooks[theHook].push(fn);
|
||||
}
|
||||
} else {
|
||||
if(!hooks[hookName]){
|
||||
hooks[hookName] = [];
|
||||
}
|
||||
hooks[hookName].push(fn);
|
||||
}
|
||||
},
|
||||
get: function(hookName){
|
||||
if(hooks[hookName]){
|
||||
return hooks[hookName];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})(),
|
||||
Behave = Behave || function (userOpts) {
|
||||
|
||||
if (typeof String.prototype.repeat !== 'function') {
|
||||
String.prototype.repeat = function(times) {
|
||||
if(times < 1){
|
||||
return '';
|
||||
}
|
||||
if(times % 2){
|
||||
return this.repeat(times - 1) + this;
|
||||
}
|
||||
var half = this.repeat(times / 2);
|
||||
return half + half;
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof Array.prototype.filter !== 'function') {
|
||||
Array.prototype.filter = function(func /*, thisp */) {
|
||||
if (this === null) {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
var t = Object(this),
|
||||
len = t.length >>> 0;
|
||||
if (typeof func != "function"){
|
||||
throw new TypeError();
|
||||
}
|
||||
var res = [],
|
||||
thisp = arguments[1];
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (i in t) {
|
||||
var val = t[i];
|
||||
if (func.call(thisp, val, i, t)) {
|
||||
res.push(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
var defaults = {
|
||||
textarea: null,
|
||||
replaceTab: true,
|
||||
softTabs: true,
|
||||
tabSize: 4,
|
||||
autoOpen: true,
|
||||
overwrite: true,
|
||||
autoStrip: true,
|
||||
autoIndent: true,
|
||||
fence: false
|
||||
},
|
||||
tab,
|
||||
newLine,
|
||||
charSettings = {
|
||||
|
||||
keyMap: [
|
||||
{ open: "\"", close: "\"", canBreak: false },
|
||||
{ open: "'", close: "'", canBreak: false },
|
||||
{ open: "(", close: ")", canBreak: false },
|
||||
{ open: "[", close: "]", canBreak: true },
|
||||
{ open: "{", close: "}", canBreak: true }
|
||||
]
|
||||
|
||||
},
|
||||
utils = {
|
||||
|
||||
_callHook: function(hookName, passData){
|
||||
var hooks = BehaveHooks.get(hookName);
|
||||
passData = typeof passData=="boolean" && passData === false ? false : true;
|
||||
|
||||
if(hooks){
|
||||
if(passData){
|
||||
var theEditor = defaults.textarea,
|
||||
textVal = theEditor.value,
|
||||
caretPos = utils.cursor.get(),
|
||||
i;
|
||||
|
||||
for(i=0; i<hooks.length; i++){
|
||||
hooks[i].call(undefined, {
|
||||
editor: {
|
||||
element: theEditor,
|
||||
text: textVal,
|
||||
levelsDeep: utils.levelsDeep()
|
||||
},
|
||||
caret: {
|
||||
pos: caretPos
|
||||
},
|
||||
lines: {
|
||||
current: utils.cursor.getLine(textVal, caretPos),
|
||||
total: utils.editor.getLines(textVal)
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for(i=0; i<hooks.length; i++){
|
||||
hooks[i].call(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
defineNewLine: function(){
|
||||
var ta = document.createElement('textarea');
|
||||
ta.value = "\n";
|
||||
|
||||
if(ta.value.length==2){
|
||||
newLine = "\r\n";
|
||||
} else {
|
||||
newLine = "\n";
|
||||
}
|
||||
},
|
||||
defineTabSize: function(tabSize){
|
||||
if(typeof defaults.textarea.style.OTabSize != "undefined"){
|
||||
defaults.textarea.style.OTabSize = tabSize; return;
|
||||
}
|
||||
if(typeof defaults.textarea.style.MozTabSize != "undefined"){
|
||||
defaults.textarea.style.MozTabSize = tabSize; return;
|
||||
}
|
||||
if(typeof defaults.textarea.style.tabSize != "undefined"){
|
||||
defaults.textarea.style.tabSize = tabSize; return;
|
||||
}
|
||||
},
|
||||
cursor: {
|
||||
getLine: function(textVal, pos){
|
||||
return ((textVal.substring(0,pos)).split("\n")).length;
|
||||
},
|
||||
get: function() {
|
||||
|
||||
if (typeof document.createElement('textarea').selectionStart==="number") {
|
||||
return defaults.textarea.selectionStart;
|
||||
} else if (document.selection) {
|
||||
var caretPos = 0,
|
||||
range = defaults.textarea.createTextRange(),
|
||||
rangeDupe = document.selection.createRange().duplicate(),
|
||||
rangeDupeBookmark = rangeDupe.getBookmark();
|
||||
range.moveToBookmark(rangeDupeBookmark);
|
||||
|
||||
while (range.moveStart('character' , -1) !== 0) {
|
||||
caretPos++;
|
||||
}
|
||||
return caretPos;
|
||||
}
|
||||
},
|
||||
set: function (start, end) {
|
||||
if(!end){
|
||||
end = start;
|
||||
}
|
||||
if (defaults.textarea.setSelectionRange) {
|
||||
defaults.textarea.focus();
|
||||
defaults.textarea.setSelectionRange(start, end);
|
||||
} else if (defaults.textarea.createTextRange) {
|
||||
var range = defaults.textarea.createTextRange();
|
||||
range.collapse(true);
|
||||
range.moveEnd('character', end);
|
||||
range.moveStart('character', start);
|
||||
range.select();
|
||||
}
|
||||
},
|
||||
selection: function(){
|
||||
var textAreaElement = defaults.textarea,
|
||||
start = 0,
|
||||
end = 0,
|
||||
normalizedValue,
|
||||
range,
|
||||
textInputRange,
|
||||
len,
|
||||
endRange;
|
||||
|
||||
if (typeof textAreaElement.selectionStart == "number" && typeof textAreaElement.selectionEnd == "number") {
|
||||
start = textAreaElement.selectionStart;
|
||||
end = textAreaElement.selectionEnd;
|
||||
} else {
|
||||
range = document.selection.createRange();
|
||||
|
||||
if (range && range.parentElement() == textAreaElement) {
|
||||
|
||||
normalizedValue = utils.editor.get();
|
||||
len = normalizedValue.length;
|
||||
|
||||
textInputRange = textAreaElement.createTextRange();
|
||||
textInputRange.moveToBookmark(range.getBookmark());
|
||||
|
||||
endRange = textAreaElement.createTextRange();
|
||||
endRange.collapse(false);
|
||||
|
||||
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
||||
start = end = len;
|
||||
} else {
|
||||
start = -textInputRange.moveStart("character", -len);
|
||||
start += normalizedValue.slice(0, start).split(newLine).length - 1;
|
||||
|
||||
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
|
||||
end = len;
|
||||
} else {
|
||||
end = -textInputRange.moveEnd("character", -len);
|
||||
end += normalizedValue.slice(0, end).split(newLine).length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return start==end ? false : {
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
}
|
||||
},
|
||||
editor: {
|
||||
getLines: function(textVal){
|
||||
return (textVal).split("\n").length;
|
||||
},
|
||||
get: function(){
|
||||
return defaults.textarea.value.replace(/\r/g,'');
|
||||
},
|
||||
set: function(data){
|
||||
defaults.textarea.value = data;
|
||||
}
|
||||
},
|
||||
fenceRange: function(){
|
||||
if(typeof defaults.fence == "string"){
|
||||
|
||||
var data = utils.editor.get(),
|
||||
pos = utils.cursor.get(),
|
||||
hacked = 0,
|
||||
matchedFence = data.indexOf(defaults.fence),
|
||||
matchCase = 0;
|
||||
|
||||
while(matchedFence>=0){
|
||||
matchCase++;
|
||||
if( pos < (matchedFence+hacked) ){
|
||||
break;
|
||||
}
|
||||
|
||||
hacked += matchedFence+defaults.fence.length;
|
||||
data = data.substring(matchedFence+defaults.fence.length);
|
||||
matchedFence = data.indexOf(defaults.fence);
|
||||
|
||||
}
|
||||
|
||||
if( (hacked) < pos && ( (matchedFence+hacked) > pos ) && matchCase%2===0){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
isEven: function(_this,i){
|
||||
return i%2;
|
||||
},
|
||||
levelsDeep: function(){
|
||||
var pos = utils.cursor.get(),
|
||||
val = utils.editor.get();
|
||||
|
||||
var left = val.substring(0, pos),
|
||||
levels = 0,
|
||||
i, j;
|
||||
|
||||
for(i=0; i<left.length; i++){
|
||||
for (j=0; j<charSettings.keyMap.length; j++) {
|
||||
if(charSettings.keyMap[j].canBreak){
|
||||
if(charSettings.keyMap[j].open == left.charAt(i)){
|
||||
levels++;
|
||||
}
|
||||
|
||||
if(charSettings.keyMap[j].close == left.charAt(i)){
|
||||
levels--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var toDecrement = 0,
|
||||
quoteMap = ["'", "\""];
|
||||
for(i=0; i<charSettings.keyMap.length; i++) {
|
||||
if(charSettings.keyMap[i].canBreak){
|
||||
for(j in quoteMap){
|
||||
toDecrement += left.split(quoteMap[j]).filter(utils.isEven).join('').split(charSettings.keyMap[i].open).length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var finalLevels = levels - toDecrement;
|
||||
|
||||
return finalLevels >=0 ? finalLevels : 0;
|
||||
},
|
||||
deepExtend: function(destination, source) {
|
||||
for (var property in source) {
|
||||
if (source[property] && source[property].constructor &&
|
||||
source[property].constructor === Object) {
|
||||
destination[property] = destination[property] || {};
|
||||
utils.deepExtend(destination[property], source[property]);
|
||||
} else {
|
||||
destination[property] = source[property];
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
},
|
||||
addEvent: function addEvent(element, eventName, func) {
|
||||
if (element.addEventListener){
|
||||
element.addEventListener(eventName,func,false);
|
||||
} else if (element.attachEvent) {
|
||||
element.attachEvent("on"+eventName, func);
|
||||
}
|
||||
},
|
||||
removeEvent: function addEvent(element, eventName, func){
|
||||
if (element.addEventListener){
|
||||
element.removeEventListener(eventName,func,false);
|
||||
} else if (element.attachEvent) {
|
||||
element.detachEvent("on"+eventName, func);
|
||||
}
|
||||
},
|
||||
|
||||
preventDefaultEvent: function(e){
|
||||
if(e.preventDefault){
|
||||
e.preventDefault();
|
||||
} else {
|
||||
e.returnValue = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
intercept = {
|
||||
tabKey: function (e) {
|
||||
|
||||
if(!utils.fenceRange()){ return; }
|
||||
|
||||
if (e.keyCode == 9) {
|
||||
utils.preventDefaultEvent(e);
|
||||
|
||||
var toReturn = true;
|
||||
utils._callHook('tab:before');
|
||||
|
||||
var selection = utils.cursor.selection(),
|
||||
pos = utils.cursor.get(),
|
||||
val = utils.editor.get();
|
||||
|
||||
if(selection){
|
||||
|
||||
var tempStart = selection.start;
|
||||
while(tempStart--){
|
||||
if(val.charAt(tempStart)=="\n"){
|
||||
selection.start = tempStart + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var toIndent = val.substring(selection.start, selection.end),
|
||||
lines = toIndent.split("\n"),
|
||||
i;
|
||||
|
||||
if(e.shiftKey){
|
||||
for(i = 0; i<lines.length; i++){
|
||||
if(lines[i].substring(0,tab.length) == tab){
|
||||
lines[i] = lines[i].substring(tab.length);
|
||||
}
|
||||
}
|
||||
toIndent = lines.join("\n");
|
||||
|
||||
utils.editor.set( val.substring(0,selection.start) + toIndent + val.substring(selection.end) );
|
||||
utils.cursor.set(selection.start, selection.start+toIndent.length);
|
||||
|
||||
} else {
|
||||
for(i in lines){
|
||||
lines[i] = tab + lines[i];
|
||||
}
|
||||
toIndent = lines.join("\n");
|
||||
|
||||
utils.editor.set( val.substring(0,selection.start) + toIndent + val.substring(selection.end) );
|
||||
utils.cursor.set(selection.start, selection.start+toIndent.length);
|
||||
}
|
||||
} else {
|
||||
var left = val.substring(0, pos),
|
||||
right = val.substring(pos),
|
||||
edited = left + tab + right;
|
||||
|
||||
if(e.shiftKey){
|
||||
if(val.substring(pos-tab.length, pos) == tab){
|
||||
edited = val.substring(0, pos-tab.length) + right;
|
||||
utils.editor.set(edited);
|
||||
utils.cursor.set(pos-tab.length);
|
||||
}
|
||||
} else {
|
||||
utils.editor.set(edited);
|
||||
utils.cursor.set(pos + tab.length);
|
||||
toReturn = false;
|
||||
}
|
||||
}
|
||||
utils._callHook('tab:after');
|
||||
}
|
||||
return toReturn;
|
||||
},
|
||||
enterKey: function (e) {
|
||||
|
||||
if(!utils.fenceRange()){ return; }
|
||||
|
||||
if (e.keyCode == 13) {
|
||||
|
||||
utils.preventDefaultEvent(e);
|
||||
utils._callHook('enter:before');
|
||||
|
||||
var pos = utils.cursor.get(),
|
||||
val = utils.editor.get(),
|
||||
left = val.substring(0, pos),
|
||||
right = val.substring(pos),
|
||||
leftChar = left.charAt(left.length - 1),
|
||||
rightChar = right.charAt(0),
|
||||
numTabs = utils.levelsDeep(),
|
||||
ourIndent = "",
|
||||
closingBreak = "",
|
||||
finalCursorPos,
|
||||
i;
|
||||
if(!numTabs){
|
||||
finalCursorPos = 1;
|
||||
} else {
|
||||
while(numTabs--){
|
||||
ourIndent+=tab;
|
||||
}
|
||||
ourIndent = ourIndent;
|
||||
finalCursorPos = ourIndent.length + 1;
|
||||
|
||||
for(i=0; i<charSettings.keyMap.length; i++) {
|
||||
if (charSettings.keyMap[i].open == leftChar && charSettings.keyMap[i].close == rightChar){
|
||||
closingBreak = newLine;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var edited = left + newLine + ourIndent + closingBreak + (ourIndent.substring(0, ourIndent.length-tab.length) ) + right;
|
||||
utils.editor.set(edited);
|
||||
utils.cursor.set(pos + finalCursorPos);
|
||||
utils._callHook('enter:after');
|
||||
}
|
||||
},
|
||||
deleteKey: function (e) {
|
||||
|
||||
if(!utils.fenceRange()){ return; }
|
||||
|
||||
if(e.keyCode == 8){
|
||||
utils.preventDefaultEvent(e);
|
||||
|
||||
utils._callHook('delete:before');
|
||||
|
||||
var pos = utils.cursor.get(),
|
||||
val = utils.editor.get(),
|
||||
left = val.substring(0, pos),
|
||||
right = val.substring(pos),
|
||||
leftChar = left.charAt(left.length - 1),
|
||||
rightChar = right.charAt(0),
|
||||
i;
|
||||
|
||||
if( utils.cursor.selection() === false ){
|
||||
for(i=0; i<charSettings.keyMap.length; i++) {
|
||||
if (charSettings.keyMap[i].open == leftChar && charSettings.keyMap[i].close == rightChar) {
|
||||
var edited = val.substring(0,pos-1) + val.substring(pos+1);
|
||||
utils.editor.set(edited);
|
||||
utils.cursor.set(pos - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
var edited = val.substring(0,pos-1) + val.substring(pos);
|
||||
utils.editor.set(edited);
|
||||
utils.cursor.set(pos - 1);
|
||||
} else {
|
||||
var sel = utils.cursor.selection(),
|
||||
edited = val.substring(0,sel.start) + val.substring(sel.end);
|
||||
utils.editor.set(edited);
|
||||
utils.cursor.set(pos);
|
||||
}
|
||||
|
||||
utils._callHook('delete:after');
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
charFuncs = {
|
||||
openedChar: function (_char, e) {
|
||||
utils.preventDefaultEvent(e);
|
||||
utils._callHook('openChar:before');
|
||||
var pos = utils.cursor.get(),
|
||||
val = utils.editor.get(),
|
||||
left = val.substring(0, pos),
|
||||
right = val.substring(pos),
|
||||
edited = left + _char.open + _char.close + right;
|
||||
|
||||
defaults.textarea.value = edited;
|
||||
utils.cursor.set(pos + 1);
|
||||
utils._callHook('openChar:after');
|
||||
},
|
||||
closedChar: function (_char, e) {
|
||||
var pos = utils.cursor.get(),
|
||||
val = utils.editor.get(),
|
||||
toOverwrite = val.substring(pos, pos + 1);
|
||||
if (toOverwrite == _char.close) {
|
||||
utils.preventDefaultEvent(e);
|
||||
utils._callHook('closeChar:before');
|
||||
utils.cursor.set(utils.cursor.get() + 1);
|
||||
utils._callHook('closeChar:after');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
action = {
|
||||
filter: function (e) {
|
||||
|
||||
if(!utils.fenceRange()){ return; }
|
||||
|
||||
var theCode = e.which || e.keyCode;
|
||||
|
||||
if(theCode == 39 || theCode == 40 && e.which===0){ return; }
|
||||
|
||||
var _char = String.fromCharCode(theCode),
|
||||
i;
|
||||
|
||||
for(i=0; i<charSettings.keyMap.length; i++) {
|
||||
|
||||
if (charSettings.keyMap[i].close == _char) {
|
||||
var didClose = defaults.overwrite && charFuncs.closedChar(charSettings.keyMap[i], e);
|
||||
|
||||
if (!didClose && charSettings.keyMap[i].open == _char && defaults.autoOpen) {
|
||||
charFuncs.openedChar(charSettings.keyMap[i], e);
|
||||
}
|
||||
} else if (charSettings.keyMap[i].open == _char && defaults.autoOpen) {
|
||||
charFuncs.openedChar(charSettings.keyMap[i], e);
|
||||
}
|
||||
}
|
||||
},
|
||||
listen: function () {
|
||||
|
||||
if(defaults.replaceTab){ utils.addEvent(defaults.textarea, 'keydown', intercept.tabKey); }
|
||||
if(defaults.autoIndent){ utils.addEvent(defaults.textarea, 'keydown', intercept.enterKey); }
|
||||
if(defaults.autoStrip){ utils.addEvent(defaults.textarea, 'keydown', intercept.deleteKey); }
|
||||
|
||||
utils.addEvent(defaults.textarea, 'keypress', action.filter);
|
||||
|
||||
utils.addEvent(defaults.textarea, 'keydown', function(){ utils._callHook('keydown'); });
|
||||
utils.addEvent(defaults.textarea, 'keyup', function(){ utils._callHook('keyup'); });
|
||||
}
|
||||
},
|
||||
init = function (opts) {
|
||||
|
||||
if(opts.textarea){
|
||||
utils._callHook('init:before', false);
|
||||
utils.deepExtend(defaults, opts);
|
||||
utils.defineNewLine();
|
||||
|
||||
if (defaults.softTabs) {
|
||||
tab = " ".repeat(defaults.tabSize);
|
||||
} else {
|
||||
tab = "\t";
|
||||
|
||||
utils.defineTabSize(defaults.tabSize);
|
||||
}
|
||||
|
||||
action.listen();
|
||||
utils._callHook('init:after', false);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.destroy = function(){
|
||||
utils.removeEvent(defaults.textarea, 'keydown', intercept.tabKey);
|
||||
utils.removeEvent(defaults.textarea, 'keydown', intercept.enterKey);
|
||||
utils.removeEvent(defaults.textarea, 'keydown', intercept.deleteKey);
|
||||
utils.removeEvent(defaults.textarea, 'keypress', action.filter);
|
||||
};
|
||||
|
||||
init(userOpts);
|
||||
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = Behave;
|
||||
}
|
||||
|
||||
if (typeof ender === 'undefined') {
|
||||
this.Behave = Behave;
|
||||
this.BehaveHooks = BehaveHooks;
|
||||
}
|
||||
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define("behave", [], function () {
|
||||
return Behave;
|
||||
});
|
||||
}
|
||||
|
||||
}).call(this);
|
||||
95
ui/vendor/canvas-to-blob.js
vendored
Normal file
95
ui/vendor/canvas-to-blob.js
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* JavaScript Canvas to Blob 2.0.5
|
||||
* https://github.com/blueimp/JavaScript-Canvas-to-Blob
|
||||
*
|
||||
* Copyright 2012, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/MIT
|
||||
*
|
||||
* Based on stackoverflow user Stoive's code snippet:
|
||||
* http://stackoverflow.com/q/4998908
|
||||
*/
|
||||
|
||||
/*jslint nomen: true, regexp: true */
|
||||
/*global window, atob, Blob, ArrayBuffer, Uint8Array, define */
|
||||
|
||||
(function (window) {
|
||||
'use strict';
|
||||
var CanvasPrototype = window.HTMLCanvasElement &&
|
||||
window.HTMLCanvasElement.prototype,
|
||||
hasBlobConstructor = window.Blob && (function () {
|
||||
try {
|
||||
return Boolean(new Blob());
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}()),
|
||||
hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
|
||||
(function () {
|
||||
try {
|
||||
return new Blob([new Uint8Array(100)]).size === 100;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}()),
|
||||
BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
|
||||
window.MozBlobBuilder || window.MSBlobBuilder,
|
||||
dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
|
||||
window.ArrayBuffer && window.Uint8Array && function (dataURI) {
|
||||
var byteString,
|
||||
arrayBuffer,
|
||||
intArray,
|
||||
i,
|
||||
mimeString,
|
||||
bb;
|
||||
if (dataURI.split(',')[0].indexOf('base64') >= 0) {
|
||||
// Convert base64 to raw binary data held in a string:
|
||||
byteString = atob(dataURI.split(',')[1]);
|
||||
} else {
|
||||
// Convert base64/URLEncoded data component to raw binary data:
|
||||
byteString = decodeURIComponent(dataURI.split(',')[1]);
|
||||
}
|
||||
// Write the bytes of the string to an ArrayBuffer:
|
||||
arrayBuffer = new ArrayBuffer(byteString.length);
|
||||
intArray = new Uint8Array(arrayBuffer);
|
||||
for (i = 0; i < byteString.length; i += 1) {
|
||||
intArray[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
// Separate out the mime component:
|
||||
mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
|
||||
// Write the ArrayBuffer (or ArrayBufferView) to a blob:
|
||||
if (hasBlobConstructor) {
|
||||
return new Blob(
|
||||
[hasArrayBufferViewSupport ? intArray : arrayBuffer],
|
||||
{type: mimeString}
|
||||
);
|
||||
}
|
||||
bb = new BlobBuilder();
|
||||
bb.append(arrayBuffer);
|
||||
return bb.getBlob(mimeString);
|
||||
};
|
||||
if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
|
||||
if (CanvasPrototype.mozGetAsFile) {
|
||||
CanvasPrototype.toBlob = function (callback, type, quality) {
|
||||
if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
|
||||
callback(dataURLtoBlob(this.toDataURL(type, quality)));
|
||||
} else {
|
||||
callback(this.mozGetAsFile('blob', type));
|
||||
}
|
||||
};
|
||||
} else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
|
||||
CanvasPrototype.toBlob = function (callback, type, quality) {
|
||||
callback(dataURLtoBlob(this.toDataURL(type, quality)));
|
||||
};
|
||||
}
|
||||
}
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(function () {
|
||||
return dataURLtoBlob;
|
||||
});
|
||||
} else {
|
||||
window.dataURLtoBlob = dataURLtoBlob;
|
||||
}
|
||||
}(this));
|
||||
4
ui/vendor/sigma.min.js
vendored
Executable file
4
ui/vendor/sigma.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
538
ui/vendor/whammy.js
vendored
Normal file
538
ui/vendor/whammy.js
vendored
Normal file
@@ -0,0 +1,538 @@
|
||||
// https://github.com/antimatter15/whammy/tree/4effe219137e48787f1e82c8bbc64fbf7b4cfeeb
|
||||
|
||||
/*
|
||||
var vid = new Whammy.Video();
|
||||
vid.add(canvas or data url)
|
||||
vid.compile()
|
||||
*/
|
||||
|
||||
window.Whammy = (function(){
|
||||
// in this case, frames has a very specific meaning, which will be
|
||||
// detailed once i finish writing the code
|
||||
|
||||
function toWebM(frames, outputAsArray){
|
||||
var info = checkFrames(frames);
|
||||
|
||||
//max duration by cluster in milliseconds
|
||||
var CLUSTER_MAX_DURATION = 30000;
|
||||
|
||||
var EBML = [
|
||||
{
|
||||
"id": 0x1a45dfa3, // EBML
|
||||
"data": [
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0x4286 // EBMLVersion
|
||||
},
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0x42f7 // EBMLReadVersion
|
||||
},
|
||||
{
|
||||
"data": 4,
|
||||
"id": 0x42f2 // EBMLMaxIDLength
|
||||
},
|
||||
{
|
||||
"data": 8,
|
||||
"id": 0x42f3 // EBMLMaxSizeLength
|
||||
},
|
||||
{
|
||||
"data": "webm",
|
||||
"id": 0x4282 // DocType
|
||||
},
|
||||
{
|
||||
"data": 2,
|
||||
"id": 0x4287 // DocTypeVersion
|
||||
},
|
||||
{
|
||||
"data": 2,
|
||||
"id": 0x4285 // DocTypeReadVersion
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0x18538067, // Segment
|
||||
"data": [
|
||||
{
|
||||
"id": 0x1549a966, // Info
|
||||
"data": [
|
||||
{
|
||||
"data": 1e6, //do things in millisecs (num of nanosecs for duration scale)
|
||||
"id": 0x2ad7b1 // TimecodeScale
|
||||
},
|
||||
{
|
||||
"data": "whammy",
|
||||
"id": 0x4d80 // MuxingApp
|
||||
},
|
||||
{
|
||||
"data": "whammy",
|
||||
"id": 0x5741 // WritingApp
|
||||
},
|
||||
{
|
||||
"data": doubleToString(info.duration),
|
||||
"id": 0x4489 // Duration
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0x1654ae6b, // Tracks
|
||||
"data": [
|
||||
{
|
||||
"id": 0xae, // TrackEntry
|
||||
"data": [
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0xd7 // TrackNumber
|
||||
},
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0x73c5 // TrackUID
|
||||
},
|
||||
{
|
||||
"data": 0,
|
||||
"id": 0x9c // FlagLacing
|
||||
},
|
||||
{
|
||||
"data": "und",
|
||||
"id": 0x22b59c // Language
|
||||
},
|
||||
{
|
||||
"data": "V_VP8",
|
||||
"id": 0x86 // CodecID
|
||||
},
|
||||
{
|
||||
"data": "VP8",
|
||||
"id": 0x258688 // CodecName
|
||||
},
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0x83 // TrackType
|
||||
},
|
||||
{
|
||||
"id": 0xe0, // Video
|
||||
"data": [
|
||||
{
|
||||
"data": info.width,
|
||||
"id": 0xb0 // PixelWidth
|
||||
},
|
||||
{
|
||||
"data": info.height,
|
||||
"id": 0xba // PixelHeight
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0x1c53bb6b, // Cues
|
||||
"data": [
|
||||
//cue insertion point
|
||||
]
|
||||
}
|
||||
|
||||
//cluster insertion point
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
var segment = EBML[1];
|
||||
var cues = segment.data[2];
|
||||
|
||||
//Generate clusters (max duration)
|
||||
var frameNumber = 0;
|
||||
var clusterTimecode = 0;
|
||||
while(frameNumber < frames.length){
|
||||
|
||||
var cuePoint = {
|
||||
"id": 0xbb, // CuePoint
|
||||
"data": [
|
||||
{
|
||||
"data": Math.round(clusterTimecode),
|
||||
"id": 0xb3 // CueTime
|
||||
},
|
||||
{
|
||||
"id": 0xb7, // CueTrackPositions
|
||||
"data": [
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0xf7 // CueTrack
|
||||
},
|
||||
{
|
||||
"data": 0, // to be filled in when we know it
|
||||
"size": 8,
|
||||
"id": 0xf1 // CueClusterPosition
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
cues.data.push(cuePoint);
|
||||
|
||||
var clusterFrames = [];
|
||||
var clusterDuration = 0;
|
||||
do {
|
||||
clusterFrames.push(frames[frameNumber]);
|
||||
clusterDuration += frames[frameNumber].duration;
|
||||
frameNumber++;
|
||||
}while(frameNumber < frames.length && clusterDuration < CLUSTER_MAX_DURATION);
|
||||
|
||||
var clusterCounter = 0;
|
||||
var cluster = {
|
||||
"id": 0x1f43b675, // Cluster
|
||||
"data": [
|
||||
{
|
||||
"data": Math.round(clusterTimecode),
|
||||
"id": 0xe7 // Timecode
|
||||
}
|
||||
].concat(clusterFrames.map(function(webp){
|
||||
var block = makeSimpleBlock({
|
||||
discardable: 0,
|
||||
frame: webp.data.slice(4),
|
||||
invisible: 0,
|
||||
keyframe: 1,
|
||||
lacing: 0,
|
||||
trackNum: 1,
|
||||
timecode: Math.round(clusterCounter)
|
||||
});
|
||||
clusterCounter += webp.duration;
|
||||
return {
|
||||
data: block,
|
||||
id: 0xa3
|
||||
};
|
||||
}))
|
||||
}
|
||||
|
||||
//Add cluster to segment
|
||||
segment.data.push(cluster);
|
||||
clusterTimecode += clusterDuration;
|
||||
}
|
||||
|
||||
//First pass to compute cluster positions
|
||||
var position = 0;
|
||||
for(var i = 0; i < segment.data.length; i++){
|
||||
if (i >= 3) {
|
||||
cues.data[i-3].data[1].data[1].data = position;
|
||||
}
|
||||
var data = generateEBML([segment.data[i]], outputAsArray);
|
||||
position += data.size || data.byteLength || data.length;
|
||||
if (i != 2) { // not cues
|
||||
//Save results to avoid having to encode everything twice
|
||||
segment.data[i] = data;
|
||||
}
|
||||
}
|
||||
|
||||
return generateEBML(EBML, outputAsArray)
|
||||
}
|
||||
|
||||
// sums the lengths of all the frames and gets the duration, woo
|
||||
|
||||
function checkFrames(frames){
|
||||
var width = frames[0].width,
|
||||
height = frames[0].height,
|
||||
duration = frames[0].duration;
|
||||
for(var i = 1; i < frames.length; i++){
|
||||
if(frames[i].width != width) throw "Frame " + (i + 1) + " has a different width";
|
||||
if(frames[i].height != height) throw "Frame " + (i + 1) + " has a different height";
|
||||
if(frames[i].duration < 0 || frames[i].duration > 0x7fff) throw "Frame " + (i + 1) + " has a weird duration (must be between 0 and 32767)";
|
||||
duration += frames[i].duration;
|
||||
}
|
||||
return {
|
||||
duration: duration,
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function numToBuffer(num){
|
||||
var parts = [];
|
||||
while(num > 0){
|
||||
parts.push(num & 0xff)
|
||||
num = num >> 8
|
||||
}
|
||||
return new Uint8Array(parts.reverse());
|
||||
}
|
||||
|
||||
function numToFixedBuffer(num, size){
|
||||
var parts = new Uint8Array(size);
|
||||
for(var i = size - 1; i >= 0; i--){
|
||||
parts[i] = num & 0xff;
|
||||
num = num >> 8;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
function strToBuffer(str){
|
||||
// return new Blob([str]);
|
||||
|
||||
var arr = new Uint8Array(str.length);
|
||||
for(var i = 0; i < str.length; i++){
|
||||
arr[i] = str.charCodeAt(i)
|
||||
}
|
||||
return arr;
|
||||
// this is slower
|
||||
// return new Uint8Array(str.split('').map(function(e){
|
||||
// return e.charCodeAt(0)
|
||||
// }))
|
||||
}
|
||||
|
||||
|
||||
//sorry this is ugly, and sort of hard to understand exactly why this was done
|
||||
// at all really, but the reason is that there's some code below that i dont really
|
||||
// feel like understanding, and this is easier than using my brain.
|
||||
|
||||
function bitsToBuffer(bits){
|
||||
var data = [];
|
||||
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
|
||||
bits = pad + bits;
|
||||
for(var i = 0; i < bits.length; i+= 8){
|
||||
data.push(parseInt(bits.substr(i,8),2))
|
||||
}
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
|
||||
function generateEBML(json, outputAsArray){
|
||||
var ebml = [];
|
||||
for(var i = 0; i < json.length; i++){
|
||||
if (!('id' in json[i])){
|
||||
//already encoded blob or byteArray
|
||||
ebml.push(json[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
var data = json[i].data;
|
||||
if(typeof data == 'object') data = generateEBML(data, outputAsArray);
|
||||
if(typeof data == 'number') data = ('size' in json[i]) ? numToFixedBuffer(data, json[i].size) : bitsToBuffer(data.toString(2));
|
||||
if(typeof data == 'string') data = strToBuffer(data);
|
||||
|
||||
if(data.length){
|
||||
var z = z;
|
||||
}
|
||||
|
||||
var len = data.size || data.byteLength || data.length;
|
||||
var zeroes = Math.ceil(Math.ceil(Math.log(len)/Math.log(2))/8);
|
||||
var size_str = len.toString(2);
|
||||
var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str;
|
||||
var size = (new Array(zeroes)).join('0') + '1' + padded;
|
||||
|
||||
//i actually dont quite understand what went on up there, so I'm not really
|
||||
//going to fix this, i'm probably just going to write some hacky thing which
|
||||
//converts that string into a buffer-esque thing
|
||||
|
||||
ebml.push(numToBuffer(json[i].id));
|
||||
ebml.push(bitsToBuffer(size));
|
||||
ebml.push(data)
|
||||
|
||||
|
||||
}
|
||||
|
||||
//output as blob or byteArray
|
||||
if(outputAsArray){
|
||||
//convert ebml to an array
|
||||
var buffer = toFlatArray(ebml)
|
||||
return new Uint8Array(buffer);
|
||||
}else{
|
||||
return new Blob(ebml, {type: "video/webm"});
|
||||
}
|
||||
}
|
||||
|
||||
function toFlatArray(arr, outBuffer){
|
||||
if(outBuffer == null){
|
||||
outBuffer = [];
|
||||
}
|
||||
for(var i = 0; i < arr.length; i++){
|
||||
if(typeof arr[i] == 'object'){
|
||||
//an array
|
||||
toFlatArray(arr[i], outBuffer)
|
||||
}else{
|
||||
//a simple element
|
||||
outBuffer.push(arr[i]);
|
||||
}
|
||||
}
|
||||
return outBuffer;
|
||||
}
|
||||
|
||||
//OKAY, so the following two functions are the string-based old stuff, the reason they're
|
||||
//still sort of in here, is that they're actually faster than the new blob stuff because
|
||||
//getAsFile isn't widely implemented, or at least, it doesn't work in chrome, which is the
|
||||
// only browser which supports get as webp
|
||||
|
||||
//Converting between a string of 0010101001's and binary back and forth is probably inefficient
|
||||
//TODO: get rid of this function
|
||||
function toBinStr_old(bits){
|
||||
var data = '';
|
||||
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
|
||||
bits = pad + bits;
|
||||
for(var i = 0; i < bits.length; i+= 8){
|
||||
data += String.fromCharCode(parseInt(bits.substr(i,8),2))
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function generateEBML_old(json){
|
||||
var ebml = '';
|
||||
for(var i = 0; i < json.length; i++){
|
||||
var data = json[i].data;
|
||||
if(typeof data == 'object') data = generateEBML_old(data);
|
||||
if(typeof data == 'number') data = toBinStr_old(data.toString(2));
|
||||
|
||||
var len = data.length;
|
||||
var zeroes = Math.ceil(Math.ceil(Math.log(len)/Math.log(2))/8);
|
||||
var size_str = len.toString(2);
|
||||
var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str;
|
||||
var size = (new Array(zeroes)).join('0') + '1' + padded;
|
||||
|
||||
ebml += toBinStr_old(json[i].id.toString(2)) + toBinStr_old(size) + data;
|
||||
|
||||
}
|
||||
return ebml;
|
||||
}
|
||||
|
||||
//woot, a function that's actually written for this project!
|
||||
//this parses some json markup and makes it into that binary magic
|
||||
//which can then get shoved into the matroska comtainer (peaceably)
|
||||
|
||||
function makeSimpleBlock(data){
|
||||
var flags = 0;
|
||||
if (data.keyframe) flags |= 128;
|
||||
if (data.invisible) flags |= 8;
|
||||
if (data.lacing) flags |= (data.lacing << 1);
|
||||
if (data.discardable) flags |= 1;
|
||||
if (data.trackNum > 127) {
|
||||
throw "TrackNumber > 127 not supported";
|
||||
}
|
||||
var out = [data.trackNum | 0x80, data.timecode >> 8, data.timecode & 0xff, flags].map(function(e){
|
||||
return String.fromCharCode(e)
|
||||
}).join('') + data.frame;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// here's something else taken verbatim from weppy, awesome rite?
|
||||
|
||||
function parseWebP(riff){
|
||||
var VP8 = riff.RIFF[0].WEBP[0];
|
||||
|
||||
var frame_start = VP8.indexOf('\x9d\x01\x2a'); //A VP8 keyframe starts with the 0x9d012a header
|
||||
for(var i = 0, c = []; i < 4; i++) c[i] = VP8.charCodeAt(frame_start + 3 + i);
|
||||
|
||||
var width, horizontal_scale, height, vertical_scale, tmp;
|
||||
|
||||
//the code below is literally copied verbatim from the bitstream spec
|
||||
tmp = (c[1] << 8) | c[0];
|
||||
width = tmp & 0x3FFF;
|
||||
horizontal_scale = tmp >> 14;
|
||||
tmp = (c[3] << 8) | c[2];
|
||||
height = tmp & 0x3FFF;
|
||||
vertical_scale = tmp >> 14;
|
||||
return {
|
||||
width: width,
|
||||
height: height,
|
||||
data: VP8,
|
||||
riff: riff
|
||||
}
|
||||
}
|
||||
|
||||
// i think i'm going off on a riff by pretending this is some known
|
||||
// idiom which i'm making a casual and brilliant pun about, but since
|
||||
// i can't find anything on google which conforms to this idiomatic
|
||||
// usage, I'm assuming this is just a consequence of some psychotic
|
||||
// break which makes me make up puns. well, enough riff-raff (aha a
|
||||
// rescue of sorts), this function was ripped wholesale from weppy
|
||||
|
||||
function parseRIFF(string){
|
||||
var offset = 0;
|
||||
var chunks = {};
|
||||
|
||||
while (offset < string.length) {
|
||||
var id = string.substr(offset, 4);
|
||||
chunks[id] = chunks[id] || [];
|
||||
if (id == 'RIFF' || id == 'LIST') {
|
||||
var len = parseInt(string.substr(offset + 4, 4).split('').map(function(i){
|
||||
var unpadded = i.charCodeAt(0).toString(2);
|
||||
return (new Array(8 - unpadded.length + 1)).join('0') + unpadded
|
||||
}).join(''),2);
|
||||
var data = string.substr(offset + 4 + 4, len);
|
||||
offset += 4 + 4 + len;
|
||||
chunks[id].push(parseRIFF(data));
|
||||
} else if (id == 'WEBP') {
|
||||
// Use (offset + 8) to skip past "VP8 "/"VP8L"/"VP8X" field after "WEBP"
|
||||
chunks[id].push(string.substr(offset + 8));
|
||||
offset = string.length;
|
||||
} else {
|
||||
// Unknown chunk type; push entire payload
|
||||
chunks[id].push(string.substr(offset + 4));
|
||||
offset = string.length;
|
||||
}
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
// here's a little utility function that acts as a utility for other functions
|
||||
// basically, the only purpose is for encoding "Duration", which is encoded as
|
||||
// a double (considerably more difficult to encode than an integer)
|
||||
function doubleToString(num){
|
||||
return [].slice.call(
|
||||
new Uint8Array(
|
||||
(
|
||||
new Float64Array([num]) //create a float64 array
|
||||
).buffer) //extract the array buffer
|
||||
, 0) // convert the Uint8Array into a regular array
|
||||
.map(function(e){ //since it's a regular array, we can now use map
|
||||
return String.fromCharCode(e) // encode all the bytes individually
|
||||
})
|
||||
.reverse() //correct the byte endianness (assume it's little endian for now)
|
||||
.join('') // join the bytes in holy matrimony as a string
|
||||
}
|
||||
|
||||
function WhammyVideo(speed, quality){ // a more abstract-ish API
|
||||
this.frames = [];
|
||||
this.duration = 1000 / speed;
|
||||
this.quality = quality || 0.8;
|
||||
}
|
||||
|
||||
WhammyVideo.prototype.add = function(frame, duration){
|
||||
if(typeof duration != 'undefined' && this.duration) throw "you can't pass a duration if the fps is set";
|
||||
if(typeof duration == 'undefined' && !this.duration) throw "if you don't have the fps set, you ned to have durations here.";
|
||||
if(frame.canvas){ //CanvasRenderingContext2D
|
||||
frame = frame.canvas;
|
||||
}
|
||||
if(frame.toDataURL){
|
||||
frame = frame.toDataURL('image/webp', this.quality)
|
||||
}else if(typeof frame != "string"){
|
||||
throw "frame must be a a HTMLCanvasElement, a CanvasRenderingContext2D or a DataURI formatted string"
|
||||
}
|
||||
if (!(/^data:image\/webp;base64,/ig).test(frame)) {
|
||||
throw "Input must be formatted properly as a base64 encoded DataURI of type image/webp";
|
||||
}
|
||||
this.frames.push({
|
||||
image: frame,
|
||||
duration: duration || this.duration
|
||||
})
|
||||
}
|
||||
|
||||
WhammyVideo.prototype.compile = function(outputAsArray){
|
||||
return new toWebM(this.frames.map(function(frame){
|
||||
var webp = parseWebP(parseRIFF(atob(frame.image.slice(23))));
|
||||
webp.duration = frame.duration;
|
||||
return webp;
|
||||
}), outputAsArray)
|
||||
}
|
||||
|
||||
return {
|
||||
Video: WhammyVideo,
|
||||
fromImageArray: function(images, fps, outputAsArray){
|
||||
return toWebM(images.map(function(image){
|
||||
var webp = parseWebP(parseRIFF(atob(image.slice(23))))
|
||||
webp.duration = 1000 / fps;
|
||||
return webp;
|
||||
}), outputAsArray)
|
||||
},
|
||||
toWebM: toWebM
|
||||
// expose methods of madness
|
||||
}
|
||||
})()
|
||||
Reference in New Issue
Block a user