6735 lines
179 KiB
JavaScript
6735 lines
179 KiB
JavaScript
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.O=e()}}(function(){var define,module,exports;return (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);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.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(_dereq_,module,exports){
|
|
|
|
var e = _dereq_('./lib/odyssey/story');
|
|
e.Actions = _dereq_('./lib/odyssey/actions');
|
|
e.Triggers = _dereq_('./lib/odyssey/triggers');
|
|
e.Core = _dereq_('./lib/odyssey/core');
|
|
e.Template = _dereq_('./lib/odyssey/template');
|
|
e.UI = _dereq_('./lib/odyssey/ui');
|
|
_dereq_('./lib/odyssey/util');
|
|
|
|
for (var k in e.Actions) {
|
|
e[k] = e.Actions[k];
|
|
}
|
|
for (var k in e.Triggers) {
|
|
e[k] = e.Triggers[k];
|
|
}
|
|
module.exports = e;
|
|
|
|
},{"./lib/odyssey/actions":5,"./lib/odyssey/core":13,"./lib/odyssey/story":14,"./lib/odyssey/template":15,"./lib/odyssey/triggers":17,"./lib/odyssey/ui":23,"./lib/odyssey/util":26}],2:[function(_dereq_,module,exports){
|
|
|
|
var Action = _dereq_('../story').Action;
|
|
|
|
|
|
function CSS(el) {
|
|
|
|
function _css() {};
|
|
|
|
_css.toggleClass = function(cl) {
|
|
return Action(function() {
|
|
el.toggleClass(cl);
|
|
});
|
|
};
|
|
|
|
_css.addClass = function(cl) {
|
|
return Action(function() {
|
|
el.addClass(cl);
|
|
});
|
|
};
|
|
|
|
_css.removeClass = function(cl) {
|
|
return Action(function() {
|
|
el.removeClass(cl);
|
|
});
|
|
};
|
|
|
|
return _css;
|
|
|
|
}
|
|
|
|
module.exports = CSS;
|
|
|
|
},{"../story":14}],3:[function(_dereq_,module,exports){
|
|
|
|
var Action = _dereq_('../story').Action;
|
|
//
|
|
// debug action
|
|
// prints information about current state
|
|
//
|
|
function Debug() {
|
|
function _debug() {};
|
|
|
|
_debug.log = function(_) {
|
|
|
|
return Action({
|
|
|
|
enter: function() {
|
|
console.log("STATE =>", _, arguments);
|
|
},
|
|
|
|
update: function() {
|
|
console.log("STATE (.)", _, arguments);
|
|
},
|
|
|
|
exit: function() {
|
|
console.log("STATE <=", _, arguments);
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
return _debug;
|
|
}
|
|
|
|
module.exports = Debug;
|
|
|
|
},{"../story":14}],4:[function(_dereq_,module,exports){
|
|
|
|
var Action = _dereq_('../story').Action;
|
|
|
|
var Audio = function(el){
|
|
return {
|
|
play: function() {
|
|
return Action(function() {
|
|
el.play()
|
|
});
|
|
},
|
|
pause: function() {
|
|
return Action(function() {
|
|
el.pause()
|
|
});
|
|
},
|
|
setCurrentTime: function(t) {
|
|
return Action(function() {
|
|
el.currentTime = t;
|
|
});
|
|
}
|
|
}
|
|
|
|
};
|
|
module.exports = Audio;
|
|
|
|
|
|
},{"../story":14}],5:[function(_dereq_,module,exports){
|
|
|
|
module.exports = {
|
|
Sleep: _dereq_('./sleep'),
|
|
Debug: _dereq_('./debug'),
|
|
Location: _dereq_('./location'),
|
|
Audio: _dereq_('./html5audio'),
|
|
Leaflet: {
|
|
Marker: _dereq_('./leaflet/marker'),
|
|
Map: _dereq_('./leaflet/map'),
|
|
Popup: _dereq_('./leaflet/popup')
|
|
},
|
|
CSS: _dereq_('./css'),
|
|
Slides: _dereq_('./slides'),
|
|
MiniProgressBar: _dereq_('./mini_progressbar')
|
|
};
|
|
|
|
},{"./css":2,"./debug":3,"./html5audio":4,"./leaflet/map":6,"./leaflet/marker":7,"./leaflet/popup":8,"./location":9,"./mini_progressbar":10,"./sleep":11,"./slides":12}],6:[function(_dereq_,module,exports){
|
|
|
|
var Action = _dereq_('../../story').Action;
|
|
|
|
function MapActions(map) {
|
|
|
|
function _map() {}
|
|
|
|
// helper method to translate leaflet methods to actions
|
|
function leaflet_method(name) {
|
|
_map[name] = function() {
|
|
var args = arguments;
|
|
return Action(function() {
|
|
map[name].apply(map, args);
|
|
});
|
|
};
|
|
}
|
|
|
|
function leaflet_move_method(name, signal) {
|
|
_map[name] = function() {
|
|
var args = arguments;
|
|
return Action({
|
|
enter: function() {
|
|
this.moveEnd = function() {
|
|
map.off(signal, this.moveEnd, this);
|
|
this.finish();
|
|
};
|
|
map.on(signal, this.moveEnd, this);
|
|
map[name].apply(map, args);
|
|
return true;
|
|
},
|
|
|
|
clear: function() {
|
|
map.off(signal, this.moveEnd, this);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
_map.moveLinearTo = function(from, to, options) {
|
|
var opt = options || { k: 2 };
|
|
var animationTimer;
|
|
var posTarget;
|
|
var delta = 20;
|
|
|
|
// leaflet map animation is not being used because
|
|
// when the action is updated the map just stop the animatin
|
|
// and start again
|
|
// the animtion should be smooth
|
|
return Action({
|
|
|
|
enter: function () {
|
|
posTarget = map.project(map.getCenter());
|
|
animationTimer = setInterval(function() {
|
|
var c = map.project(map.getCenter());
|
|
var px = c.x + (posTarget.x - c.x)*delta*0.001* opt.k;
|
|
var py = c.y + (posTarget.y - c.y)*delta*0.001* opt.k;
|
|
map.panTo(map.unproject(L.point(px, py)), { animate: false });
|
|
}, delta);
|
|
},
|
|
|
|
update: function (t) {
|
|
var p0 = map.project(from);
|
|
var p1 = map.project(to);
|
|
posTarget = p0.add(p1.subtract(p0).multiplyBy(t));
|
|
},
|
|
|
|
exit: function() {
|
|
clearInterval(animationTimer);
|
|
},
|
|
|
|
clean: function() {
|
|
this.exit();
|
|
}
|
|
|
|
});
|
|
};
|
|
|
|
|
|
// leaflet methods
|
|
leaflet_move_method('panTo', 'moveend');
|
|
leaflet_move_method('setView', 'moveend');
|
|
leaflet_move_method('setZoom', 'zoomend');
|
|
|
|
return _map;
|
|
}
|
|
|
|
|
|
if (typeof window.L !== 'undefined') {
|
|
L.Map.addInitHook(function () {
|
|
this.actions = MapActions(this);
|
|
});
|
|
}
|
|
module.exports = MapActions;
|
|
|
|
|
|
},{"../../story":14}],7:[function(_dereq_,module,exports){
|
|
|
|
var Action = _dereq_('../../story').Action;
|
|
|
|
function MarkerActions(marker) {
|
|
|
|
function _marker() {}
|
|
|
|
_marker.addTo = function(map) {
|
|
return Action(function() {
|
|
marker.addTo(map);
|
|
});
|
|
};
|
|
|
|
_marker.addRemove = function(map) {
|
|
return Action({
|
|
enter: function() {
|
|
marker.addTo(map);
|
|
},
|
|
exit: function() {
|
|
map.removeLayer(marker);
|
|
},
|
|
clear: function() {
|
|
map.removeLayer(marker);
|
|
}
|
|
});
|
|
};
|
|
|
|
_marker.icon = function(iconEnabled, iconDisabled) {
|
|
iconEnabled = L.icon({
|
|
iconUrl: iconEnabled
|
|
});
|
|
iconDisabled = L.icon({
|
|
iconUrl: iconDisabled
|
|
});
|
|
return Action({
|
|
enter: function() {
|
|
marker.setIcon(iconEnabled);
|
|
},
|
|
exit: function() {
|
|
marker.setIcon(iconDisabled);
|
|
},
|
|
clear: function() { }
|
|
});
|
|
}
|
|
|
|
return _marker;
|
|
}
|
|
|
|
function PathActions(path) {
|
|
function _path() {};
|
|
|
|
_path.toggleStyle = function(styleDisabled, styleEnabled) {
|
|
return Action({
|
|
enter: function() {
|
|
path.setStyle(styleEnabled);
|
|
},
|
|
exit: function() {
|
|
path.setStyle(styleDisabled);
|
|
},
|
|
clear: function() { }
|
|
});
|
|
}
|
|
|
|
return _path;
|
|
}
|
|
|
|
|
|
if (typeof window.L !== 'undefined') {
|
|
L.Marker.addInitHook(function () {
|
|
this.actions = MarkerActions(this);
|
|
});
|
|
L.Path.addInitHook(function () {
|
|
this.actions = PathActions(this);
|
|
})
|
|
}
|
|
module.exports = MarkerActions;
|
|
|
|
//marker.actions.addTo(map);
|
|
//addState(, map.actions.moveTo(..).addMarker(m)
|
|
|
|
},{"../../story":14}],8:[function(_dereq_,module,exports){
|
|
|
|
/**
|
|
directional popup allows to create popups in the left and the right of a point,
|
|
not only on the top.
|
|
|
|
Same api than L.popup but you can spcify ``potision`` to 'left' or 'right'
|
|
|
|
*/
|
|
|
|
var Action = _dereq_('../../story').Action;
|
|
|
|
|
|
if (typeof window.L !== 'undefined') {
|
|
|
|
var DirectionalPopup = L.Popup.extend({
|
|
_updatePosition: function() {
|
|
L.Popup.prototype._updatePosition.call(this);
|
|
var offset = L.point(this.options.offset),
|
|
animated = this._animated;
|
|
|
|
switch(this.options.position) {
|
|
case 'left':
|
|
this._container.style.bottom = 'auto';
|
|
this._container.style.left = 'auto';
|
|
this._container.style.right = offset.x + (animated ? 0 : pos.x) + "px";
|
|
break;
|
|
case 'right':
|
|
this._container.style.bottom = 'auto';
|
|
this._container.style.left = offset.x + "px";
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
L.DirectionalPopup = DirectionalPopup;
|
|
L.directionalPopup = function (options, source) {
|
|
return new L.DirectionalPopup(options, source);
|
|
};
|
|
|
|
function PopupActions(popup) {
|
|
|
|
function _popup() {}
|
|
|
|
// helper method to translate leaflet methods to actions
|
|
function leaflet_method(name) {
|
|
_popup[name] = function() {
|
|
var args = arguments;
|
|
return Action(function() {
|
|
popup[name].apply(popup, args);
|
|
});
|
|
};
|
|
}
|
|
|
|
// leaflet methods
|
|
leaflet_method('openOn');
|
|
_popup.openClose = function(map) {
|
|
if (!map) {
|
|
throw new Error("openClose gets map as first param");
|
|
}
|
|
return Action({
|
|
enter: function() {
|
|
popup.openOn(map);
|
|
},
|
|
|
|
exit: function() {
|
|
map.closePopup(popup);
|
|
}
|
|
|
|
});
|
|
};
|
|
|
|
return _popup;
|
|
}
|
|
|
|
|
|
L.Popup.addInitHook(function () {
|
|
this.actions = PopupActions(this);
|
|
});
|
|
}
|
|
|
|
|
|
},{"../../story":14}],9:[function(_dereq_,module,exports){
|
|
|
|
var Action = _dereq_('../story').Action;
|
|
|
|
var loc = window.location;
|
|
var Location = {
|
|
|
|
// changes the browser url hash
|
|
changeHash: function(hash) {
|
|
if (hash === undefined) throw new Error("hash should be a string");
|
|
return Action(function() {
|
|
loc.hash = hash;
|
|
});
|
|
}
|
|
|
|
};
|
|
|
|
|
|
module.exports = Location;
|
|
|
|
},{"../story":14}],10:[function(_dereq_,module,exports){
|
|
|
|
var Action = _dereq_('../story').Action;
|
|
|
|
/**
|
|
* mini progress var adds a small line to the top of the browser
|
|
* and moves according to story progress
|
|
* usage:
|
|
* var pg = MiniProgressBar()
|
|
* story.addAction(trigger, pg.percent(10)) //goes to 10%
|
|
*/
|
|
|
|
var MiniProgressBar = function(el) {
|
|
|
|
var defaultStyle = {
|
|
position: 'fixed',
|
|
left: 0,
|
|
top: 0,
|
|
height: '3px',
|
|
display: 'inline-block',
|
|
background: '#ff7373',
|
|
'z-index': 2
|
|
};
|
|
|
|
var pg = {};
|
|
|
|
// create an element and apply default style
|
|
var div = document.createElement('div');
|
|
div.setAttribute('class', 'oddysey-miniprogressbar');
|
|
for (var s in defaultStyle) {
|
|
div.style[s] = defaultStyle[s];
|
|
}
|
|
|
|
// append to element or to tge body
|
|
(el || document.body).appendChild(div);
|
|
|
|
/**
|
|
* returns an action that moves the percentaje bar to the specified one
|
|
*/
|
|
pg.percent = function(p) {
|
|
return Action(function() {
|
|
div.style.width = p + "%";
|
|
});
|
|
};
|
|
|
|
return pg;
|
|
}
|
|
|
|
module.exports = MiniProgressBar;
|
|
|
|
},{"../story":14}],11:[function(_dereq_,module,exports){
|
|
|
|
var Action = _dereq_('../story').Action;
|
|
|
|
function Sleep(ms) {
|
|
|
|
return Action({
|
|
|
|
enter: function() {
|
|
setTimeout(this.finish, ms);
|
|
return true;
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
module.exports = Sleep;
|
|
|
|
|
|
},{"../story":14}],12:[function(_dereq_,module,exports){
|
|
|
|
var Action = _dereq_('../story').Action;
|
|
var Core = _dereq_('../core');
|
|
var classList = _dereq_('../util/classList');
|
|
|
|
function Slides(el) {
|
|
|
|
|
|
function slides() {};
|
|
|
|
function _activate(idx) {
|
|
var slideElements = Core.getElement(el).children;
|
|
for(var i = 0; i < slideElements.length; ++i) {
|
|
if (i === idx) {
|
|
slideElements[i].classList.add("selected", "selected_slide");
|
|
} else {
|
|
slideElements[i].classList.remove("selected", "selected_slide");
|
|
}
|
|
}
|
|
}
|
|
|
|
slides.activate = function(i) {
|
|
return Action(function() {
|
|
_activate(i);
|
|
});
|
|
};
|
|
|
|
_activate(-1);
|
|
|
|
return slides;
|
|
|
|
}
|
|
|
|
module.exports = Slides;
|
|
},{"../core":13,"../story":14,"../util/classList":24}],13:[function(_dereq_,module,exports){
|
|
|
|
function getElement(el) {
|
|
if(typeof jQuery !== 'undefined') {
|
|
if (el instanceof jQuery) {
|
|
return el[0];
|
|
} else if(typeof el === 'string') {
|
|
if (el[0] === '#' || el[0] === '.') {
|
|
return getElement($(el));
|
|
}
|
|
}
|
|
}
|
|
if (el instanceof NodeList || el instanceof HTMLCollection) {
|
|
return el[0];
|
|
} else if (el instanceof Element) {
|
|
return el;
|
|
}
|
|
return document.getElementById(el);
|
|
}
|
|
|
|
module.exports = {
|
|
getElement: getElement
|
|
};
|
|
|
|
},{}],14:[function(_dereq_,module,exports){
|
|
|
|
_dereq_('../../vendor/d3.custom');
|
|
|
|
function Story() {
|
|
|
|
var triggers = [];
|
|
var events = [];
|
|
var currentState = null;
|
|
var prevState = null;
|
|
|
|
function story(t) {
|
|
}
|
|
|
|
// event non attached to states
|
|
story.addEvent = function(trigger, action) {
|
|
trigger._story(story, function() {
|
|
action.enter();
|
|
});
|
|
events.push({
|
|
a: trigger,
|
|
b: action
|
|
});
|
|
return story;
|
|
};
|
|
|
|
story.clear = function() {
|
|
var all = triggers.concat(events);
|
|
for(var i = 0; i < all.length; ++i) {
|
|
var a = all[i];
|
|
a.a.story = null;
|
|
a.a.trigger = null;
|
|
a.a.clear && a.a.clear();
|
|
a.b.clear && a.b.clear();
|
|
}
|
|
|
|
triggers = [];
|
|
events = [];
|
|
currentState = null;
|
|
prevState = null;
|
|
|
|
};
|
|
|
|
// go to state index
|
|
story.go = function(index, opts) {
|
|
opts = opts || {};
|
|
if(index < 0 && index > triggers.length) {
|
|
throw new Error("index should be less than states length");
|
|
}
|
|
if (story.state() !== index) {
|
|
|
|
if (opts.reverse) {
|
|
var a = triggers[index].a;
|
|
if (a.reverse) {
|
|
a.reverse();
|
|
}
|
|
}
|
|
// current state
|
|
story.state(index);
|
|
|
|
// raise exit
|
|
if (prevState !== null) {
|
|
var prev = triggers[prevState].b;
|
|
if (prev.exit) {
|
|
prev.exit();
|
|
}
|
|
}
|
|
|
|
var b = triggers[index].b;
|
|
|
|
// enter in current state
|
|
b.enter();
|
|
}
|
|
|
|
};
|
|
|
|
story.addState = function(a, b, opts) {
|
|
var i = triggers.length;
|
|
|
|
if(!a || !b) {
|
|
throw new Error("action and trigger must be defined");
|
|
}
|
|
|
|
triggers.push({
|
|
a: a,
|
|
b: b,
|
|
opts: opts
|
|
});
|
|
|
|
a._story(story, function() {
|
|
story.go(i);
|
|
});
|
|
|
|
return story;
|
|
};
|
|
|
|
story.addLinearState = function(a, b, opts) {
|
|
var j;
|
|
var i = triggers.length;
|
|
|
|
triggers.push({
|
|
a: a,
|
|
b: b,
|
|
opts: opts
|
|
});
|
|
|
|
a._story(story, function(t) {
|
|
if (story.state() !== i) {
|
|
story.go(i);
|
|
} else {
|
|
if (b.update) {
|
|
b.update(t);
|
|
}
|
|
}
|
|
});
|
|
|
|
return story;
|
|
};
|
|
|
|
story.state = function(_) {
|
|
if(_ === undefined) return currentState;
|
|
prevState = currentState;
|
|
currentState = _;
|
|
return;
|
|
};
|
|
|
|
return story;
|
|
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// basic action
|
|
// t can be a function or an object
|
|
// if is a function it's called on ``enter`` event
|
|
// if t is an object with enter/exit/update methods
|
|
// they're called on state changes
|
|
function Action(t) {
|
|
|
|
var evt = d3.dispatch('finish');
|
|
var action = t;
|
|
if (t.enter === undefined && !(typeof(t) === 'function' && t.prototype.enter !== undefined)) {
|
|
action = {
|
|
enter: t
|
|
}
|
|
}
|
|
|
|
return d3.rebind(action, evt, 'on', 'finish');
|
|
|
|
}
|
|
|
|
function Trigger(t) {
|
|
t = t || {}
|
|
t._story = function(story, trigger) {
|
|
this.trigger = trigger;
|
|
this.story = story;
|
|
};
|
|
|
|
t.then = function(t, context) {
|
|
this.trigger = function() {
|
|
if (t.trigger) {
|
|
t.trigger();
|
|
if (t.reverse) t.reverse();
|
|
} else if (t.call) {
|
|
t.call(context || self);
|
|
} else {
|
|
throw new Error("then first param should be either function or trigger");
|
|
}
|
|
};
|
|
};
|
|
return t;
|
|
}
|
|
|
|
///
|
|
// executes actions in parallel
|
|
// usage:
|
|
// Parallel(action1, action2, action3)
|
|
//
|
|
// raises finish when all the tasks has been completed
|
|
//
|
|
function Parallel () {
|
|
var actions = Array.prototype.slice.call(arguments);
|
|
var tasksLeft;
|
|
|
|
function _Parallel() {}
|
|
|
|
function start() {
|
|
tasksLeft = actions.length;
|
|
}
|
|
|
|
function done() {
|
|
if (--tasksLeft === 0) {
|
|
_Parallel.finish();
|
|
}
|
|
}
|
|
|
|
function wait(action) {
|
|
action.on('finish.parallel', function() {
|
|
action.on('finish.parallel', null);
|
|
done();
|
|
});
|
|
}
|
|
|
|
|
|
_Parallel.enter = function() {
|
|
start();
|
|
for(var i = 0, len = actions.length; i < len; ++i) {
|
|
var a = actions[i];
|
|
if (a.enter) {
|
|
if (a.enter()) {
|
|
wait(a);
|
|
} else {
|
|
done();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
_Parallel.exit = function() {
|
|
start();
|
|
for(var i = actions.length - 1; i >= 0; --i) {
|
|
var a = actions[i];
|
|
if (a.exit) {
|
|
if (a.exit()) {
|
|
wait(a);
|
|
} else {
|
|
done();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
_Parallel.clear = function() {
|
|
for(var i = 0, len = actions.length; i < len; ++i) {
|
|
var a = actions[i];
|
|
a.clear && a.clear();
|
|
}
|
|
}
|
|
|
|
_Parallel = Action(_Parallel);
|
|
return _Parallel;
|
|
}
|
|
|
|
|
|
|
|
///
|
|
// executes actions serially, waits until the previous task
|
|
// is completed to start with the second and so on
|
|
// usage:
|
|
// Step(action1, action2, action3)
|
|
//
|
|
// raises finish when all the tasks has been completed
|
|
//
|
|
function Step() {
|
|
|
|
var actions = Array.prototype.slice.call(arguments);
|
|
var queue;
|
|
|
|
function _Step() {}
|
|
|
|
function next(method) {
|
|
if (queue.length === 0) {
|
|
_Step.finish();
|
|
return;
|
|
}
|
|
var a = queue.pop();
|
|
if (a.on) {
|
|
a.on('finish.chain', function() {
|
|
a.on('finish.chain', null);
|
|
next(method);
|
|
});
|
|
}
|
|
if (!a[method] || !a[method]()) {
|
|
next(method);
|
|
if(a.on) a.on('finish.chain', null);
|
|
}
|
|
}
|
|
|
|
_Step.enter = function() {
|
|
// call enter on each action
|
|
queue = actions.slice().reverse();
|
|
next('enter');
|
|
return true;
|
|
}
|
|
|
|
_Step.exit = function() {
|
|
// call exit on each action
|
|
queue = actions.slice();
|
|
next('exit');
|
|
return true;
|
|
}
|
|
|
|
_Step.clear = function() {
|
|
for(var i = 0, len = actions.length; i < len; ++i) {
|
|
var a = actions[i];
|
|
a.clear && a.clear();
|
|
}
|
|
}
|
|
|
|
_Step = Action(_Step);
|
|
return _Step;
|
|
}
|
|
|
|
// check change between two states and triggers
|
|
function Edge(a, b) {
|
|
var s = 0;
|
|
function t() {}
|
|
a._story(null, function() {
|
|
if(s !== 0) {
|
|
t.trigger();
|
|
}
|
|
s = 0;
|
|
});
|
|
b._story(null, function() {
|
|
if(s !== 1) {
|
|
t.trigger();
|
|
}
|
|
s = 1;
|
|
});
|
|
return Trigger(t);
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
Story: Story,
|
|
Action: Action,
|
|
Trigger: Trigger,
|
|
Step: Step,
|
|
Parallel: Parallel,
|
|
Edge: Edge
|
|
};
|
|
|
|
|
|
},{"../../vendor/d3.custom":32}],15:[function(_dereq_,module,exports){
|
|
|
|
_dereq_('../../vendor/markdown');
|
|
var mapActions = {
|
|
'move map to current position': function() {
|
|
var center = this.map.getCenter()
|
|
return '- center: [' + center.lat.toFixed(4) + ', ' + center.lng.toFixed(4) + ']\n- zoom: ' + this.map.getZoom()
|
|
},
|
|
'show marker at current position': function() {
|
|
var center = this.map.getCenter()
|
|
return 'L.marker([' + center.lat.toFixed(4) + ', ' + center.lng.toFixed(4) + ']).actions.addRemove(S.map)';
|
|
},
|
|
'sleep': function() {
|
|
return "O.Actions.Sleep(1000)";
|
|
}
|
|
};
|
|
|
|
var Template = function(template) {
|
|
var initialized = false;
|
|
|
|
function readMessage(event) {
|
|
var msg = JSON.parse(event.data);
|
|
template.editor = true;
|
|
|
|
if (!initialized) {
|
|
configureEditor();
|
|
initialized = true;
|
|
}
|
|
|
|
function sendMsg(_) {
|
|
event.source.postMessage(JSON.stringify({
|
|
id: msg.id,
|
|
data: _
|
|
}), event.currentTarget ? event.currentTarget.location : event.source.location);
|
|
}
|
|
|
|
if (msg.type === 'md') {
|
|
var actions = actionsFromMarkdown(msg.code);
|
|
try {
|
|
template.update(actions);
|
|
sendMsg(null);
|
|
} catch(e) {
|
|
sendMsg(e.message);
|
|
}
|
|
} else if (msg.type === 'actions') {
|
|
var actions = [];
|
|
if (template.map && template.map instanceof L.Map) {
|
|
for (var k in mapActions) {
|
|
actions.push(k);
|
|
}
|
|
}
|
|
if (template.actions) {
|
|
for (var k in template.actions) {
|
|
actions.push(k);
|
|
}
|
|
}
|
|
sendMsg(actions);
|
|
} else if (msg.type === 'get_action') {
|
|
if (msg.code in mapActions) {
|
|
sendMsg(mapActions[msg.code].call(template));
|
|
} else {
|
|
sendMsg(template.actions[msg.code].call(template));
|
|
}
|
|
} else if (msg.type === 'code') {
|
|
sendMsg(eval(msg.code));
|
|
} else if (msg.type === 'change_slide') {
|
|
template.changeSlide && template.changeSlide(msg.slide);
|
|
}
|
|
}
|
|
|
|
if (!window.addEventListener) {
|
|
window.attachEvent("message", function load(event) {
|
|
readMessage(event);
|
|
});
|
|
} else {
|
|
window.addEventListener("message", function load(event) {
|
|
readMessage(event);
|
|
});
|
|
}
|
|
|
|
template.init(function() { });
|
|
|
|
function configureEditor() {
|
|
// add helpers
|
|
if (template.map && template.map instanceof L.Map) {
|
|
new L.CrossHair('//cartodb.github.io/odyssey.js/img/crosshair.png').addTo(template.map);
|
|
}
|
|
}
|
|
|
|
window.onload = function() {
|
|
var origin = location.pathname.split('/')[1];
|
|
|
|
Template.Storage.load(function(md) {
|
|
template.update(actionsFromMarkdown(md));
|
|
});
|
|
}
|
|
};
|
|
|
|
Template.Storage = {
|
|
|
|
save: function(md, template) {
|
|
location.hash = "md/" + template + "/" + btoa(md);
|
|
},
|
|
|
|
load: function(done) {
|
|
if (document.getElementById('md_template')) {
|
|
done(document.getElementById('md_template').text);
|
|
return;
|
|
}
|
|
|
|
var h = location.hash;
|
|
if (done && h) {
|
|
// #md/template/base_64_markdown
|
|
var tk = h.split('/');
|
|
if (tk[0] === '#md') {
|
|
done(atob(tk.slice(2).join('/')), tk[1]);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
function Slide(tree, actions, prop) {
|
|
|
|
var html;
|
|
var md_tree = tree;
|
|
var properties = prop;
|
|
|
|
var action;
|
|
|
|
function compile(context) {
|
|
propertiesToActions();
|
|
//sort actions by order
|
|
actions.sort(function(a, b) {
|
|
return a.order - b.order;
|
|
});
|
|
|
|
if (!('map' in Array.prototype)) {
|
|
Array.prototype.map= function(mapper, that /*opt*/) {
|
|
var other= new Array(this.length);
|
|
for (var i= 0, n= this.length; i<n; i++)
|
|
if (i in this)
|
|
other[i]= mapper.call(that, this[i], i, this);
|
|
return other;
|
|
};
|
|
}
|
|
|
|
action = O.Step.apply(window,
|
|
actions.map(function(a) {
|
|
var o = a.cmd;
|
|
try {
|
|
var f = Function("S", "return " + o + ";");
|
|
} catch (e) {
|
|
throw new Error("error while compiling " + o + ": " + e.message);
|
|
}
|
|
return f(context);
|
|
})
|
|
);
|
|
}
|
|
|
|
/*
|
|
* there are special propertues like pos, marker and so
|
|
* on that should become actions
|
|
*/
|
|
function propertiesToActions() {
|
|
for (var k in properties) {
|
|
var prop = properties[k];
|
|
switch (k) {
|
|
case 'center':
|
|
var pos = prop.value;
|
|
if (properties.zoom === undefined) {
|
|
actions.push({
|
|
order: prop.order,
|
|
cmd: 'S.map.actions.panTo([' + pos[0] + ',' + pos[1] + '])'
|
|
});
|
|
} else {
|
|
actions.push({
|
|
order: prop.order,
|
|
cmd: 'S.map.actions.setView([' + pos[0] + ',' + pos[1] + '], ' + properties.zoom.value + ')'
|
|
});
|
|
}
|
|
case 'zoom':
|
|
if (!properties.center) {
|
|
actions.push({
|
|
order: prop.order,
|
|
cmd: 'S.map.actions.setZoom(' + properties.zoom.value+ ')'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _slide(context) {
|
|
if (!action) compile(context);
|
|
return action;
|
|
}
|
|
|
|
_slide.html = function() {
|
|
if (!html) {
|
|
html = markdown.toHTML(['markdown'].concat(md_tree));
|
|
}
|
|
return html;
|
|
};
|
|
|
|
_slide.props = function() {
|
|
return properties || {}
|
|
};
|
|
|
|
_slide.get = function(_) {
|
|
return (properties || {})[_];
|
|
}
|
|
|
|
return _slide;
|
|
}
|
|
|
|
function trim(x) {
|
|
return x.replace(/^\s+|\s+$/gm,'');
|
|
}
|
|
|
|
var prop_re = /^-([^:]+):(.*)/;
|
|
function parseProperties(code) {
|
|
var props = {};
|
|
var lines = code.split('\n');
|
|
for (var i = 0; i < lines.length; ++i) {
|
|
var e = prop_re.exec(lines[i]);
|
|
if (e) {
|
|
try {
|
|
props[trim(e[1])] = JSON.parse(trim(e[2]));
|
|
} catch(e) {
|
|
}
|
|
}
|
|
}
|
|
return props;
|
|
}
|
|
|
|
/**
|
|
* given a markdown parsed tree return a list of slides with
|
|
* html and actions
|
|
*/
|
|
|
|
function md2json(tree) {
|
|
var slide = null;
|
|
var slides = [];
|
|
var globalProperties = {}
|
|
var elements = tree.slice(1);
|
|
for (var i = 0; i < elements.length; ++i) {
|
|
var el = elements[i];
|
|
var add = true;
|
|
if (el[0] === 'header' && el[1].level === 1) {
|
|
if (slide) slides.push(Slide(slide.md_tree, slide.actions, slide.properties));
|
|
slide = {
|
|
md_tree: [],
|
|
html: '',
|
|
actions: [],
|
|
properties: {}
|
|
};
|
|
} else if (el[0] === 'para') {
|
|
// search inline code inside it
|
|
var actions = [];
|
|
for(var j = 1; j < el.length; ++j) {
|
|
var subel = el[j];
|
|
if (subel[0] === "inlinecode") {
|
|
var lines = subel[1].split('\n');
|
|
|
|
for (var ln = 0; ln < lines.length; ++ln) {
|
|
var line = lines[ln];
|
|
if (trim(line) !== '') {
|
|
// if matches property, parse it
|
|
if (prop_re.exec(line)) {
|
|
var p = parseProperties(subel[1]);
|
|
var prop = slide ? slide.properties: globalProperties
|
|
for(var k in p) {
|
|
prop[k] = {
|
|
order: ln,
|
|
value: p[k]
|
|
}
|
|
}
|
|
} else {
|
|
slide && slide.actions.push({
|
|
cmd: line,
|
|
order: ln
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove from list
|
|
el.splice(j, 1);
|
|
--j;
|
|
}
|
|
}
|
|
}
|
|
add && slide && slide.md_tree.push(el);
|
|
}
|
|
if (slide) slides.push(Slide(slide.md_tree, slide.actions, slide.properties));
|
|
|
|
// remove the order for globalProperties
|
|
slides.global = {};
|
|
for(var k in globalProperties) {
|
|
slides.global[k] = globalProperties[k].value;
|
|
}
|
|
|
|
return slides;
|
|
}
|
|
|
|
|
|
// return a list of Slide objects
|
|
function actionsFromMarkdown(md) {
|
|
var md_tree = markdown.parse(md);
|
|
return md2json(md_tree);
|
|
}
|
|
|
|
|
|
Template.parseProperties = parseProperties;
|
|
|
|
module.exports = Template;
|
|
|
|
},{"../../vendor/markdown":33}],16:[function(_dereq_,module,exports){
|
|
|
|
var Trigger = _dereq_('../story').Trigger;
|
|
var Core = _dereq_('../core');
|
|
var Hammer = _dereq_('hammerjs');
|
|
|
|
function Gestures(el) {
|
|
|
|
var gestures = {};
|
|
el = el || document;
|
|
function _hammerEvent(eventName, fnName) {
|
|
gestures[fnName]= function() {
|
|
var t = Trigger({});
|
|
Hammer(el).on(eventName, function() {
|
|
t.trigger();
|
|
});
|
|
return t;
|
|
};
|
|
}
|
|
|
|
_hammerEvent('swipeup', 'swipeUp');
|
|
_hammerEvent('swipedown', 'swipeDown');
|
|
_hammerEvent('swipeleft', 'swipeLeft');
|
|
_hammerEvent('swiperight', 'swipeRight');
|
|
|
|
return gestures;
|
|
|
|
}
|
|
|
|
module.exports = Gestures;
|
|
|
|
},{"../core":13,"../story":14,"hammerjs":31}],17:[function(_dereq_,module,exports){
|
|
|
|
module.exports = {
|
|
Scroll: _dereq_('./scroll'),
|
|
Sequential: _dereq_('./sequential'),
|
|
Keys: _dereq_('./keys'),
|
|
Video: _dereq_('./video'),
|
|
Gestures: _dereq_('./gestures')
|
|
};
|
|
|
|
},{"./gestures":16,"./keys":18,"./scroll":19,"./sequential":20,"./video":21}],18:[function(_dereq_,module,exports){
|
|
|
|
var Trigger = _dereq_('../story').Trigger;
|
|
var Core = _dereq_('../core');
|
|
|
|
/**
|
|
*
|
|
*
|
|
*/
|
|
function Keys() {
|
|
|
|
KEY_LEFT = 37;
|
|
KEY_UP = 38;
|
|
KEY_RIGHT = 39;
|
|
KEY_DOWN = 40;
|
|
|
|
var el = document;
|
|
|
|
var keys = {};
|
|
|
|
function listenForKey(el, k, callback) {
|
|
function keyDown(e) {
|
|
e = e || window.event;
|
|
|
|
var code = e.keyCode;
|
|
if (code === k) {
|
|
callback();
|
|
e.preventDefault ? e.preventDefault() : e.returnValue = false;
|
|
e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
|
|
}
|
|
}
|
|
|
|
if (!window.addEventListener) {
|
|
el.attachEvent("onkeydown", function load(event) {
|
|
keyDown(event);
|
|
});
|
|
} else {
|
|
el.addEventListener("keydown", function load(event) {
|
|
keyDown(event);
|
|
});
|
|
}
|
|
}
|
|
|
|
keys.left = function() {
|
|
var t = Trigger({});
|
|
listenForKey(el, KEY_LEFT, function() {
|
|
t.trigger();
|
|
});
|
|
return t;
|
|
}
|
|
|
|
keys.right = function() {
|
|
var t = Trigger({});
|
|
listenForKey(el, KEY_RIGHT, function() {
|
|
t.trigger();
|
|
});
|
|
return t;
|
|
}
|
|
|
|
keys.on = function(element) {
|
|
el = Core.getElement(element);
|
|
return keys;
|
|
}
|
|
|
|
return keys;
|
|
|
|
}
|
|
|
|
module.exports = Keys;
|
|
|
|
},{"../core":13,"../story":14}],19:[function(_dereq_,module,exports){
|
|
|
|
var Trigger = _dereq_('../story').Trigger;
|
|
var Core = _dereq_('../core');
|
|
|
|
function cte(c) { return function() { return c; } }
|
|
|
|
//TODO: add support for elements != window
|
|
function Scroll() {
|
|
|
|
var scroller = window;
|
|
var scrolls = [];
|
|
var initialized = false;
|
|
var offset = cte(0);
|
|
var condition = null;
|
|
|
|
function scroll() {}
|
|
|
|
scroll.reach = function(el) {
|
|
function _reach () {}
|
|
Trigger(_reach);
|
|
|
|
_reach.scroll = function(scrollY) {
|
|
var e = Core.getElement(el);
|
|
var bounds = e.getBoundingClientRect();
|
|
var offset = _reach.offset();
|
|
var t = condition(bounds, offset, scrollY);
|
|
if (t !== null && t !== undefined) {
|
|
_reach.trigger(t);
|
|
}
|
|
return _reach;
|
|
};
|
|
|
|
_reach.condition = function(_) {
|
|
if (!arguments.length) {
|
|
return condition;
|
|
}
|
|
condition = _;
|
|
}
|
|
|
|
/// sets offset in px or % of element
|
|
// offset('50%') offset(100)
|
|
_reach.offset = function(_) {
|
|
if (!arguments.length) {
|
|
return offset();
|
|
}
|
|
if (typeof(_) === 'number') {
|
|
offset = cte(_);
|
|
} else {
|
|
offset = function() {
|
|
//remove %
|
|
var percent = +_.replace('%', '');
|
|
return scroller.innerHeight * percent * 0.01;
|
|
}
|
|
}
|
|
return _reach;
|
|
}
|
|
|
|
_reach.reverse = function() {
|
|
var e = document.getElementById(el);
|
|
var bounds = e.getBoundingClientRect();
|
|
var offset = _reach.offset();
|
|
scroller.scrollTo(0, bounds.top - offset);
|
|
};
|
|
|
|
_reach.clear = function() {
|
|
unregister(_reach);
|
|
}
|
|
|
|
// add to working scrolls
|
|
register(_reach);
|
|
|
|
return _reach;
|
|
};
|
|
|
|
scroll.within = function(el) {
|
|
var r = scroll.reach(el);
|
|
r.condition(function(bounds, offset) {
|
|
if(bounds.top <= offset && bounds.bottom >= offset) {
|
|
var t = (offset - bounds.top)/(bounds.bottom - bounds.top);
|
|
return t;
|
|
}
|
|
});
|
|
return r;
|
|
};
|
|
|
|
scroll.less = function(el, opt) {
|
|
opt = opt || {};
|
|
var r = scroll.reach(el);
|
|
var fixedBoundsTop;
|
|
if (opt.fixed) {
|
|
var e = Core.getElement(el);
|
|
fixedBoundsTop = scroller.scrollY + e.getBoundingClientRect().top;
|
|
}
|
|
r.condition(function(bounds, offset, scrollY) {
|
|
var t = opt.fixed ? fixedBoundsTop: bounds.top;
|
|
var o = opt.fixed ? scrollY: offset;
|
|
if(t >= o) {
|
|
return 0;
|
|
}
|
|
});
|
|
return r;
|
|
};
|
|
|
|
scroll.greater = function(el, opt) {
|
|
opt = opt || {};
|
|
var r = scroll.reach(el);
|
|
var fixedBoundsTop;
|
|
if (opt.fixed) {
|
|
var e = Core.getElement(el);
|
|
fixedBoundsTop = scroller.scrollY + e.getBoundingClientRect().top;
|
|
}
|
|
r.condition(function(bounds, offset, scrollY) {
|
|
var t = opt.fixed ? fixedBoundsTop: bounds.top;
|
|
var o = opt.fixed ? scrollY: offset;
|
|
if(t <= o) {
|
|
return 0;
|
|
}
|
|
});
|
|
return r;
|
|
};
|
|
|
|
function register(s) {
|
|
scrolls.push(s);
|
|
initScroll();
|
|
}
|
|
|
|
function unregister(s) {
|
|
var i = scrolls.indexOf(s);
|
|
if (i >= 0) {
|
|
scrolls.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
function initScroll() {
|
|
if (!initialized) {
|
|
initialized = true;
|
|
|
|
if (!Array.prototype.forEach) {
|
|
|
|
Array.prototype.forEach = function (callback, thisArg) {
|
|
|
|
var T, k;
|
|
|
|
if (this == null) {
|
|
throw new TypeError(" this is null or not defined");
|
|
}
|
|
|
|
// 1. Let O be the result of calling ToObject passing the |this| value as the argument.
|
|
var O = Object(this);
|
|
|
|
// 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
|
|
// 3. Let len be ToUint32(lenValue).
|
|
var len = O.length >>> 0;
|
|
|
|
// 4. If IsCallable(callback) is false, throw a TypeError exception.
|
|
// See: http://es5.github.com/#x9.11
|
|
if (typeof callback !== "function") {
|
|
throw new TypeError(callback + " is not a function");
|
|
}
|
|
|
|
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
|
if (thisArg) {
|
|
T = thisArg;
|
|
}
|
|
|
|
// 6. Let k be 0
|
|
k = 0;
|
|
|
|
// 7. Repeat, while k < len
|
|
while (k < len) {
|
|
|
|
var kValue;
|
|
|
|
// a. Let Pk be ToString(k).
|
|
// This is implicit for LHS operands of the in operator
|
|
// b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
|
|
// This step can be combined with c
|
|
// c. If kPresent is true, then
|
|
if (k in O) {
|
|
|
|
// i. Let kValue be the result of calling the Get internal method of O with argument Pk.
|
|
kValue = O[k];
|
|
|
|
// ii. Call the Call internal method of callback with T as the this value and
|
|
// argument list containing kValue, k, and O.
|
|
callback.call(T, kValue, k, O);
|
|
}
|
|
// d. Increase k by 1.
|
|
k++;
|
|
}
|
|
// 8. return undefined
|
|
};
|
|
}
|
|
|
|
function scrollEach() {
|
|
scrolls.forEach(function(s) {
|
|
s.scroll(window.scrollY);
|
|
});
|
|
}
|
|
|
|
if (!window.addEventListener) {
|
|
scroller.attachEvent("onscroll", function load(event) {
|
|
scrollEach();
|
|
});
|
|
} else {
|
|
window.addEventListener("scroll", function load(event) {
|
|
scrollEach();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return scroll;
|
|
}
|
|
|
|
Scroll._scrolls = [];
|
|
module.exports = Scroll;
|
|
|
|
},{"../core":13,"../story":14}],20:[function(_dereq_,module,exports){
|
|
|
|
var Trigger = _dereq_('../story').Trigger;
|
|
|
|
function Sequential() {
|
|
var current = 0;
|
|
var steps = [];
|
|
var max = 0;
|
|
var triggers = {};
|
|
|
|
function seq() {}
|
|
|
|
function update() {
|
|
for (var i = 0; i < steps.length; ++i) {
|
|
steps[i].check();
|
|
}
|
|
}
|
|
|
|
seq.state = seq.step = function(n) {
|
|
if (n in triggers) {
|
|
return triggers[n];
|
|
}
|
|
var t = Trigger({
|
|
check: function() {
|
|
if (n === current && this.trigger) this.trigger();
|
|
},
|
|
reverse: function() {
|
|
current = n;
|
|
}
|
|
});
|
|
max = Math.max(max, n);
|
|
steps.push(t);
|
|
return triggers[n] = t;
|
|
};
|
|
|
|
seq.next = function() {
|
|
current += 1;
|
|
if (current > max) {
|
|
current = 0;
|
|
}
|
|
update();
|
|
};
|
|
|
|
seq.clear = function() {
|
|
steps = [];
|
|
max = 0;
|
|
current = 0;
|
|
return seq;
|
|
};
|
|
|
|
seq.current = function(_) {
|
|
if (_ !== undefined) {
|
|
var c = Math.max(Math.min(max, _), 0);
|
|
if (c !== current) {
|
|
current = c;
|
|
update();
|
|
}
|
|
return this;
|
|
}
|
|
return current;
|
|
};
|
|
|
|
seq.prev = function() {
|
|
current -= 1;
|
|
if (current < 0) {
|
|
current = max;
|
|
}
|
|
update();
|
|
};
|
|
|
|
return seq;
|
|
}
|
|
|
|
module.exports = Sequential;
|
|
|
|
},{"../story":14}],21:[function(_dereq_,module,exports){
|
|
|
|
function Video(player) {
|
|
if (typeof YT === 'undefined' || !(player instanceof YT.Player)) {
|
|
throw new Error("player should be a YT.Player instance, see youtube API");
|
|
}
|
|
|
|
var triggers = [];
|
|
|
|
var i = setInterval(function() {
|
|
var seconds = player.getCurrentTime();
|
|
for (var i = 0; i < triggers.length; ++i) {
|
|
var t = triggers[i];
|
|
if (t.start <= seconds && t.end > seconds) {
|
|
t.trigger((seconds - t.start)/(t.end - t.start));
|
|
}
|
|
}
|
|
}, 100);
|
|
|
|
var clear = function(t) {
|
|
if (triggers.length === 0) {
|
|
clearInterval(i);
|
|
}
|
|
};
|
|
|
|
return {
|
|
between: function(start, end) {
|
|
var t = O.Trigger();
|
|
t.start = start;
|
|
t.end = end;
|
|
triggers.push(t);
|
|
t.clear = function() {
|
|
triggers.splice(triggers.indexOf(t), 1);
|
|
clear();
|
|
};
|
|
return t;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = Video
|
|
|
|
},{}],22:[function(_dereq_,module,exports){
|
|
/**
|
|
# dot progress
|
|
ui widget that controls dot progress
|
|
|
|
## usage
|
|
in order to use it you need to instanciate using a container, so for example:
|
|
|
|
<div id="dots"></div>
|
|
|
|
...
|
|
|
|
var progress = DotProgress('dots')
|
|
|
|
// we set the number of slides
|
|
progress.count(10);
|
|
|
|
// we can activate it as an action
|
|
// then the story enters in this state the second dot
|
|
// will be activated
|
|
story.addState(trigger, progress.activate(1))
|
|
|
|
// when an user clicks on the dot it can trigger an action
|
|
story.addState(progress.step(1), action);
|
|
|
|
## styling
|
|
the html rendered is the following:
|
|
```
|
|
<div id="dots">
|
|
<ul>
|
|
<li><href="#0"></a></li>
|
|
<li><href="#1" class="active"></a></li>
|
|
<li><href="#2"></a></li>
|
|
<li><href="#2"></a></li>
|
|
</ul>
|
|
</div>
|
|
```
|
|
|
|
so you can use active class to style the active one
|
|
|
|
*/
|
|
|
|
var Core = _dereq_('../core');
|
|
|
|
function DotProgress(el) {
|
|
var count = 0;
|
|
var element = Core.getElement(el);
|
|
var triggers = {};
|
|
|
|
function _progress() {
|
|
return _progress;
|
|
}
|
|
|
|
function render() {
|
|
var html = '<ul>';
|
|
for(var i = 0; i < count; ++i) {
|
|
html += '<li><a slide-index="' + i + '" href="#' + i + '"></a></li>';
|
|
}
|
|
html += "</ul>";
|
|
element.innerHTML = html;
|
|
}
|
|
|
|
_progress.count = function(_) {
|
|
count = _;
|
|
render();
|
|
return _progress;
|
|
};
|
|
|
|
// returns an action to activate the index
|
|
_progress.activate = function(activeIndex) {
|
|
return O.Action(function () {
|
|
var children = element.children[0].children;
|
|
for(var i = 0; i < children.length; ++i) {
|
|
children[i].children[0].setAttribute('class', '');
|
|
}
|
|
children[activeIndex].children[0].setAttribute('class', 'active');
|
|
});
|
|
};
|
|
|
|
element.onclick = function(e) {
|
|
e = e || window.event;
|
|
|
|
var idx = (e.target || e.srcElement).getAttribute('slide-index');
|
|
var t = triggers[idx];
|
|
if (t) {
|
|
t.trigger();
|
|
}
|
|
};
|
|
|
|
_progress.step = function(i) {
|
|
var t = O.Trigger();
|
|
triggers[i] = t;
|
|
return t;
|
|
};
|
|
|
|
return _progress;
|
|
|
|
}
|
|
|
|
module.exports = DotProgress;
|
|
|
|
},{"../core":13}],23:[function(_dereq_,module,exports){
|
|
|
|
module.exports = {
|
|
DotProgress: _dereq_('./dotprogress')
|
|
}
|
|
|
|
},{"./dotprogress":22}],24:[function(_dereq_,module,exports){
|
|
/*
|
|
* classList.js: Cross-browser full element.classList implementation.
|
|
* 2014-01-31
|
|
*
|
|
* By Eli Grey, http://eligrey.com
|
|
* Public Domain.
|
|
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
|
*/
|
|
|
|
/*global self, document, DOMException */
|
|
|
|
/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
|
|
|
|
if ("document" in self && !("classList" in document.createElement("_"))) {
|
|
|
|
(function (view) {
|
|
|
|
"use strict";
|
|
|
|
if (!('Element' in view)) return;
|
|
|
|
var
|
|
classListProp = "classList"
|
|
, protoProp = "prototype"
|
|
, elemCtrProto = view.Element[protoProp]
|
|
, objCtr = Object
|
|
, strTrim = String[protoProp].trim || function () {
|
|
return this.replace(/^\s+|\s+$/g, "");
|
|
}
|
|
, arrIndexOf = Array[protoProp].indexOf || function (item) {
|
|
var
|
|
i = 0
|
|
, len = this.length
|
|
;
|
|
for (; i < len; i++) {
|
|
if (i in this && this[i] === item) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
// Vendors: please allow content code to instantiate DOMExceptions
|
|
, DOMEx = function (type, message) {
|
|
this.name = type;
|
|
this.code = DOMException[type];
|
|
this.message = message;
|
|
}
|
|
, checkTokenAndGetIndex = function (classList, token) {
|
|
if (token === "") {
|
|
throw new DOMEx(
|
|
"SYNTAX_ERR"
|
|
, "An invalid or illegal string was specified"
|
|
);
|
|
}
|
|
if (/\s/.test(token)) {
|
|
throw new DOMEx(
|
|
"INVALID_CHARACTER_ERR"
|
|
, "String contains an invalid character"
|
|
);
|
|
}
|
|
return arrIndexOf.call(classList, token);
|
|
}
|
|
, ClassList = function (elem) {
|
|
var
|
|
trimmedClasses = strTrim.call(elem.getAttribute("class") || "")
|
|
, classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
|
|
, i = 0
|
|
, len = classes.length
|
|
;
|
|
for (; i < len; i++) {
|
|
this.push(classes[i]);
|
|
}
|
|
this._updateClassName = function () {
|
|
elem.setAttribute("class", this.toString());
|
|
};
|
|
}
|
|
, classListProto = ClassList[protoProp] = []
|
|
, classListGetter = function () {
|
|
return new ClassList(this);
|
|
}
|
|
;
|
|
// Most DOMException implementations don't allow calling DOMException's toString()
|
|
// on non-DOMExceptions. Error's toString() is sufficient here.
|
|
DOMEx[protoProp] = Error[protoProp];
|
|
classListProto.item = function (i) {
|
|
return this[i] || null;
|
|
};
|
|
classListProto.contains = function (token) {
|
|
token += "";
|
|
return checkTokenAndGetIndex(this, token) !== -1;
|
|
};
|
|
classListProto.add = function () {
|
|
var
|
|
tokens = arguments
|
|
, i = 0
|
|
, l = tokens.length
|
|
, token
|
|
, updated = false
|
|
;
|
|
do {
|
|
token = tokens[i] + "";
|
|
if (checkTokenAndGetIndex(this, token) === -1) {
|
|
this.push(token);
|
|
updated = true;
|
|
}
|
|
}
|
|
while (++i < l);
|
|
|
|
if (updated) {
|
|
this._updateClassName();
|
|
}
|
|
};
|
|
classListProto.remove = function () {
|
|
var
|
|
tokens = arguments
|
|
, i = 0
|
|
, l = tokens.length
|
|
, token
|
|
, updated = false
|
|
;
|
|
do {
|
|
token = tokens[i] + "";
|
|
var index = checkTokenAndGetIndex(this, token);
|
|
if (index !== -1) {
|
|
this.splice(index, 1);
|
|
updated = true;
|
|
}
|
|
}
|
|
while (++i < l);
|
|
|
|
if (updated) {
|
|
this._updateClassName();
|
|
}
|
|
};
|
|
classListProto.toggle = function (token, force) {
|
|
token += "";
|
|
|
|
var
|
|
result = this.contains(token)
|
|
, method = result ?
|
|
force !== true && "remove"
|
|
:
|
|
force !== false && "add"
|
|
;
|
|
|
|
if (method) {
|
|
this[method](token);
|
|
}
|
|
|
|
return !result;
|
|
};
|
|
classListProto.toString = function () {
|
|
return this.join(" ");
|
|
};
|
|
|
|
if (objCtr.defineProperty) {
|
|
var classListPropDesc = {
|
|
get: classListGetter
|
|
, enumerable: true
|
|
, configurable: true
|
|
};
|
|
try {
|
|
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
|
|
} catch (ex) { // IE 8 doesn't support enumerable:true
|
|
if (ex.number === -0x7FF5EC54) {
|
|
classListPropDesc.enumerable = false;
|
|
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
|
|
}
|
|
}
|
|
} else if (objCtr[protoProp].__defineGetter__) {
|
|
elemCtrProto.__defineGetter__(classListProp, classListGetter);
|
|
}
|
|
|
|
}(self));
|
|
|
|
}
|
|
|
|
},{}],25:[function(_dereq_,module,exports){
|
|
/**
|
|
* new L.CrossHair('http://image.com/image', { x: 10, y :10 }).addTo(map)
|
|
*/
|
|
if (typeof window.L !== 'undefined') {
|
|
|
|
L.CrossHair = L.Control.extend({
|
|
|
|
initialize: function(img, size) {
|
|
this.image = new Image();
|
|
this.image.onload = L.bind(this._updatePos, this);
|
|
this.image.src = img;
|
|
if (size) {
|
|
this.image.width = size.x;
|
|
this.image.height = size.y;
|
|
}
|
|
},
|
|
|
|
_updatePos: function() {
|
|
if (this._map) {
|
|
var w = this.image.width >> 1;
|
|
var h = this.image.height >> 1;
|
|
w = this._map.getSize().x/2.0 - w;
|
|
h = this._map.getSize().y/2.0 - h;
|
|
L.DomUtil.setPosition(this.image, { x: w, y: h });
|
|
}
|
|
},
|
|
|
|
addTo: function(map) {
|
|
var r = L.Control.prototype.addTo.call(this, map);
|
|
// remove leaflet-top and leaflet-right classes
|
|
this.image.parentNode.setAttribute('class', '');
|
|
map.on('resize', L.bind(this._updatePos, this));
|
|
return r;
|
|
},
|
|
|
|
onRemove: function(map) {
|
|
map.off('resize', null, this);
|
|
},
|
|
|
|
onAdd: function(map) {
|
|
this._updatePos();
|
|
return this.image;
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},{}],26:[function(_dereq_,module,exports){
|
|
|
|
module.exports = {
|
|
CrossHair: _dereq_('./crosshair')
|
|
}
|
|
|
|
},{"./crosshair":25}],27:[function(_dereq_,module,exports){
|
|
if (typeof Object.create === 'function') {
|
|
// implementation from standard node.js 'util' module
|
|
module.exports = function inherits(ctor, superCtor) {
|
|
ctor.super_ = superCtor
|
|
ctor.prototype = Object.create(superCtor.prototype, {
|
|
constructor: {
|
|
value: ctor,
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true
|
|
}
|
|
});
|
|
};
|
|
} else {
|
|
// old school shim for old browsers
|
|
module.exports = function inherits(ctor, superCtor) {
|
|
ctor.super_ = superCtor
|
|
var TempCtor = function () {}
|
|
TempCtor.prototype = superCtor.prototype
|
|
ctor.prototype = new TempCtor()
|
|
ctor.prototype.constructor = ctor
|
|
}
|
|
}
|
|
|
|
},{}],28:[function(_dereq_,module,exports){
|
|
// shim for using process in browser
|
|
|
|
var process = module.exports = {};
|
|
|
|
process.nextTick = (function () {
|
|
var canSetImmediate = typeof window !== 'undefined'
|
|
&& window.setImmediate;
|
|
var canPost = typeof window !== 'undefined'
|
|
&& window.postMessage && window.addEventListener
|
|
;
|
|
|
|
if (canSetImmediate) {
|
|
return function (f) { return window.setImmediate(f) };
|
|
}
|
|
|
|
if (canPost) {
|
|
var queue = [];
|
|
window.addEventListener('message', function (ev) {
|
|
var source = ev.source;
|
|
if ((source === window || source === null) && ev.data === 'process-tick') {
|
|
ev.stopPropagation();
|
|
if (queue.length > 0) {
|
|
var fn = queue.shift();
|
|
fn();
|
|
}
|
|
}
|
|
}, true);
|
|
|
|
return function nextTick(fn) {
|
|
queue.push(fn);
|
|
window.postMessage('process-tick', '*');
|
|
};
|
|
}
|
|
|
|
return function nextTick(fn) {
|
|
setTimeout(fn, 0);
|
|
};
|
|
})();
|
|
|
|
process.title = 'browser';
|
|
process.browser = true;
|
|
process.env = {};
|
|
process.argv = [];
|
|
|
|
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');
|
|
}
|
|
|
|
// TODO(shtylman)
|
|
process.cwd = function () { return '/' };
|
|
process.chdir = function (dir) {
|
|
throw new Error('process.chdir is not supported');
|
|
};
|
|
|
|
},{}],29:[function(_dereq_,module,exports){
|
|
module.exports = function isBuffer(arg) {
|
|
return arg && typeof arg === 'object'
|
|
&& typeof arg.copy === 'function'
|
|
&& typeof arg.fill === 'function'
|
|
&& typeof arg.readUInt8 === 'function';
|
|
}
|
|
},{}],30:[function(_dereq_,module,exports){
|
|
(function (process,global){
|
|
// Copyright Joyent, Inc. and other Node contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
// following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
var formatRegExp = /%[sdj%]/g;
|
|
exports.format = function(f) {
|
|
if (!isString(f)) {
|
|
var objects = [];
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
objects.push(inspect(arguments[i]));
|
|
}
|
|
return objects.join(' ');
|
|
}
|
|
|
|
var i = 1;
|
|
var args = arguments;
|
|
var len = args.length;
|
|
var str = String(f).replace(formatRegExp, function(x) {
|
|
if (x === '%%') return '%';
|
|
if (i >= len) return x;
|
|
switch (x) {
|
|
case '%s': return String(args[i++]);
|
|
case '%d': return Number(args[i++]);
|
|
case '%j':
|
|
try {
|
|
return JSON.stringify(args[i++]);
|
|
} catch (_) {
|
|
return '[Circular]';
|
|
}
|
|
default:
|
|
return x;
|
|
}
|
|
});
|
|
for (var x = args[i]; i < len; x = args[++i]) {
|
|
if (isNull(x) || !isObject(x)) {
|
|
str += ' ' + x;
|
|
} else {
|
|
str += ' ' + inspect(x);
|
|
}
|
|
}
|
|
return str;
|
|
};
|
|
|
|
|
|
// Mark that a method should not be used.
|
|
// Returns a modified function which warns once by default.
|
|
// If --no-deprecation is set, then it is a no-op.
|
|
exports.deprecate = function(fn, msg) {
|
|
// Allow for deprecating things in the process of starting up.
|
|
if (isUndefined(global.process)) {
|
|
return function() {
|
|
return exports.deprecate(fn, msg).apply(this, arguments);
|
|
};
|
|
}
|
|
|
|
if (process.noDeprecation === true) {
|
|
return fn;
|
|
}
|
|
|
|
var warned = false;
|
|
function deprecated() {
|
|
if (!warned) {
|
|
if (process.throwDeprecation) {
|
|
throw new Error(msg);
|
|
} else if (process.traceDeprecation) {
|
|
console.trace(msg);
|
|
} else {
|
|
console.error(msg);
|
|
}
|
|
warned = true;
|
|
}
|
|
return fn.apply(this, arguments);
|
|
}
|
|
|
|
return deprecated;
|
|
};
|
|
|
|
|
|
var debugs = {};
|
|
var debugEnviron;
|
|
exports.debuglog = function(set) {
|
|
if (isUndefined(debugEnviron))
|
|
debugEnviron = process.env.NODE_DEBUG || '';
|
|
set = set.toUpperCase();
|
|
if (!debugs[set]) {
|
|
if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) {
|
|
var pid = process.pid;
|
|
debugs[set] = function() {
|
|
var msg = exports.format.apply(exports, arguments);
|
|
console.error('%s %d: %s', set, pid, msg);
|
|
};
|
|
} else {
|
|
debugs[set] = function() {};
|
|
}
|
|
}
|
|
return debugs[set];
|
|
};
|
|
|
|
|
|
/**
|
|
* Echos the value of a value. Trys to print the value out
|
|
* in the best way possible given the different types.
|
|
*
|
|
* @param {Object} obj The object to print out.
|
|
* @param {Object} opts Optional options object that alters the output.
|
|
*/
|
|
/* legacy: obj, showHidden, depth, colors*/
|
|
function inspect(obj, opts) {
|
|
// default options
|
|
var ctx = {
|
|
seen: [],
|
|
stylize: stylizeNoColor
|
|
};
|
|
// legacy...
|
|
if (arguments.length >= 3) ctx.depth = arguments[2];
|
|
if (arguments.length >= 4) ctx.colors = arguments[3];
|
|
if (isBoolean(opts)) {
|
|
// legacy...
|
|
ctx.showHidden = opts;
|
|
} else if (opts) {
|
|
// got an "options" object
|
|
exports._extend(ctx, opts);
|
|
}
|
|
// set default options
|
|
if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
|
|
if (isUndefined(ctx.depth)) ctx.depth = 2;
|
|
if (isUndefined(ctx.colors)) ctx.colors = false;
|
|
if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
|
|
if (ctx.colors) ctx.stylize = stylizeWithColor;
|
|
return formatValue(ctx, obj, ctx.depth);
|
|
}
|
|
exports.inspect = inspect;
|
|
|
|
|
|
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
|
|
inspect.colors = {
|
|
'bold' : [1, 22],
|
|
'italic' : [3, 23],
|
|
'underline' : [4, 24],
|
|
'inverse' : [7, 27],
|
|
'white' : [37, 39],
|
|
'grey' : [90, 39],
|
|
'black' : [30, 39],
|
|
'blue' : [34, 39],
|
|
'cyan' : [36, 39],
|
|
'green' : [32, 39],
|
|
'magenta' : [35, 39],
|
|
'red' : [31, 39],
|
|
'yellow' : [33, 39]
|
|
};
|
|
|
|
// Don't use 'blue' not visible on cmd.exe
|
|
inspect.styles = {
|
|
'special': 'cyan',
|
|
'number': 'yellow',
|
|
'boolean': 'yellow',
|
|
'undefined': 'grey',
|
|
'null': 'bold',
|
|
'string': 'green',
|
|
'date': 'magenta',
|
|
// "name": intentionally not styling
|
|
'regexp': 'red'
|
|
};
|
|
|
|
|
|
function stylizeWithColor(str, styleType) {
|
|
var style = inspect.styles[styleType];
|
|
|
|
if (style) {
|
|
return '\u001b[' + inspect.colors[style][0] + 'm' + str +
|
|
'\u001b[' + inspect.colors[style][1] + 'm';
|
|
} else {
|
|
return str;
|
|
}
|
|
}
|
|
|
|
|
|
function stylizeNoColor(str, styleType) {
|
|
return str;
|
|
}
|
|
|
|
|
|
function arrayToHash(array) {
|
|
var hash = {};
|
|
|
|
array.forEach(function(val, idx) {
|
|
hash[val] = true;
|
|
});
|
|
|
|
return hash;
|
|
}
|
|
|
|
|
|
function formatValue(ctx, value, recurseTimes) {
|
|
// Provide a hook for user-specified inspect functions.
|
|
// Check that value is an object with an inspect function on it
|
|
if (ctx.customInspect &&
|
|
value &&
|
|
isFunction(value.inspect) &&
|
|
// Filter out the util module, it's inspect function is special
|
|
value.inspect !== exports.inspect &&
|
|
// Also filter out any prototype objects using the circular check.
|
|
!(value.constructor && value.constructor.prototype === value)) {
|
|
var ret = value.inspect(recurseTimes, ctx);
|
|
if (!isString(ret)) {
|
|
ret = formatValue(ctx, ret, recurseTimes);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Primitive types cannot have properties
|
|
var primitive = formatPrimitive(ctx, value);
|
|
if (primitive) {
|
|
return primitive;
|
|
}
|
|
|
|
// Look up the keys of the object.
|
|
var keys = Object.keys(value);
|
|
var visibleKeys = arrayToHash(keys);
|
|
|
|
if (ctx.showHidden) {
|
|
keys = Object.getOwnPropertyNames(value);
|
|
}
|
|
|
|
// IE doesn't make error fields non-enumerable
|
|
// http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
|
|
if (isError(value)
|
|
&& (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
|
|
return formatError(value);
|
|
}
|
|
|
|
// Some type of object without properties can be shortcutted.
|
|
if (keys.length === 0) {
|
|
if (isFunction(value)) {
|
|
var name = value.name ? ': ' + value.name : '';
|
|
return ctx.stylize('[Function' + name + ']', 'special');
|
|
}
|
|
if (isRegExp(value)) {
|
|
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
|
|
}
|
|
if (isDate(value)) {
|
|
return ctx.stylize(Date.prototype.toString.call(value), 'date');
|
|
}
|
|
if (isError(value)) {
|
|
return formatError(value);
|
|
}
|
|
}
|
|
|
|
var base = '', array = false, braces = ['{', '}'];
|
|
|
|
// Make Array say that they are Array
|
|
if (isArray(value)) {
|
|
array = true;
|
|
braces = ['[', ']'];
|
|
}
|
|
|
|
// Make functions say that they are functions
|
|
if (isFunction(value)) {
|
|
var n = value.name ? ': ' + value.name : '';
|
|
base = ' [Function' + n + ']';
|
|
}
|
|
|
|
// Make RegExps say that they are RegExps
|
|
if (isRegExp(value)) {
|
|
base = ' ' + RegExp.prototype.toString.call(value);
|
|
}
|
|
|
|
// Make dates with properties first say the date
|
|
if (isDate(value)) {
|
|
base = ' ' + Date.prototype.toUTCString.call(value);
|
|
}
|
|
|
|
// Make error with message first say the error
|
|
if (isError(value)) {
|
|
base = ' ' + formatError(value);
|
|
}
|
|
|
|
if (keys.length === 0 && (!array || value.length == 0)) {
|
|
return braces[0] + base + braces[1];
|
|
}
|
|
|
|
if (recurseTimes < 0) {
|
|
if (isRegExp(value)) {
|
|
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
|
|
} else {
|
|
return ctx.stylize('[Object]', 'special');
|
|
}
|
|
}
|
|
|
|
ctx.seen.push(value);
|
|
|
|
var output;
|
|
if (array) {
|
|
output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
|
|
} else {
|
|
output = keys.map(function(key) {
|
|
return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
|
|
});
|
|
}
|
|
|
|
ctx.seen.pop();
|
|
|
|
return reduceToSingleString(output, base, braces);
|
|
}
|
|
|
|
|
|
function formatPrimitive(ctx, value) {
|
|
if (isUndefined(value))
|
|
return ctx.stylize('undefined', 'undefined');
|
|
if (isString(value)) {
|
|
var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
|
|
.replace(/'/g, "\\'")
|
|
.replace(/\\"/g, '"') + '\'';
|
|
return ctx.stylize(simple, 'string');
|
|
}
|
|
if (isNumber(value))
|
|
return ctx.stylize('' + value, 'number');
|
|
if (isBoolean(value))
|
|
return ctx.stylize('' + value, 'boolean');
|
|
// For some reason typeof null is "object", so special case here.
|
|
if (isNull(value))
|
|
return ctx.stylize('null', 'null');
|
|
}
|
|
|
|
|
|
function formatError(value) {
|
|
return '[' + Error.prototype.toString.call(value) + ']';
|
|
}
|
|
|
|
|
|
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
|
|
var output = [];
|
|
for (var i = 0, l = value.length; i < l; ++i) {
|
|
if (hasOwnProperty(value, String(i))) {
|
|
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
|
|
String(i), true));
|
|
} else {
|
|
output.push('');
|
|
}
|
|
}
|
|
keys.forEach(function(key) {
|
|
if (!key.match(/^\d+$/)) {
|
|
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
|
|
key, true));
|
|
}
|
|
});
|
|
return output;
|
|
}
|
|
|
|
|
|
function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
|
|
var name, str, desc;
|
|
desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
|
|
if (desc.get) {
|
|
if (desc.set) {
|
|
str = ctx.stylize('[Getter/Setter]', 'special');
|
|
} else {
|
|
str = ctx.stylize('[Getter]', 'special');
|
|
}
|
|
} else {
|
|
if (desc.set) {
|
|
str = ctx.stylize('[Setter]', 'special');
|
|
}
|
|
}
|
|
if (!hasOwnProperty(visibleKeys, key)) {
|
|
name = '[' + key + ']';
|
|
}
|
|
if (!str) {
|
|
if (ctx.seen.indexOf(desc.value) < 0) {
|
|
if (isNull(recurseTimes)) {
|
|
str = formatValue(ctx, desc.value, null);
|
|
} else {
|
|
str = formatValue(ctx, desc.value, recurseTimes - 1);
|
|
}
|
|
if (str.indexOf('\n') > -1) {
|
|
if (array) {
|
|
str = str.split('\n').map(function(line) {
|
|
return ' ' + line;
|
|
}).join('\n').substr(2);
|
|
} else {
|
|
str = '\n' + str.split('\n').map(function(line) {
|
|
return ' ' + line;
|
|
}).join('\n');
|
|
}
|
|
}
|
|
} else {
|
|
str = ctx.stylize('[Circular]', 'special');
|
|
}
|
|
}
|
|
if (isUndefined(name)) {
|
|
if (array && key.match(/^\d+$/)) {
|
|
return str;
|
|
}
|
|
name = JSON.stringify('' + key);
|
|
if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
|
|
name = name.substr(1, name.length - 2);
|
|
name = ctx.stylize(name, 'name');
|
|
} else {
|
|
name = name.replace(/'/g, "\\'")
|
|
.replace(/\\"/g, '"')
|
|
.replace(/(^"|"$)/g, "'");
|
|
name = ctx.stylize(name, 'string');
|
|
}
|
|
}
|
|
|
|
return name + ': ' + str;
|
|
}
|
|
|
|
|
|
function reduceToSingleString(output, base, braces) {
|
|
var numLinesEst = 0;
|
|
var length = output.reduce(function(prev, cur) {
|
|
numLinesEst++;
|
|
if (cur.indexOf('\n') >= 0) numLinesEst++;
|
|
return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
|
|
}, 0);
|
|
|
|
if (length > 60) {
|
|
return braces[0] +
|
|
(base === '' ? '' : base + '\n ') +
|
|
' ' +
|
|
output.join(',\n ') +
|
|
' ' +
|
|
braces[1];
|
|
}
|
|
|
|
return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
|
|
}
|
|
|
|
|
|
// NOTE: These type checking functions intentionally don't use `instanceof`
|
|
// because it is fragile and can be easily faked with `Object.create()`.
|
|
function isArray(ar) {
|
|
return Array.isArray(ar);
|
|
}
|
|
exports.isArray = isArray;
|
|
|
|
function isBoolean(arg) {
|
|
return typeof arg === 'boolean';
|
|
}
|
|
exports.isBoolean = isBoolean;
|
|
|
|
function isNull(arg) {
|
|
return arg === null;
|
|
}
|
|
exports.isNull = isNull;
|
|
|
|
function isNullOrUndefined(arg) {
|
|
return arg == null;
|
|
}
|
|
exports.isNullOrUndefined = isNullOrUndefined;
|
|
|
|
function isNumber(arg) {
|
|
return typeof arg === 'number';
|
|
}
|
|
exports.isNumber = isNumber;
|
|
|
|
function isString(arg) {
|
|
return typeof arg === 'string';
|
|
}
|
|
exports.isString = isString;
|
|
|
|
function isSymbol(arg) {
|
|
return typeof arg === 'symbol';
|
|
}
|
|
exports.isSymbol = isSymbol;
|
|
|
|
function isUndefined(arg) {
|
|
return arg === void 0;
|
|
}
|
|
exports.isUndefined = isUndefined;
|
|
|
|
function isRegExp(re) {
|
|
return isObject(re) && objectToString(re) === '[object RegExp]';
|
|
}
|
|
exports.isRegExp = isRegExp;
|
|
|
|
function isObject(arg) {
|
|
return typeof arg === 'object' && arg !== null;
|
|
}
|
|
exports.isObject = isObject;
|
|
|
|
function isDate(d) {
|
|
return isObject(d) && objectToString(d) === '[object Date]';
|
|
}
|
|
exports.isDate = isDate;
|
|
|
|
function isError(e) {
|
|
return isObject(e) &&
|
|
(objectToString(e) === '[object Error]' || e instanceof Error);
|
|
}
|
|
exports.isError = isError;
|
|
|
|
function isFunction(arg) {
|
|
return typeof arg === 'function';
|
|
}
|
|
exports.isFunction = isFunction;
|
|
|
|
function isPrimitive(arg) {
|
|
return arg === null ||
|
|
typeof arg === 'boolean' ||
|
|
typeof arg === 'number' ||
|
|
typeof arg === 'string' ||
|
|
typeof arg === 'symbol' || // ES6 symbol
|
|
typeof arg === 'undefined';
|
|
}
|
|
exports.isPrimitive = isPrimitive;
|
|
|
|
exports.isBuffer = _dereq_('./support/isBuffer');
|
|
|
|
function objectToString(o) {
|
|
return Object.prototype.toString.call(o);
|
|
}
|
|
|
|
|
|
function pad(n) {
|
|
return n < 10 ? '0' + n.toString(10) : n.toString(10);
|
|
}
|
|
|
|
|
|
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
|
|
'Oct', 'Nov', 'Dec'];
|
|
|
|
// 26 Feb 16:19:34
|
|
function timestamp() {
|
|
var d = new Date();
|
|
var time = [pad(d.getHours()),
|
|
pad(d.getMinutes()),
|
|
pad(d.getSeconds())].join(':');
|
|
return [d.getDate(), months[d.getMonth()], time].join(' ');
|
|
}
|
|
|
|
|
|
// log is just a thin wrapper to console.log that prepends a timestamp
|
|
exports.log = function() {
|
|
console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments));
|
|
};
|
|
|
|
|
|
/**
|
|
* Inherit the prototype methods from one constructor into another.
|
|
*
|
|
* The Function.prototype.inherits from lang.js rewritten as a standalone
|
|
* function (not on Function.prototype). NOTE: If this file is to be loaded
|
|
* during bootstrapping this function needs to be rewritten using some native
|
|
* functions as prototype setup using normal JavaScript does not work as
|
|
* expected during bootstrapping (see mirror.js in r114903).
|
|
*
|
|
* @param {function} ctor Constructor function which needs to inherit the
|
|
* prototype.
|
|
* @param {function} superCtor Constructor function to inherit prototype from.
|
|
*/
|
|
exports.inherits = _dereq_('inherits');
|
|
|
|
exports._extend = function(origin, add) {
|
|
// Don't do anything if add isn't an object
|
|
if (!add || !isObject(add)) return origin;
|
|
|
|
var keys = Object.keys(add);
|
|
var i = keys.length;
|
|
while (i--) {
|
|
origin[keys[i]] = add[keys[i]];
|
|
}
|
|
return origin;
|
|
};
|
|
|
|
function hasOwnProperty(obj, prop) {
|
|
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
}
|
|
|
|
}).call(this,_dereq_("FWaASH"),typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
|
},{"./support/isBuffer":29,"FWaASH":28,"inherits":27}],31:[function(_dereq_,module,exports){
|
|
/*! Hammer.JS - v1.1.3 - 2014-05-20
|
|
* http://eightmedia.github.io/hammer.js
|
|
*
|
|
* Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
|
|
* Licensed under the MIT license */
|
|
|
|
(function(window, undefined) {
|
|
'use strict';
|
|
|
|
/**
|
|
* @main
|
|
* @module hammer
|
|
*
|
|
* @class Hammer
|
|
* @static
|
|
*/
|
|
|
|
/**
|
|
* Hammer, use this to create instances
|
|
* ````
|
|
* var hammertime = new Hammer(myElement);
|
|
* ````
|
|
*
|
|
* @method Hammer
|
|
* @param {HTMLElement} element
|
|
* @param {Object} [options={}]
|
|
* @return {Hammer.Instance}
|
|
*/
|
|
var Hammer = function Hammer(element, options) {
|
|
return new Hammer.Instance(element, options || {});
|
|
};
|
|
|
|
/**
|
|
* version, as defined in package.json
|
|
* the value will be set at each build
|
|
* @property VERSION
|
|
* @final
|
|
* @type {String}
|
|
*/
|
|
Hammer.VERSION = '1.1.3';
|
|
|
|
/**
|
|
* default settings.
|
|
* more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled
|
|
* by setting it's name (like `swipe`) to false.
|
|
* You can set the defaults for all instances by changing this object before creating an instance.
|
|
* @example
|
|
* ````
|
|
* Hammer.defaults.drag = false;
|
|
* Hammer.defaults.behavior.touchAction = 'pan-y';
|
|
* delete Hammer.defaults.behavior.userSelect;
|
|
* ````
|
|
* @property defaults
|
|
* @type {Object}
|
|
*/
|
|
Hammer.defaults = {
|
|
/**
|
|
* this setting object adds styles and attributes to the element to prevent the browser from doing
|
|
* its native behavior. The css properties are auto prefixed for the browsers when needed.
|
|
* @property defaults.behavior
|
|
* @type {Object}
|
|
*/
|
|
behavior: {
|
|
/**
|
|
* Disables text selection to improve the dragging gesture. When the value is `none` it also sets
|
|
* `onselectstart=false` for IE on the element. Mainly for desktop browsers.
|
|
* @property defaults.behavior.userSelect
|
|
* @type {String}
|
|
* @default 'none'
|
|
*/
|
|
userSelect: 'none',
|
|
|
|
/**
|
|
* Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming).
|
|
* Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event.
|
|
* @property defaults.behavior.touchAction
|
|
* @type {String}
|
|
* @default: 'pan-y'
|
|
*/
|
|
touchAction: 'pan-y',
|
|
|
|
/**
|
|
* Disables the default callout shown when you touch and hold a touch target.
|
|
* On iOS, when you touch and hold a touch target such as a link, Safari displays
|
|
* a callout containing information about the link. This property allows you to disable that callout.
|
|
* @property defaults.behavior.touchCallout
|
|
* @type {String}
|
|
* @default 'none'
|
|
*/
|
|
touchCallout: 'none',
|
|
|
|
/**
|
|
* Specifies whether zooming is enabled. Used by IE10>
|
|
* @property defaults.behavior.contentZooming
|
|
* @type {String}
|
|
* @default 'none'
|
|
*/
|
|
contentZooming: 'none',
|
|
|
|
/**
|
|
* Specifies that an entire element should be draggable instead of its contents.
|
|
* Mainly for desktop browsers.
|
|
* @property defaults.behavior.userDrag
|
|
* @type {String}
|
|
* @default 'none'
|
|
*/
|
|
userDrag: 'none',
|
|
|
|
/**
|
|
* Overrides the highlight color shown when the user taps a link or a JavaScript
|
|
* clickable element in Safari on iPhone. This property obeys the alpha value, if specified.
|
|
*
|
|
* If you don't specify an alpha value, Safari on iPhone applies a default alpha value
|
|
* to the color. To disable tap highlighting, set the alpha value to 0 (invisible).
|
|
* If you set the alpha value to 1.0 (opaque), the element is not visible when tapped.
|
|
* @property defaults.behavior.tapHighlightColor
|
|
* @type {String}
|
|
* @default 'rgba(0,0,0,0)'
|
|
*/
|
|
tapHighlightColor: 'rgba(0,0,0,0)'
|
|
}
|
|
};
|
|
|
|
/**
|
|
* hammer document where the base events are added at
|
|
* @property DOCUMENT
|
|
* @type {HTMLElement}
|
|
* @default window.document
|
|
*/
|
|
Hammer.DOCUMENT = document;
|
|
|
|
/**
|
|
* detect support for pointer events
|
|
* @property HAS_POINTEREVENTS
|
|
* @type {Boolean}
|
|
*/
|
|
Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
|
|
|
|
/**
|
|
* detect support for touch events
|
|
* @property HAS_TOUCHEVENTS
|
|
* @type {Boolean}
|
|
*/
|
|
Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
|
|
|
|
/**
|
|
* detect mobile browsers
|
|
* @property IS_MOBILE
|
|
* @type {Boolean}
|
|
*/
|
|
Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent);
|
|
|
|
/**
|
|
* detect if we want to support mouseevents at all
|
|
* @property NO_MOUSEEVENTS
|
|
* @type {Boolean}
|
|
*/
|
|
Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS;
|
|
|
|
/**
|
|
* interval in which Hammer recalculates current velocity/direction/angle in ms
|
|
* @property CALCULATE_INTERVAL
|
|
* @type {Number}
|
|
* @default 25
|
|
*/
|
|
Hammer.CALCULATE_INTERVAL = 25;
|
|
|
|
/**
|
|
* eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup`
|
|
* the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`)
|
|
* @property EVENT_TYPES
|
|
* @private
|
|
* @writeOnce
|
|
* @type {Object}
|
|
*/
|
|
var EVENT_TYPES = {};
|
|
|
|
/**
|
|
* direction strings, for safe comparisons
|
|
* @property DIRECTION_DOWN|LEFT|UP|RIGHT
|
|
* @final
|
|
* @type {String}
|
|
* @default 'down' 'left' 'up' 'right'
|
|
*/
|
|
var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down';
|
|
var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left';
|
|
var DIRECTION_UP = Hammer.DIRECTION_UP = 'up';
|
|
var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right';
|
|
|
|
/**
|
|
* pointertype strings, for safe comparisons
|
|
* @property POINTER_MOUSE|TOUCH|PEN
|
|
* @final
|
|
* @type {String}
|
|
* @default 'mouse' 'touch' 'pen'
|
|
*/
|
|
var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse';
|
|
var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch';
|
|
var POINTER_PEN = Hammer.POINTER_PEN = 'pen';
|
|
|
|
/**
|
|
* eventtypes
|
|
* @property EVENT_START|MOVE|END|RELEASE|TOUCH
|
|
* @final
|
|
* @type {String}
|
|
* @default 'start' 'change' 'move' 'end' 'release' 'touch'
|
|
*/
|
|
var EVENT_START = Hammer.EVENT_START = 'start';
|
|
var EVENT_MOVE = Hammer.EVENT_MOVE = 'move';
|
|
var EVENT_END = Hammer.EVENT_END = 'end';
|
|
var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release';
|
|
var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch';
|
|
|
|
/**
|
|
* if the window events are set...
|
|
* @property READY
|
|
* @writeOnce
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
Hammer.READY = false;
|
|
|
|
/**
|
|
* plugins namespace
|
|
* @property plugins
|
|
* @type {Object}
|
|
*/
|
|
Hammer.plugins = Hammer.plugins || {};
|
|
|
|
/**
|
|
* gestures namespace
|
|
* see `/gestures` for the definitions
|
|
* @property gestures
|
|
* @type {Object}
|
|
*/
|
|
Hammer.gestures = Hammer.gestures || {};
|
|
|
|
/**
|
|
* setup events to detect gestures on the document
|
|
* this function is called when creating an new instance
|
|
* @private
|
|
*/
|
|
function setup() {
|
|
if(Hammer.READY) {
|
|
return;
|
|
}
|
|
|
|
// find what eventtypes we add listeners to
|
|
Event.determineEventTypes();
|
|
|
|
// Register all gestures inside Hammer.gestures
|
|
Utils.each(Hammer.gestures, function(gesture) {
|
|
Detection.register(gesture);
|
|
});
|
|
|
|
// Add touch events on the document
|
|
Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect);
|
|
Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect);
|
|
|
|
// Hammer is ready...!
|
|
Hammer.READY = true;
|
|
}
|
|
|
|
/**
|
|
* @module hammer
|
|
*
|
|
* @class Utils
|
|
* @static
|
|
*/
|
|
var Utils = Hammer.utils = {
|
|
/**
|
|
* extend method, could also be used for cloning when `dest` is an empty object.
|
|
* changes the dest object
|
|
* @method extend
|
|
* @param {Object} dest
|
|
* @param {Object} src
|
|
* @param {Boolean} [merge=false] do a merge
|
|
* @return {Object} dest
|
|
*/
|
|
extend: function extend(dest, src, merge) {
|
|
for(var key in src) {
|
|
if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) {
|
|
continue;
|
|
}
|
|
dest[key] = src[key];
|
|
}
|
|
return dest;
|
|
},
|
|
|
|
/**
|
|
* simple addEventListener wrapper
|
|
* @method on
|
|
* @param {HTMLElement} element
|
|
* @param {String} type
|
|
* @param {Function} handler
|
|
*/
|
|
on: function on(element, type, handler) {
|
|
element.addEventListener(type, handler, false);
|
|
},
|
|
|
|
/**
|
|
* simple removeEventListener wrapper
|
|
* @method off
|
|
* @param {HTMLElement} element
|
|
* @param {String} type
|
|
* @param {Function} handler
|
|
*/
|
|
off: function off(element, type, handler) {
|
|
element.removeEventListener(type, handler, false);
|
|
},
|
|
|
|
/**
|
|
* forEach over arrays and objects
|
|
* @method each
|
|
* @param {Object|Array} obj
|
|
* @param {Function} iterator
|
|
* @param {any} iterator.item
|
|
* @param {Number} iterator.index
|
|
* @param {Object|Array} iterator.obj the source object
|
|
* @param {Object} context value to use as `this` in the iterator
|
|
*/
|
|
each: function each(obj, iterator, context) {
|
|
var i, len;
|
|
|
|
// native forEach on arrays
|
|
if('forEach' in obj) {
|
|
obj.forEach(iterator, context);
|
|
// arrays
|
|
} else if(obj.length !== undefined) {
|
|
for(i = 0, len = obj.length; i < len; i++) {
|
|
if(iterator.call(context, obj[i], i, obj) === false) {
|
|
return;
|
|
}
|
|
}
|
|
// objects
|
|
} else {
|
|
for(i in obj) {
|
|
if(obj.hasOwnProperty(i) &&
|
|
iterator.call(context, obj[i], i, obj) === false) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* find if a string contains the string using indexOf
|
|
* @method inStr
|
|
* @param {String} src
|
|
* @param {String} find
|
|
* @return {Boolean} found
|
|
*/
|
|
inStr: function inStr(src, find) {
|
|
return src.indexOf(find) > -1;
|
|
},
|
|
|
|
/**
|
|
* find if a array contains the object using indexOf or a simple polyfill
|
|
* @method inArray
|
|
* @param {String} src
|
|
* @param {String} find
|
|
* @return {Boolean|Number} false when not found, or the index
|
|
*/
|
|
inArray: function inArray(src, find) {
|
|
if(src.indexOf) {
|
|
var index = src.indexOf(find);
|
|
return (index === -1) ? false : index;
|
|
} else {
|
|
for(var i = 0, len = src.length; i < len; i++) {
|
|
if(src[i] === find) {
|
|
return i;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* convert an array-like object (`arguments`, `touchlist`) to an array
|
|
* @method toArray
|
|
* @param {Object} obj
|
|
* @return {Array}
|
|
*/
|
|
toArray: function toArray(obj) {
|
|
return Array.prototype.slice.call(obj, 0);
|
|
},
|
|
|
|
/**
|
|
* find if a node is in the given parent
|
|
* @method hasParent
|
|
* @param {HTMLElement} node
|
|
* @param {HTMLElement} parent
|
|
* @return {Boolean} found
|
|
*/
|
|
hasParent: function hasParent(node, parent) {
|
|
while(node) {
|
|
if(node == parent) {
|
|
return true;
|
|
}
|
|
node = node.parentNode;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* get the center of all the touches
|
|
* @method getCenter
|
|
* @param {Array} touches
|
|
* @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties
|
|
*/
|
|
getCenter: function getCenter(touches) {
|
|
var pageX = [],
|
|
pageY = [],
|
|
clientX = [],
|
|
clientY = [],
|
|
min = Math.min,
|
|
max = Math.max;
|
|
|
|
// no need to loop when only one touch
|
|
if(touches.length === 1) {
|
|
return {
|
|
pageX: touches[0].pageX,
|
|
pageY: touches[0].pageY,
|
|
clientX: touches[0].clientX,
|
|
clientY: touches[0].clientY
|
|
};
|
|
}
|
|
|
|
Utils.each(touches, function(touch) {
|
|
pageX.push(touch.pageX);
|
|
pageY.push(touch.pageY);
|
|
clientX.push(touch.clientX);
|
|
clientY.push(touch.clientY);
|
|
});
|
|
|
|
return {
|
|
pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
|
|
pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
|
|
clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
|
|
clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
|
|
};
|
|
},
|
|
|
|
/**
|
|
* calculate the velocity between two points. unit is in px per ms.
|
|
* @method getVelocity
|
|
* @param {Number} deltaTime
|
|
* @param {Number} deltaX
|
|
* @param {Number} deltaY
|
|
* @return {Object} velocity `x` and `y`
|
|
*/
|
|
getVelocity: function getVelocity(deltaTime, deltaX, deltaY) {
|
|
return {
|
|
x: Math.abs(deltaX / deltaTime) || 0,
|
|
y: Math.abs(deltaY / deltaTime) || 0
|
|
};
|
|
},
|
|
|
|
/**
|
|
* calculate the angle between two coordinates
|
|
* @method getAngle
|
|
* @param {Touch} touch1
|
|
* @param {Touch} touch2
|
|
* @return {Number} angle
|
|
*/
|
|
getAngle: function getAngle(touch1, touch2) {
|
|
var x = touch2.clientX - touch1.clientX,
|
|
y = touch2.clientY - touch1.clientY;
|
|
|
|
return Math.atan2(y, x) * 180 / Math.PI;
|
|
},
|
|
|
|
/**
|
|
* do a small comparision to get the direction between two touches.
|
|
* @method getDirection
|
|
* @param {Touch} touch1
|
|
* @param {Touch} touch2
|
|
* @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN`
|
|
*/
|
|
getDirection: function getDirection(touch1, touch2) {
|
|
var x = Math.abs(touch1.clientX - touch2.clientX),
|
|
y = Math.abs(touch1.clientY - touch2.clientY);
|
|
|
|
if(x >= y) {
|
|
return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
|
|
}
|
|
return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
|
|
},
|
|
|
|
/**
|
|
* calculate the distance between two touches
|
|
* @method getDistance
|
|
* @param {Touch}touch1
|
|
* @param {Touch} touch2
|
|
* @return {Number} distance
|
|
*/
|
|
getDistance: function getDistance(touch1, touch2) {
|
|
var x = touch2.clientX - touch1.clientX,
|
|
y = touch2.clientY - touch1.clientY;
|
|
|
|
return Math.sqrt((x * x) + (y * y));
|
|
},
|
|
|
|
/**
|
|
* calculate the scale factor between two touchLists
|
|
* no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
|
|
* @method getScale
|
|
* @param {Array} start array of touches
|
|
* @param {Array} end array of touches
|
|
* @return {Number} scale
|
|
*/
|
|
getScale: function getScale(start, end) {
|
|
// need two fingers...
|
|
if(start.length >= 2 && end.length >= 2) {
|
|
return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
|
|
}
|
|
return 1;
|
|
},
|
|
|
|
/**
|
|
* calculate the rotation degrees between two touchLists
|
|
* @method getRotation
|
|
* @param {Array} start array of touches
|
|
* @param {Array} end array of touches
|
|
* @return {Number} rotation
|
|
*/
|
|
getRotation: function getRotation(start, end) {
|
|
// need two fingers
|
|
if(start.length >= 2 && end.length >= 2) {
|
|
return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
|
|
}
|
|
return 0;
|
|
},
|
|
|
|
/**
|
|
* find out if the direction is vertical *
|
|
* @method isVertical
|
|
* @param {String} direction matches `DIRECTION_UP|DOWN`
|
|
* @return {Boolean} is_vertical
|
|
*/
|
|
isVertical: function isVertical(direction) {
|
|
return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
|
|
},
|
|
|
|
/**
|
|
* set css properties with their prefixes
|
|
* @param {HTMLElement} element
|
|
* @param {String} prop
|
|
* @param {String} value
|
|
* @param {Boolean} [toggle=true]
|
|
* @return {Boolean}
|
|
*/
|
|
setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) {
|
|
var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms'];
|
|
prop = Utils.toCamelCase(prop);
|
|
|
|
for(var i = 0; i < prefixes.length; i++) {
|
|
var p = prop;
|
|
// prefixes
|
|
if(prefixes[i]) {
|
|
p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1);
|
|
}
|
|
|
|
// test the style
|
|
if(p in element.style) {
|
|
element.style[p] = (toggle == null || toggle) && value || '';
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* toggle browser default behavior by setting css properties.
|
|
* `userSelect='none'` also sets `element.onselectstart` to false
|
|
* `userDrag='none'` also sets `element.ondragstart` to false
|
|
*
|
|
* @method toggleBehavior
|
|
* @param {HtmlElement} element
|
|
* @param {Object} props
|
|
* @param {Boolean} [toggle=true]
|
|
*/
|
|
toggleBehavior: function toggleBehavior(element, props, toggle) {
|
|
if(!props || !element || !element.style) {
|
|
return;
|
|
}
|
|
|
|
// set the css properties
|
|
Utils.each(props, function(value, prop) {
|
|
Utils.setPrefixedCss(element, prop, value, toggle);
|
|
});
|
|
|
|
var falseFn = toggle && function() {
|
|
return false;
|
|
};
|
|
|
|
// also the disable onselectstart
|
|
if(props.userSelect == 'none') {
|
|
element.onselectstart = falseFn;
|
|
}
|
|
// and disable ondragstart
|
|
if(props.userDrag == 'none') {
|
|
element.ondragstart = falseFn;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* convert a string with underscores to camelCase
|
|
* so prevent_default becomes preventDefault
|
|
* @param {String} str
|
|
* @return {String} camelCaseStr
|
|
*/
|
|
toCamelCase: function toCamelCase(str) {
|
|
return str.replace(/[_-]([a-z])/g, function(s) {
|
|
return s[1].toUpperCase();
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @module hammer
|
|
*/
|
|
/**
|
|
* @class Event
|
|
* @static
|
|
*/
|
|
var Event = Hammer.event = {
|
|
/**
|
|
* when touch events have been fired, this is true
|
|
* this is used to stop mouse events
|
|
* @property prevent_mouseevents
|
|
* @private
|
|
* @type {Boolean}
|
|
*/
|
|
preventMouseEvents: false,
|
|
|
|
/**
|
|
* if EVENT_START has been fired
|
|
* @property started
|
|
* @private
|
|
* @type {Boolean}
|
|
*/
|
|
started: false,
|
|
|
|
/**
|
|
* when the mouse is hold down, this is true
|
|
* @property should_detect
|
|
* @private
|
|
* @type {Boolean}
|
|
*/
|
|
shouldDetect: false,
|
|
|
|
/**
|
|
* simple event binder with a hook and support for multiple types
|
|
* @method on
|
|
* @param {HTMLElement} element
|
|
* @param {String} type
|
|
* @param {Function} handler
|
|
* @param {Function} [hook]
|
|
* @param {Object} hook.type
|
|
*/
|
|
on: function on(element, type, handler, hook) {
|
|
var types = type.split(' ');
|
|
Utils.each(types, function(type) {
|
|
Utils.on(element, type, handler);
|
|
hook && hook(type);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* simple event unbinder with a hook and support for multiple types
|
|
* @method off
|
|
* @param {HTMLElement} element
|
|
* @param {String} type
|
|
* @param {Function} handler
|
|
* @param {Function} [hook]
|
|
* @param {Object} hook.type
|
|
*/
|
|
off: function off(element, type, handler, hook) {
|
|
var types = type.split(' ');
|
|
Utils.each(types, function(type) {
|
|
Utils.off(element, type, handler);
|
|
hook && hook(type);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* the core touch event handler.
|
|
* this finds out if we should to detect gestures
|
|
* @method onTouch
|
|
* @param {HTMLElement} element
|
|
* @param {String} eventType matches `EVENT_START|MOVE|END`
|
|
* @param {Function} handler
|
|
* @return onTouchHandler {Function} the core event handler
|
|
*/
|
|
onTouch: function onTouch(element, eventType, handler) {
|
|
var self = this;
|
|
|
|
var onTouchHandler = function onTouchHandler(ev) {
|
|
var srcType = ev.type.toLowerCase(),
|
|
isPointer = Hammer.HAS_POINTEREVENTS,
|
|
isMouse = Utils.inStr(srcType, 'mouse'),
|
|
triggerType;
|
|
|
|
// if we are in a mouseevent, but there has been a touchevent triggered in this session
|
|
// we want to do nothing. simply break out of the event.
|
|
if(isMouse && self.preventMouseEvents) {
|
|
return;
|
|
|
|
// mousebutton must be down
|
|
} else if(isMouse && eventType == EVENT_START && ev.button === 0) {
|
|
self.preventMouseEvents = false;
|
|
self.shouldDetect = true;
|
|
} else if(isPointer && eventType == EVENT_START) {
|
|
self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev));
|
|
// just a valid start event, but no mouse
|
|
} else if(!isMouse && eventType == EVENT_START) {
|
|
self.preventMouseEvents = true;
|
|
self.shouldDetect = true;
|
|
}
|
|
|
|
// update the pointer event before entering the detection
|
|
if(isPointer && eventType != EVENT_END) {
|
|
PointerEvent.updatePointer(eventType, ev);
|
|
}
|
|
|
|
// we are in a touch/down state, so allowed detection of gestures
|
|
if(self.shouldDetect) {
|
|
triggerType = self.doDetect.call(self, ev, eventType, element, handler);
|
|
}
|
|
|
|
// ...and we are done with the detection
|
|
// so reset everything to start each detection totally fresh
|
|
if(triggerType == EVENT_END) {
|
|
self.preventMouseEvents = false;
|
|
self.shouldDetect = false;
|
|
PointerEvent.reset();
|
|
// update the pointerevent object after the detection
|
|
}
|
|
|
|
if(isPointer && eventType == EVENT_END) {
|
|
PointerEvent.updatePointer(eventType, ev);
|
|
}
|
|
};
|
|
|
|
this.on(element, EVENT_TYPES[eventType], onTouchHandler);
|
|
return onTouchHandler;
|
|
},
|
|
|
|
/**
|
|
* the core detection method
|
|
* this finds out what hammer-touch-events to trigger
|
|
* @method doDetect
|
|
* @param {Object} ev
|
|
* @param {String} eventType matches `EVENT_START|MOVE|END`
|
|
* @param {HTMLElement} element
|
|
* @param {Function} handler
|
|
* @return {String} triggerType matches `EVENT_START|MOVE|END`
|
|
*/
|
|
doDetect: function doDetect(ev, eventType, element, handler) {
|
|
var touchList = this.getTouchList(ev, eventType);
|
|
var touchListLength = touchList.length;
|
|
var triggerType = eventType;
|
|
var triggerChange = touchList.trigger; // used by fakeMultitouch plugin
|
|
var changedLength = touchListLength;
|
|
|
|
// at each touchstart-like event we want also want to trigger a TOUCH event...
|
|
if(eventType == EVENT_START) {
|
|
triggerChange = EVENT_TOUCH;
|
|
// ...the same for a touchend-like event
|
|
} else if(eventType == EVENT_END) {
|
|
triggerChange = EVENT_RELEASE;
|
|
|
|
// keep track of how many touches have been removed
|
|
changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1);
|
|
}
|
|
|
|
// after there are still touches on the screen,
|
|
// we just want to trigger a MOVE event. so change the START or END to a MOVE
|
|
// but only after detection has been started, the first time we actualy want a START
|
|
if(changedLength > 0 && this.started) {
|
|
triggerType = EVENT_MOVE;
|
|
}
|
|
|
|
// detection has been started, we keep track of this, see above
|
|
this.started = true;
|
|
|
|
// generate some event data, some basic information
|
|
var evData = this.collectEventData(element, triggerType, touchList, ev);
|
|
|
|
// trigger the triggerType event before the change (TOUCH, RELEASE) events
|
|
// but the END event should be at last
|
|
if(eventType != EVENT_END) {
|
|
handler.call(Detection, evData);
|
|
}
|
|
|
|
// trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed
|
|
if(triggerChange) {
|
|
evData.changedLength = changedLength;
|
|
evData.eventType = triggerChange;
|
|
|
|
handler.call(Detection, evData);
|
|
|
|
evData.eventType = triggerType;
|
|
delete evData.changedLength;
|
|
}
|
|
|
|
// trigger the END event
|
|
if(triggerType == EVENT_END) {
|
|
handler.call(Detection, evData);
|
|
|
|
// ...and we are done with the detection
|
|
// so reset everything to start each detection totally fresh
|
|
this.started = false;
|
|
}
|
|
|
|
return triggerType;
|
|
},
|
|
|
|
/**
|
|
* we have different events for each device/browser
|
|
* determine what we need and set them in the EVENT_TYPES constant
|
|
* the `onTouch` method is bind to these properties.
|
|
* @method determineEventTypes
|
|
* @return {Object} events
|
|
*/
|
|
determineEventTypes: function determineEventTypes() {
|
|
var types;
|
|
if(Hammer.HAS_POINTEREVENTS) {
|
|
if(window.PointerEvent) {
|
|
types = [
|
|
'pointerdown',
|
|
'pointermove',
|
|
'pointerup pointercancel lostpointercapture'
|
|
];
|
|
} else {
|
|
types = [
|
|
'MSPointerDown',
|
|
'MSPointerMove',
|
|
'MSPointerUp MSPointerCancel MSLostPointerCapture'
|
|
];
|
|
}
|
|
} else if(Hammer.NO_MOUSEEVENTS) {
|
|
types = [
|
|
'touchstart',
|
|
'touchmove',
|
|
'touchend touchcancel'
|
|
];
|
|
} else {
|
|
types = [
|
|
'touchstart mousedown',
|
|
'touchmove mousemove',
|
|
'touchend touchcancel mouseup'
|
|
];
|
|
}
|
|
|
|
EVENT_TYPES[EVENT_START] = types[0];
|
|
EVENT_TYPES[EVENT_MOVE] = types[1];
|
|
EVENT_TYPES[EVENT_END] = types[2];
|
|
return EVENT_TYPES;
|
|
},
|
|
|
|
/**
|
|
* create touchList depending on the event
|
|
* @method getTouchList
|
|
* @param {Object} ev
|
|
* @param {String} eventType
|
|
* @return {Array} touches
|
|
*/
|
|
getTouchList: function getTouchList(ev, eventType) {
|
|
// get the fake pointerEvent touchlist
|
|
if(Hammer.HAS_POINTEREVENTS) {
|
|
return PointerEvent.getTouchList();
|
|
}
|
|
|
|
// get the touchlist
|
|
if(ev.touches) {
|
|
if(eventType == EVENT_MOVE) {
|
|
return ev.touches;
|
|
}
|
|
|
|
var identifiers = [];
|
|
var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches));
|
|
var touchList = [];
|
|
|
|
Utils.each(concat, function(touch) {
|
|
if(Utils.inArray(identifiers, touch.identifier) === false) {
|
|
touchList.push(touch);
|
|
}
|
|
identifiers.push(touch.identifier);
|
|
});
|
|
|
|
return touchList;
|
|
}
|
|
|
|
// make fake touchList from mouse position
|
|
ev.identifier = 1;
|
|
return [ev];
|
|
},
|
|
|
|
/**
|
|
* collect basic event data
|
|
* @method collectEventData
|
|
* @param {HTMLElement} element
|
|
* @param {String} eventType matches `EVENT_START|MOVE|END`
|
|
* @param {Array} touches
|
|
* @param {Object} ev
|
|
* @return {Object} ev
|
|
*/
|
|
collectEventData: function collectEventData(element, eventType, touches, ev) {
|
|
// find out pointerType
|
|
var pointerType = POINTER_TOUCH;
|
|
if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) {
|
|
pointerType = POINTER_MOUSE;
|
|
} else if(PointerEvent.matchType(POINTER_PEN, ev)) {
|
|
pointerType = POINTER_PEN;
|
|
}
|
|
|
|
return {
|
|
center: Utils.getCenter(touches),
|
|
timeStamp: Date.now(),
|
|
target: ev.target,
|
|
touches: touches,
|
|
eventType: eventType,
|
|
pointerType: pointerType,
|
|
srcEvent: ev,
|
|
|
|
/**
|
|
* prevent the browser default actions
|
|
* mostly used to disable scrolling of the browser
|
|
*/
|
|
preventDefault: function() {
|
|
var srcEvent = this.srcEvent;
|
|
srcEvent.preventManipulation && srcEvent.preventManipulation();
|
|
srcEvent.preventDefault && srcEvent.preventDefault();
|
|
},
|
|
|
|
/**
|
|
* stop bubbling the event up to its parents
|
|
*/
|
|
stopPropagation: function() {
|
|
this.srcEvent.stopPropagation();
|
|
},
|
|
|
|
/**
|
|
* immediately stop gesture detection
|
|
* might be useful after a swipe was detected
|
|
* @return {*}
|
|
*/
|
|
stopDetect: function() {
|
|
return Detection.stopDetect();
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @module hammer
|
|
*
|
|
* @class PointerEvent
|
|
* @static
|
|
*/
|
|
var PointerEvent = Hammer.PointerEvent = {
|
|
/**
|
|
* holds all pointers, by `identifier`
|
|
* @property pointers
|
|
* @type {Object}
|
|
*/
|
|
pointers: {},
|
|
|
|
/**
|
|
* get the pointers as an array
|
|
* @method getTouchList
|
|
* @return {Array} touchlist
|
|
*/
|
|
getTouchList: function getTouchList() {
|
|
var touchlist = [];
|
|
// we can use forEach since pointerEvents only is in IE10
|
|
Utils.each(this.pointers, function(pointer) {
|
|
touchlist.push(pointer);
|
|
});
|
|
return touchlist;
|
|
},
|
|
|
|
/**
|
|
* update the position of a pointer
|
|
* @method updatePointer
|
|
* @param {String} eventType matches `EVENT_START|MOVE|END`
|
|
* @param {Object} pointerEvent
|
|
*/
|
|
updatePointer: function updatePointer(eventType, pointerEvent) {
|
|
if(eventType == EVENT_END || (eventType != EVENT_END && pointerEvent.buttons !== 1)) {
|
|
delete this.pointers[pointerEvent.pointerId];
|
|
} else {
|
|
pointerEvent.identifier = pointerEvent.pointerId;
|
|
this.pointers[pointerEvent.pointerId] = pointerEvent;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* check if ev matches pointertype
|
|
* @method matchType
|
|
* @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN`
|
|
* @param {PointerEvent} ev
|
|
*/
|
|
matchType: function matchType(pointerType, ev) {
|
|
if(!ev.pointerType) {
|
|
return false;
|
|
}
|
|
|
|
var pt = ev.pointerType,
|
|
types = {};
|
|
|
|
types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE));
|
|
types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH));
|
|
types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN));
|
|
return types[pointerType];
|
|
},
|
|
|
|
/**
|
|
* reset the stored pointers
|
|
* @method reset
|
|
*/
|
|
reset: function resetList() {
|
|
this.pointers = {};
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @module hammer
|
|
*
|
|
* @class Detection
|
|
* @static
|
|
*/
|
|
var Detection = Hammer.detection = {
|
|
// contains all registred Hammer.gestures in the correct order
|
|
gestures: [],
|
|
|
|
// data of the current Hammer.gesture detection session
|
|
current: null,
|
|
|
|
// the previous Hammer.gesture session data
|
|
// is a full clone of the previous gesture.current object
|
|
previous: null,
|
|
|
|
// when this becomes true, no gestures are fired
|
|
stopped: false,
|
|
|
|
/**
|
|
* start Hammer.gesture detection
|
|
* @method startDetect
|
|
* @param {Hammer.Instance} inst
|
|
* @param {Object} eventData
|
|
*/
|
|
startDetect: function startDetect(inst, eventData) {
|
|
// already busy with a Hammer.gesture detection on an element
|
|
if(this.current) {
|
|
return;
|
|
}
|
|
|
|
this.stopped = false;
|
|
|
|
// holds current session
|
|
this.current = {
|
|
inst: inst, // reference to HammerInstance we're working for
|
|
startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc
|
|
lastEvent: false, // last eventData
|
|
lastCalcEvent: false, // last eventData for calculations.
|
|
futureCalcEvent: false, // last eventData for calculations.
|
|
lastCalcData: {}, // last lastCalcData
|
|
name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
|
|
};
|
|
|
|
this.detect(eventData);
|
|
},
|
|
|
|
/**
|
|
* Hammer.gesture detection
|
|
* @method detect
|
|
* @param {Object} eventData
|
|
* @return {any}
|
|
*/
|
|
detect: function detect(eventData) {
|
|
if(!this.current || this.stopped) {
|
|
return;
|
|
}
|
|
|
|
// extend event data with calculations about scale, distance etc
|
|
eventData = this.extendEventData(eventData);
|
|
|
|
// hammer instance and instance options
|
|
var inst = this.current.inst,
|
|
instOptions = inst.options;
|
|
|
|
// call Hammer.gesture handlers
|
|
Utils.each(this.gestures, function triggerGesture(gesture) {
|
|
// only when the instance options have enabled this gesture
|
|
if(!this.stopped && inst.enabled && instOptions[gesture.name]) {
|
|
gesture.handler.call(gesture, eventData, inst);
|
|
}
|
|
}, this);
|
|
|
|
// store as previous event event
|
|
if(this.current) {
|
|
this.current.lastEvent = eventData;
|
|
}
|
|
|
|
if(eventData.eventType == EVENT_END) {
|
|
this.stopDetect();
|
|
}
|
|
|
|
return eventData;
|
|
},
|
|
|
|
/**
|
|
* clear the Hammer.gesture vars
|
|
* this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
|
|
* to stop other Hammer.gestures from being fired
|
|
* @method stopDetect
|
|
*/
|
|
stopDetect: function stopDetect() {
|
|
// clone current data to the store as the previous gesture
|
|
// used for the double tap gesture, since this is an other gesture detect session
|
|
this.previous = Utils.extend({}, this.current);
|
|
|
|
// reset the current
|
|
this.current = null;
|
|
this.stopped = true;
|
|
},
|
|
|
|
/**
|
|
* calculate velocity, angle and direction
|
|
* @method getVelocityData
|
|
* @param {Object} ev
|
|
* @param {Object} center
|
|
* @param {Number} deltaTime
|
|
* @param {Number} deltaX
|
|
* @param {Number} deltaY
|
|
*/
|
|
getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) {
|
|
var cur = this.current,
|
|
recalc = false,
|
|
calcEv = cur.lastCalcEvent,
|
|
calcData = cur.lastCalcData;
|
|
|
|
if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) {
|
|
center = calcEv.center;
|
|
deltaTime = ev.timeStamp - calcEv.timeStamp;
|
|
deltaX = ev.center.clientX - calcEv.center.clientX;
|
|
deltaY = ev.center.clientY - calcEv.center.clientY;
|
|
recalc = true;
|
|
}
|
|
|
|
if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
|
|
cur.futureCalcEvent = ev;
|
|
}
|
|
|
|
if(!cur.lastCalcEvent || recalc) {
|
|
calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY);
|
|
calcData.angle = Utils.getAngle(center, ev.center);
|
|
calcData.direction = Utils.getDirection(center, ev.center);
|
|
|
|
cur.lastCalcEvent = cur.futureCalcEvent || ev;
|
|
cur.futureCalcEvent = ev;
|
|
}
|
|
|
|
ev.velocityX = calcData.velocity.x;
|
|
ev.velocityY = calcData.velocity.y;
|
|
ev.interimAngle = calcData.angle;
|
|
ev.interimDirection = calcData.direction;
|
|
},
|
|
|
|
/**
|
|
* extend eventData for Hammer.gestures
|
|
* @method extendEventData
|
|
* @param {Object} ev
|
|
* @return {Object} ev
|
|
*/
|
|
extendEventData: function extendEventData(ev) {
|
|
var cur = this.current,
|
|
startEv = cur.startEvent,
|
|
lastEv = cur.lastEvent || startEv;
|
|
|
|
// update the start touchlist to calculate the scale/rotation
|
|
if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
|
|
startEv.touches = [];
|
|
Utils.each(ev.touches, function(touch) {
|
|
startEv.touches.push({
|
|
clientX: touch.clientX,
|
|
clientY: touch.clientY
|
|
});
|
|
});
|
|
}
|
|
|
|
var deltaTime = ev.timeStamp - startEv.timeStamp,
|
|
deltaX = ev.center.clientX - startEv.center.clientX,
|
|
deltaY = ev.center.clientY - startEv.center.clientY;
|
|
|
|
this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY);
|
|
|
|
Utils.extend(ev, {
|
|
startEvent: startEv,
|
|
|
|
deltaTime: deltaTime,
|
|
deltaX: deltaX,
|
|
deltaY: deltaY,
|
|
|
|
distance: Utils.getDistance(startEv.center, ev.center),
|
|
angle: Utils.getAngle(startEv.center, ev.center),
|
|
direction: Utils.getDirection(startEv.center, ev.center),
|
|
scale: Utils.getScale(startEv.touches, ev.touches),
|
|
rotation: Utils.getRotation(startEv.touches, ev.touches)
|
|
});
|
|
|
|
return ev;
|
|
},
|
|
|
|
/**
|
|
* register new gesture
|
|
* @method register
|
|
* @param {Object} gesture object, see `gestures/` for documentation
|
|
* @return {Array} gestures
|
|
*/
|
|
register: function register(gesture) {
|
|
// add an enable gesture options if there is no given
|
|
var options = gesture.defaults || {};
|
|
if(options[gesture.name] === undefined) {
|
|
options[gesture.name] = true;
|
|
}
|
|
|
|
// extend Hammer default options with the Hammer.gesture options
|
|
Utils.extend(Hammer.defaults, options, true);
|
|
|
|
// set its index
|
|
gesture.index = gesture.index || 1000;
|
|
|
|
// add Hammer.gesture to the list
|
|
this.gestures.push(gesture);
|
|
|
|
// sort the list by index
|
|
this.gestures.sort(function(a, b) {
|
|
if(a.index < b.index) {
|
|
return -1;
|
|
}
|
|
if(a.index > b.index) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
return this.gestures;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @module hammer
|
|
*/
|
|
|
|
/**
|
|
* create new hammer instance
|
|
* all methods should return the instance itself, so it is chainable.
|
|
*
|
|
* @class Instance
|
|
* @constructor
|
|
* @param {HTMLElement} element
|
|
* @param {Object} [options={}] options are merged with `Hammer.defaults`
|
|
* @return {Hammer.Instance}
|
|
*/
|
|
Hammer.Instance = function(element, options) {
|
|
var self = this;
|
|
|
|
// setup HammerJS window events and register all gestures
|
|
// this also sets up the default options
|
|
setup();
|
|
|
|
/**
|
|
* @property element
|
|
* @type {HTMLElement}
|
|
*/
|
|
this.element = element;
|
|
|
|
/**
|
|
* @property enabled
|
|
* @type {Boolean}
|
|
* @protected
|
|
*/
|
|
this.enabled = true;
|
|
|
|
/**
|
|
* options, merged with the defaults
|
|
* options with an _ are converted to camelCase
|
|
* @property options
|
|
* @type {Object}
|
|
*/
|
|
Utils.each(options, function(value, name) {
|
|
delete options[name];
|
|
options[Utils.toCamelCase(name)] = value;
|
|
});
|
|
|
|
this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {});
|
|
|
|
// add some css to the element to prevent the browser from doing its native behavoir
|
|
if(this.options.behavior) {
|
|
Utils.toggleBehavior(this.element, this.options.behavior, true);
|
|
}
|
|
|
|
/**
|
|
* event start handler on the element to start the detection
|
|
* @property eventStartHandler
|
|
* @type {Object}
|
|
*/
|
|
this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) {
|
|
if(self.enabled && ev.eventType == EVENT_START) {
|
|
Detection.startDetect(self, ev);
|
|
} else if(ev.eventType == EVENT_TOUCH) {
|
|
Detection.detect(ev);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* keep a list of user event handlers which needs to be removed when calling 'dispose'
|
|
* @property eventHandlers
|
|
* @type {Array}
|
|
*/
|
|
this.eventHandlers = [];
|
|
};
|
|
|
|
Hammer.Instance.prototype = {
|
|
/**
|
|
* bind events to the instance
|
|
* @method on
|
|
* @chainable
|
|
* @param {String} gestures multiple gestures by splitting with a space
|
|
* @param {Function} handler
|
|
* @param {Object} handler.ev event object
|
|
*/
|
|
on: function onEvent(gestures, handler) {
|
|
var self = this;
|
|
Event.on(self.element, gestures, handler, function(type) {
|
|
self.eventHandlers.push({ gesture: type, handler: handler });
|
|
});
|
|
return self;
|
|
},
|
|
|
|
/**
|
|
* unbind events to the instance
|
|
* @method off
|
|
* @chainable
|
|
* @param {String} gestures
|
|
* @param {Function} handler
|
|
*/
|
|
off: function offEvent(gestures, handler) {
|
|
var self = this;
|
|
|
|
Event.off(self.element, gestures, handler, function(type) {
|
|
var index = Utils.inArray({ gesture: type, handler: handler });
|
|
if(index !== false) {
|
|
self.eventHandlers.splice(index, 1);
|
|
}
|
|
});
|
|
return self;
|
|
},
|
|
|
|
/**
|
|
* trigger gesture event
|
|
* @method trigger
|
|
* @chainable
|
|
* @param {String} gesture
|
|
* @param {Object} [eventData]
|
|
*/
|
|
trigger: function triggerEvent(gesture, eventData) {
|
|
// optional
|
|
if(!eventData) {
|
|
eventData = {};
|
|
}
|
|
|
|
// create DOM event
|
|
var event = Hammer.DOCUMENT.createEvent('Event');
|
|
event.initEvent(gesture, true, true);
|
|
event.gesture = eventData;
|
|
|
|
// trigger on the target if it is in the instance element,
|
|
// this is for event delegation tricks
|
|
var element = this.element;
|
|
if(Utils.hasParent(eventData.target, element)) {
|
|
element = eventData.target;
|
|
}
|
|
|
|
element.dispatchEvent(event);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* enable of disable hammer.js detection
|
|
* @method enable
|
|
* @chainable
|
|
* @param {Boolean} state
|
|
*/
|
|
enable: function enable(state) {
|
|
this.enabled = state;
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* dispose this hammer instance
|
|
* @method dispose
|
|
* @return {Null}
|
|
*/
|
|
dispose: function dispose() {
|
|
var i, eh;
|
|
|
|
// undo all changes made by stop_browser_behavior
|
|
Utils.toggleBehavior(this.element, this.options.behavior, false);
|
|
|
|
// unbind all custom event handlers
|
|
for(i = -1; (eh = this.eventHandlers[++i]);) {
|
|
Utils.off(this.element, eh.gesture, eh.handler);
|
|
}
|
|
|
|
this.eventHandlers = [];
|
|
|
|
// unbind the start event listener
|
|
Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler);
|
|
|
|
return null;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @module gestures
|
|
*/
|
|
/**
|
|
* Move with x fingers (default 1) around on the page.
|
|
* Preventing the default browser behavior is a good way to improve feel and working.
|
|
* ````
|
|
* hammertime.on("drag", function(ev) {
|
|
* console.log(ev);
|
|
* ev.gesture.preventDefault();
|
|
* });
|
|
* ````
|
|
*
|
|
* @class Drag
|
|
* @static
|
|
*/
|
|
/**
|
|
* @event drag
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event dragstart
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event dragend
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event drapleft
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event dragright
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event dragup
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event dragdown
|
|
* @param {Object} ev
|
|
*/
|
|
|
|
/**
|
|
* @param {String} name
|
|
*/
|
|
(function(name) {
|
|
var triggered = false;
|
|
|
|
function dragGesture(ev, inst) {
|
|
var cur = Detection.current;
|
|
|
|
// max touches
|
|
if(inst.options.dragMaxTouches > 0 &&
|
|
ev.touches.length > inst.options.dragMaxTouches) {
|
|
return;
|
|
}
|
|
|
|
switch(ev.eventType) {
|
|
case EVENT_START:
|
|
triggered = false;
|
|
break;
|
|
|
|
case EVENT_MOVE:
|
|
// when the distance we moved is too small we skip this gesture
|
|
// or we can be already in dragging
|
|
if(ev.distance < inst.options.dragMinDistance &&
|
|
cur.name != name) {
|
|
return;
|
|
}
|
|
|
|
var startCenter = cur.startEvent.center;
|
|
|
|
// we are dragging!
|
|
if(cur.name != name) {
|
|
cur.name = name;
|
|
if(inst.options.dragDistanceCorrection && ev.distance > 0) {
|
|
// When a drag is triggered, set the event center to dragMinDistance pixels from the original event center.
|
|
// Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0.
|
|
// It might be useful to save the original start point somewhere
|
|
var factor = Math.abs(inst.options.dragMinDistance / ev.distance);
|
|
startCenter.pageX += ev.deltaX * factor;
|
|
startCenter.pageY += ev.deltaY * factor;
|
|
startCenter.clientX += ev.deltaX * factor;
|
|
startCenter.clientY += ev.deltaY * factor;
|
|
|
|
// recalculate event data using new start point
|
|
ev = Detection.extendEventData(ev);
|
|
}
|
|
}
|
|
|
|
// lock drag to axis?
|
|
if(cur.lastEvent.dragLockToAxis ||
|
|
( inst.options.dragLockToAxis &&
|
|
inst.options.dragLockMinDistance <= ev.distance
|
|
)) {
|
|
ev.dragLockToAxis = true;
|
|
}
|
|
|
|
// keep direction on the axis that the drag gesture started on
|
|
var lastDirection = cur.lastEvent.direction;
|
|
if(ev.dragLockToAxis && lastDirection !== ev.direction) {
|
|
if(Utils.isVertical(lastDirection)) {
|
|
ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN;
|
|
} else {
|
|
ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
|
|
}
|
|
}
|
|
|
|
// first time, trigger dragstart event
|
|
if(!triggered) {
|
|
inst.trigger(name + 'start', ev);
|
|
triggered = true;
|
|
}
|
|
|
|
// trigger events
|
|
inst.trigger(name, ev);
|
|
inst.trigger(name + ev.direction, ev);
|
|
|
|
var isVertical = Utils.isVertical(ev.direction);
|
|
|
|
// block the browser events
|
|
if((inst.options.dragBlockVertical && isVertical) ||
|
|
(inst.options.dragBlockHorizontal && !isVertical)) {
|
|
ev.preventDefault();
|
|
}
|
|
break;
|
|
|
|
case EVENT_RELEASE:
|
|
if(triggered && ev.changedLength <= inst.options.dragMaxTouches) {
|
|
inst.trigger(name + 'end', ev);
|
|
triggered = false;
|
|
}
|
|
break;
|
|
|
|
case EVENT_END:
|
|
triggered = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Hammer.gestures.Drag = {
|
|
name: name,
|
|
index: 50,
|
|
handler: dragGesture,
|
|
defaults: {
|
|
/**
|
|
* minimal movement that have to be made before the drag event gets triggered
|
|
* @property dragMinDistance
|
|
* @type {Number}
|
|
* @default 10
|
|
*/
|
|
dragMinDistance: 10,
|
|
|
|
/**
|
|
* Set dragDistanceCorrection to true to make the starting point of the drag
|
|
* be calculated from where the drag was triggered, not from where the touch started.
|
|
* Useful to avoid a jerk-starting drag, which can make fine-adjustments
|
|
* through dragging difficult, and be visually unappealing.
|
|
* @property dragDistanceCorrection
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
dragDistanceCorrection: true,
|
|
|
|
/**
|
|
* set 0 for unlimited, but this can conflict with transform
|
|
* @property dragMaxTouches
|
|
* @type {Number}
|
|
* @default 1
|
|
*/
|
|
dragMaxTouches: 1,
|
|
|
|
/**
|
|
* prevent default browser behavior when dragging occurs
|
|
* be careful with it, it makes the element a blocking element
|
|
* when you are using the drag gesture, it is a good practice to set this true
|
|
* @property dragBlockHorizontal
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
dragBlockHorizontal: false,
|
|
|
|
/**
|
|
* same as `dragBlockHorizontal`, but for vertical movement
|
|
* @property dragBlockVertical
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
dragBlockVertical: false,
|
|
|
|
/**
|
|
* dragLockToAxis keeps the drag gesture on the axis that it started on,
|
|
* It disallows vertical directions if the initial direction was horizontal, and vice versa.
|
|
* @property dragLockToAxis
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
dragLockToAxis: false,
|
|
|
|
/**
|
|
* drag lock only kicks in when distance > dragLockMinDistance
|
|
* This way, locking occurs only when the distance has become large enough to reliably determine the direction
|
|
* @property dragLockMinDistance
|
|
* @type {Number}
|
|
* @default 25
|
|
*/
|
|
dragLockMinDistance: 25
|
|
}
|
|
};
|
|
})('drag');
|
|
|
|
/**
|
|
* @module gestures
|
|
*/
|
|
/**
|
|
* trigger a simple gesture event, so you can do anything in your handler.
|
|
* only usable if you know what your doing...
|
|
*
|
|
* @class Gesture
|
|
* @static
|
|
*/
|
|
/**
|
|
* @event gesture
|
|
* @param {Object} ev
|
|
*/
|
|
Hammer.gestures.Gesture = {
|
|
name: 'gesture',
|
|
index: 1337,
|
|
handler: function releaseGesture(ev, inst) {
|
|
inst.trigger(this.name, ev);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @module gestures
|
|
*/
|
|
/**
|
|
* Touch stays at the same place for x time
|
|
*
|
|
* @class Hold
|
|
* @static
|
|
*/
|
|
/**
|
|
* @event hold
|
|
* @param {Object} ev
|
|
*/
|
|
|
|
/**
|
|
* @param {String} name
|
|
*/
|
|
(function(name) {
|
|
var timer;
|
|
|
|
function holdGesture(ev, inst) {
|
|
var options = inst.options,
|
|
current = Detection.current;
|
|
|
|
switch(ev.eventType) {
|
|
case EVENT_START:
|
|
clearTimeout(timer);
|
|
|
|
// set the gesture so we can check in the timeout if it still is
|
|
current.name = name;
|
|
|
|
// set timer and if after the timeout it still is hold,
|
|
// we trigger the hold event
|
|
timer = setTimeout(function() {
|
|
if(current && current.name == name) {
|
|
inst.trigger(name, ev);
|
|
}
|
|
}, options.holdTimeout);
|
|
break;
|
|
|
|
case EVENT_MOVE:
|
|
if(ev.distance > options.holdThreshold) {
|
|
clearTimeout(timer);
|
|
}
|
|
break;
|
|
|
|
case EVENT_RELEASE:
|
|
clearTimeout(timer);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Hammer.gestures.Hold = {
|
|
name: name,
|
|
index: 10,
|
|
defaults: {
|
|
/**
|
|
* @property holdTimeout
|
|
* @type {Number}
|
|
* @default 500
|
|
*/
|
|
holdTimeout: 500,
|
|
|
|
/**
|
|
* movement allowed while holding
|
|
* @property holdThreshold
|
|
* @type {Number}
|
|
* @default 2
|
|
*/
|
|
holdThreshold: 2
|
|
},
|
|
handler: holdGesture
|
|
};
|
|
})('hold');
|
|
|
|
/**
|
|
* @module gestures
|
|
*/
|
|
/**
|
|
* when a touch is being released from the page
|
|
*
|
|
* @class Release
|
|
* @static
|
|
*/
|
|
/**
|
|
* @event release
|
|
* @param {Object} ev
|
|
*/
|
|
Hammer.gestures.Release = {
|
|
name: 'release',
|
|
index: Infinity,
|
|
handler: function releaseGesture(ev, inst) {
|
|
if(ev.eventType == EVENT_RELEASE) {
|
|
inst.trigger(this.name, ev);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @module gestures
|
|
*/
|
|
/**
|
|
* triggers swipe events when the end velocity is above the threshold
|
|
* for best usage, set `preventDefault` (on the drag gesture) to `true`
|
|
* ````
|
|
* hammertime.on("dragleft swipeleft", function(ev) {
|
|
* console.log(ev);
|
|
* ev.gesture.preventDefault();
|
|
* });
|
|
* ````
|
|
*
|
|
* @class Swipe
|
|
* @static
|
|
*/
|
|
/**
|
|
* @event swipe
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event swipeleft
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event swiperight
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event swipeup
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event swipedown
|
|
* @param {Object} ev
|
|
*/
|
|
Hammer.gestures.Swipe = {
|
|
name: 'swipe',
|
|
index: 40,
|
|
defaults: {
|
|
/**
|
|
* @property swipeMinTouches
|
|
* @type {Number}
|
|
* @default 1
|
|
*/
|
|
swipeMinTouches: 1,
|
|
|
|
/**
|
|
* @property swipeMaxTouches
|
|
* @type {Number}
|
|
* @default 1
|
|
*/
|
|
swipeMaxTouches: 1,
|
|
|
|
/**
|
|
* horizontal swipe velocity
|
|
* @property swipeVelocityX
|
|
* @type {Number}
|
|
* @default 0.6
|
|
*/
|
|
swipeVelocityX: 0.6,
|
|
|
|
/**
|
|
* vertical swipe velocity
|
|
* @property swipeVelocityY
|
|
* @type {Number}
|
|
* @default 0.6
|
|
*/
|
|
swipeVelocityY: 0.6
|
|
},
|
|
|
|
handler: function swipeGesture(ev, inst) {
|
|
if(ev.eventType == EVENT_RELEASE) {
|
|
var touches = ev.touches.length,
|
|
options = inst.options;
|
|
|
|
// max touches
|
|
if(touches < options.swipeMinTouches ||
|
|
touches > options.swipeMaxTouches) {
|
|
return;
|
|
}
|
|
|
|
// when the distance we moved is too small we skip this gesture
|
|
// or we can be already in dragging
|
|
if(ev.velocityX > options.swipeVelocityX ||
|
|
ev.velocityY > options.swipeVelocityY) {
|
|
// trigger swipe events
|
|
inst.trigger(this.name, ev);
|
|
inst.trigger(this.name + ev.direction, ev);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @module gestures
|
|
*/
|
|
/**
|
|
* Single tap and a double tap on a place
|
|
*
|
|
* @class Tap
|
|
* @static
|
|
*/
|
|
/**
|
|
* @event tap
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event doubletap
|
|
* @param {Object} ev
|
|
*/
|
|
|
|
/**
|
|
* @param {String} name
|
|
*/
|
|
(function(name) {
|
|
var hasMoved = false;
|
|
|
|
function tapGesture(ev, inst) {
|
|
var options = inst.options,
|
|
current = Detection.current,
|
|
prev = Detection.previous,
|
|
sincePrev,
|
|
didDoubleTap;
|
|
|
|
switch(ev.eventType) {
|
|
case EVENT_START:
|
|
hasMoved = false;
|
|
break;
|
|
|
|
case EVENT_MOVE:
|
|
hasMoved = hasMoved || (ev.distance > options.tapMaxDistance);
|
|
break;
|
|
|
|
case EVENT_END:
|
|
if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) {
|
|
// previous gesture, for the double tap since these are two different gesture detections
|
|
sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp;
|
|
didDoubleTap = false;
|
|
|
|
// check if double tap
|
|
if(prev && prev.name == name &&
|
|
(sincePrev && sincePrev < options.doubleTapInterval) &&
|
|
ev.distance < options.doubleTapDistance) {
|
|
inst.trigger('doubletap', ev);
|
|
didDoubleTap = true;
|
|
}
|
|
|
|
// do a single tap
|
|
if(!didDoubleTap || options.tapAlways) {
|
|
current.name = name;
|
|
inst.trigger(current.name, ev);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Hammer.gestures.Tap = {
|
|
name: name,
|
|
index: 100,
|
|
handler: tapGesture,
|
|
defaults: {
|
|
/**
|
|
* max time of a tap, this is for the slow tappers
|
|
* @property tapMaxTime
|
|
* @type {Number}
|
|
* @default 250
|
|
*/
|
|
tapMaxTime: 250,
|
|
|
|
/**
|
|
* max distance of movement of a tap, this is for the slow tappers
|
|
* @property tapMaxDistance
|
|
* @type {Number}
|
|
* @default 10
|
|
*/
|
|
tapMaxDistance: 10,
|
|
|
|
/**
|
|
* always trigger the `tap` event, even while double-tapping
|
|
* @property tapAlways
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
tapAlways: true,
|
|
|
|
/**
|
|
* max distance between two taps
|
|
* @property doubleTapDistance
|
|
* @type {Number}
|
|
* @default 20
|
|
*/
|
|
doubleTapDistance: 20,
|
|
|
|
/**
|
|
* max time between two taps
|
|
* @property doubleTapInterval
|
|
* @type {Number}
|
|
* @default 300
|
|
*/
|
|
doubleTapInterval: 300
|
|
}
|
|
};
|
|
})('tap');
|
|
|
|
/**
|
|
* @module gestures
|
|
*/
|
|
/**
|
|
* when a touch is being touched at the page
|
|
*
|
|
* @class Touch
|
|
* @static
|
|
*/
|
|
/**
|
|
* @event touch
|
|
* @param {Object} ev
|
|
*/
|
|
Hammer.gestures.Touch = {
|
|
name: 'touch',
|
|
index: -Infinity,
|
|
defaults: {
|
|
/**
|
|
* call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page,
|
|
* but it improves gestures like transforming and dragging.
|
|
* be careful with using this, it can be very annoying for users to be stuck on the page
|
|
* @property preventDefault
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
preventDefault: false,
|
|
|
|
/**
|
|
* disable mouse events, so only touch (or pen!) input triggers events
|
|
* @property preventMouse
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
preventMouse: false
|
|
},
|
|
handler: function touchGesture(ev, inst) {
|
|
if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) {
|
|
ev.stopDetect();
|
|
return;
|
|
}
|
|
|
|
if(inst.options.preventDefault) {
|
|
ev.preventDefault();
|
|
}
|
|
|
|
if(ev.eventType == EVENT_TOUCH) {
|
|
inst.trigger('touch', ev);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @module gestures
|
|
*/
|
|
/**
|
|
* User want to scale or rotate with 2 fingers
|
|
* Preventing the default browser behavior is a good way to improve feel and working. This can be done with the
|
|
* `preventDefault` option.
|
|
*
|
|
* @class Transform
|
|
* @static
|
|
*/
|
|
/**
|
|
* @event transform
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event transformstart
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event transformend
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event pinchin
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event pinchout
|
|
* @param {Object} ev
|
|
*/
|
|
/**
|
|
* @event rotate
|
|
* @param {Object} ev
|
|
*/
|
|
|
|
/**
|
|
* @param {String} name
|
|
*/
|
|
(function(name) {
|
|
var triggered = false;
|
|
|
|
function transformGesture(ev, inst) {
|
|
switch(ev.eventType) {
|
|
case EVENT_START:
|
|
triggered = false;
|
|
break;
|
|
|
|
case EVENT_MOVE:
|
|
// at least multitouch
|
|
if(ev.touches.length < 2) {
|
|
return;
|
|
}
|
|
|
|
var scaleThreshold = Math.abs(1 - ev.scale);
|
|
var rotationThreshold = Math.abs(ev.rotation);
|
|
|
|
// when the distance we moved is too small we skip this gesture
|
|
// or we can be already in dragging
|
|
if(scaleThreshold < inst.options.transformMinScale &&
|
|
rotationThreshold < inst.options.transformMinRotation) {
|
|
return;
|
|
}
|
|
|
|
// we are transforming!
|
|
Detection.current.name = name;
|
|
|
|
// first time, trigger dragstart event
|
|
if(!triggered) {
|
|
inst.trigger(name + 'start', ev);
|
|
triggered = true;
|
|
}
|
|
|
|
inst.trigger(name, ev); // basic transform event
|
|
|
|
// trigger rotate event
|
|
if(rotationThreshold > inst.options.transformMinRotation) {
|
|
inst.trigger('rotate', ev);
|
|
}
|
|
|
|
// trigger pinch event
|
|
if(scaleThreshold > inst.options.transformMinScale) {
|
|
inst.trigger('pinch', ev);
|
|
inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev);
|
|
}
|
|
break;
|
|
|
|
case EVENT_RELEASE:
|
|
if(triggered && ev.changedLength < 2) {
|
|
inst.trigger(name + 'end', ev);
|
|
triggered = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Hammer.gestures.Transform = {
|
|
name: name,
|
|
index: 45,
|
|
defaults: {
|
|
/**
|
|
* minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
|
|
* @property transformMinScale
|
|
* @type {Number}
|
|
* @default 0.01
|
|
*/
|
|
transformMinScale: 0.01,
|
|
|
|
/**
|
|
* rotation in degrees
|
|
* @property transformMinRotation
|
|
* @type {Number}
|
|
* @default 1
|
|
*/
|
|
transformMinRotation: 1
|
|
},
|
|
|
|
handler: transformGesture
|
|
};
|
|
})('transform');
|
|
|
|
/**
|
|
* @module hammer
|
|
*/
|
|
|
|
// AMD export
|
|
if(typeof define == 'function' && define.amd) {
|
|
define(function() {
|
|
return Hammer;
|
|
});
|
|
// commonjs export
|
|
} else if(typeof module !== 'undefined' && module.exports) {
|
|
module.exports = Hammer;
|
|
// browser export
|
|
} else {
|
|
window.Hammer = Hammer;
|
|
}
|
|
|
|
})(window);
|
|
},{}],32:[function(_dereq_,module,exports){
|
|
if (!('indexOf' in Array.prototype)) {
|
|
Array.prototype.indexOf= function(find, i /*opt*/) {
|
|
if (i===undefined) i= 0;
|
|
if (i<0) i+= this.length;
|
|
if (i<0) i= 0;
|
|
for (var n= this.length; i<n; i++)
|
|
if (i in this && this[i]===find)
|
|
return i;
|
|
return -1;
|
|
};
|
|
}
|
|
|
|
d3 = (function(){
|
|
var d3 = {version: "3.3.10"}; // semver
|
|
function d3_class(ctor, properties) {
|
|
try {
|
|
for (var key in properties) {
|
|
Object.defineProperty(ctor.prototype, key, {
|
|
value: properties[key],
|
|
enumerable: false
|
|
});
|
|
}
|
|
} catch (e) {
|
|
ctor.prototype = properties;
|
|
}
|
|
}
|
|
|
|
d3.map = function(object) {
|
|
var map = new d3_Map;
|
|
if (object instanceof d3_Map) object.forEach(function(key, value) { map.set(key, value); });
|
|
else for (var key in object) map.set(key, object[key]);
|
|
return map;
|
|
};
|
|
|
|
function d3_Map() {}
|
|
|
|
d3_class(d3_Map, {
|
|
has: function(key) {
|
|
return d3_map_prefix + key in this;
|
|
},
|
|
get: function(key) {
|
|
return this[d3_map_prefix + key];
|
|
},
|
|
set: function(key, value) {
|
|
return this[d3_map_prefix + key] = value;
|
|
},
|
|
remove: function(key) {
|
|
key = d3_map_prefix + key;
|
|
return key in this && delete this[key];
|
|
},
|
|
keys: function() {
|
|
var keys = [];
|
|
this.forEach(function(key) { keys.push(key); });
|
|
return keys;
|
|
},
|
|
values: function() {
|
|
var values = [];
|
|
this.forEach(function(key, value) { values.push(value); });
|
|
return values;
|
|
},
|
|
entries: function() {
|
|
var entries = [];
|
|
this.forEach(function(key, value) { entries.push({key: key, value: value}); });
|
|
return entries;
|
|
},
|
|
forEach: function(f) {
|
|
for (var key in this) {
|
|
if (key.charCodeAt(0) === d3_map_prefixCode) {
|
|
f.call(this, key.substring(1), this[key]);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var d3_map_prefix = "\0", // prevent collision with built-ins
|
|
d3_map_prefixCode = d3_map_prefix.charCodeAt(0);
|
|
|
|
d3.dispatch = function() {
|
|
var dispatch = new d3_dispatch,
|
|
i = -1,
|
|
n = arguments.length;
|
|
while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
|
|
return dispatch;
|
|
};
|
|
|
|
function d3_dispatch() {}
|
|
|
|
d3_dispatch.prototype.on = function(type, listener) {
|
|
var i = type.indexOf("."),
|
|
name = "";
|
|
|
|
// Extract optional namespace, e.g., "click.foo"
|
|
if (i >= 0) {
|
|
name = type.substring(i + 1);
|
|
type = type.substring(0, i);
|
|
}
|
|
|
|
if (type) return arguments.length < 2
|
|
? this[type].on(name)
|
|
: this[type].on(name, listener);
|
|
|
|
if (arguments.length === 2) {
|
|
if (listener == null) for (type in this) {
|
|
if (this.hasOwnProperty(type)) this[type].on(name, null);
|
|
}
|
|
return this;
|
|
}
|
|
};
|
|
|
|
function d3_dispatch_event(dispatch) {
|
|
var listeners = [],
|
|
listenerByName = new d3_Map;
|
|
|
|
function event() {
|
|
var z = listeners, // defensive reference
|
|
i = -1,
|
|
n = z.length,
|
|
l;
|
|
while (++i < n) if (l = z[i].on) l.apply(this, arguments);
|
|
return dispatch;
|
|
}
|
|
|
|
event.on = function(name, listener) {
|
|
var l = listenerByName.get(name),
|
|
i;
|
|
|
|
// return the current listener, if any
|
|
if (arguments.length < 2) return l && l.on;
|
|
|
|
// remove the old listener, if any (with copy-on-write)
|
|
if (l) {
|
|
l.on = null;
|
|
listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
|
|
listenerByName.remove(name);
|
|
}
|
|
|
|
// add the new listener, if any
|
|
if (listener) listeners.push(listenerByName.set(name, {on: listener}));
|
|
|
|
return dispatch;
|
|
};
|
|
|
|
return event;
|
|
}
|
|
// Copies a variable number of methods from source to target.
|
|
d3.rebind = function(target, source) {
|
|
var i = 1, n = arguments.length, method;
|
|
while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
|
|
return target;
|
|
};
|
|
|
|
// Method is assumed to be a standard D3 getter-setter:
|
|
// If passed with no arguments, gets the value.
|
|
// If passed with arguments, sets the value and returns the target.
|
|
function d3_rebind(target, source, method) {
|
|
return function() {
|
|
var value = method.apply(source, arguments);
|
|
return value === source ? target : value;
|
|
};
|
|
}
|
|
return d3;
|
|
})();
|
|
|
|
},{}],33:[function(_dereq_,module,exports){
|
|
// Released under MIT license
|
|
// Copyright (c) 2009-2010 Dominic Baggott
|
|
// Copyright (c) 2009-2010 Ash Berlin
|
|
// Copyright (c) 2011 Christoph Dorn <christoph@christophdorn.com> (http://www.christophdorn.com)
|
|
// Date: 2013-09-15T16:12Z
|
|
|
|
(function(expose) {
|
|
|
|
|
|
|
|
|
|
var MarkdownHelpers = {};
|
|
|
|
// For Spidermonkey based engines
|
|
function mk_block_toSource() {
|
|
return "Markdown.mk_block( " +
|
|
uneval(this.toString()) +
|
|
", " +
|
|
uneval(this.trailing) +
|
|
", " +
|
|
uneval(this.lineNumber) +
|
|
" )";
|
|
}
|
|
|
|
// node
|
|
function mk_block_inspect() {
|
|
var util = _dereq_("util");
|
|
return "Markdown.mk_block( " +
|
|
util.inspect(this.toString()) +
|
|
", " +
|
|
util.inspect(this.trailing) +
|
|
", " +
|
|
util.inspect(this.lineNumber) +
|
|
" )";
|
|
|
|
}
|
|
|
|
MarkdownHelpers.mk_block = function(block, trail, line) {
|
|
// Be helpful for default case in tests.
|
|
if ( arguments.length === 1 )
|
|
trail = "\n\n";
|
|
|
|
// We actually need a String object, not a string primitive
|
|
/* jshint -W053 */
|
|
var s = new String(block);
|
|
s.trailing = trail;
|
|
// To make it clear its not just a string
|
|
s.inspect = mk_block_inspect;
|
|
s.toSource = mk_block_toSource;
|
|
|
|
if ( line !== undefined )
|
|
s.lineNumber = line;
|
|
|
|
return s;
|
|
};
|
|
|
|
|
|
var isArray = MarkdownHelpers.isArray = Array.isArray || function(obj) {
|
|
return Object.prototype.toString.call(obj) === "[object Array]";
|
|
};
|
|
|
|
// Don't mess with Array.prototype. Its not friendly
|
|
if ( Array.prototype.forEach ) {
|
|
MarkdownHelpers.forEach = function forEach( arr, cb, thisp ) {
|
|
return arr.forEach( cb, thisp );
|
|
};
|
|
}
|
|
else {
|
|
MarkdownHelpers.forEach = function forEach(arr, cb, thisp) {
|
|
for (var i = 0; i < arr.length; i++)
|
|
cb.call(thisp || arr, arr[i], i, arr);
|
|
};
|
|
}
|
|
|
|
MarkdownHelpers.isEmpty = function isEmpty( obj ) {
|
|
for ( var key in obj ) {
|
|
if ( hasOwnProperty.call( obj, key ) )
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
MarkdownHelpers.extract_attr = function extract_attr( jsonml ) {
|
|
return isArray(jsonml)
|
|
&& jsonml.length > 1
|
|
&& typeof jsonml[ 1 ] === "object"
|
|
&& !( isArray(jsonml[ 1 ]) )
|
|
? jsonml[ 1 ]
|
|
: undefined;
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
* class Markdown
|
|
*
|
|
* Markdown processing in Javascript done right. We have very particular views
|
|
* on what constitutes 'right' which include:
|
|
*
|
|
* - produces well-formed HTML (this means that em and strong nesting is
|
|
* important)
|
|
*
|
|
* - has an intermediate representation to allow processing of parsed data (We
|
|
* in fact have two, both as [JsonML]: a markdown tree and an HTML tree).
|
|
*
|
|
* - is easily extensible to add new dialects without having to rewrite the
|
|
* entire parsing mechanics
|
|
*
|
|
* - has a good test suite
|
|
*
|
|
* This implementation fulfills all of these (except that the test suite could
|
|
* do with expanding to automatically run all the fixtures from other Markdown
|
|
* implementations.)
|
|
*
|
|
* ##### Intermediate Representation
|
|
*
|
|
* *TODO* Talk about this :) Its JsonML, but document the node names we use.
|
|
*
|
|
* [JsonML]: http://jsonml.org/ "JSON Markup Language"
|
|
**/
|
|
var Markdown = function(dialect) {
|
|
switch (typeof dialect) {
|
|
case "undefined":
|
|
this.dialect = Markdown.dialects.Gruber;
|
|
break;
|
|
case "object":
|
|
this.dialect = dialect;
|
|
break;
|
|
default:
|
|
if ( dialect in Markdown.dialects )
|
|
this.dialect = Markdown.dialects[dialect];
|
|
else
|
|
throw new Error("Unknown Markdown dialect '" + String(dialect) + "'");
|
|
break;
|
|
}
|
|
this.em_state = [];
|
|
this.strong_state = [];
|
|
this.debug_indent = "";
|
|
};
|
|
|
|
/**
|
|
* Markdown.dialects
|
|
*
|
|
* Namespace of built-in dialects.
|
|
**/
|
|
Markdown.dialects = {};
|
|
|
|
|
|
|
|
|
|
// Imported functions
|
|
var mk_block = Markdown.mk_block = MarkdownHelpers.mk_block,
|
|
isArray = MarkdownHelpers.isArray;
|
|
|
|
/**
|
|
* parse( markdown, [dialect] ) -> JsonML
|
|
* - markdown (String): markdown string to parse
|
|
* - dialect (String | Dialect): the dialect to use, defaults to gruber
|
|
*
|
|
* Parse `markdown` and return a markdown document as a Markdown.JsonML tree.
|
|
**/
|
|
Markdown.parse = function( source, dialect ) {
|
|
// dialect will default if undefined
|
|
var md = new Markdown( dialect );
|
|
return md.toTree( source );
|
|
};
|
|
|
|
function count_lines( str ) {
|
|
var n = 0,
|
|
i = -1;
|
|
while ( ( i = str.indexOf("\n", i + 1) ) !== -1 )
|
|
n++;
|
|
return n;
|
|
}
|
|
|
|
// Internal - split source into rough blocks
|
|
Markdown.prototype.split_blocks = function splitBlocks( input ) {
|
|
input = input.replace(/(\r\n|\n|\r)/g, "\n");
|
|
// [\s\S] matches _anything_ (newline or space)
|
|
// [^] is equivalent but doesn't work in IEs.
|
|
var re = /([\s\S]+?)($|\n#|\n(?:\s*\n|$)+)/g,
|
|
blocks = [],
|
|
m;
|
|
|
|
var line_no = 1;
|
|
|
|
if ( ( m = /^(\s*\n)/.exec(input) ) !== null ) {
|
|
// skip (but count) leading blank lines
|
|
line_no += count_lines( m[0] );
|
|
re.lastIndex = m[0].length;
|
|
}
|
|
|
|
while ( ( m = re.exec(input) ) !== null ) {
|
|
if (m[2] === "\n#") {
|
|
m[2] = "\n";
|
|
re.lastIndex--;
|
|
}
|
|
blocks.push( mk_block( m[1], m[2], line_no ) );
|
|
line_no += count_lines( m[0] );
|
|
}
|
|
|
|
return blocks;
|
|
};
|
|
|
|
/**
|
|
* Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ]
|
|
* - block (String): the block to process
|
|
* - next (Array): the following blocks
|
|
*
|
|
* Process `block` and return an array of JsonML nodes representing `block`.
|
|
*
|
|
* It does this by asking each block level function in the dialect to process
|
|
* the block until one can. Succesful handling is indicated by returning an
|
|
* array (with zero or more JsonML nodes), failure by a false value.
|
|
*
|
|
* Blocks handlers are responsible for calling [[Markdown#processInline]]
|
|
* themselves as appropriate.
|
|
*
|
|
* If the blocks were split incorrectly or adjacent blocks need collapsing you
|
|
* can adjust `next` in place using shift/splice etc.
|
|
*
|
|
* If any of this default behaviour is not right for the dialect, you can
|
|
* define a `__call__` method on the dialect that will get invoked to handle
|
|
* the block processing.
|
|
*/
|
|
Markdown.prototype.processBlock = function processBlock( block, next ) {
|
|
var cbs = this.dialect.block,
|
|
ord = cbs.__order__;
|
|
|
|
if ( "__call__" in cbs )
|
|
return cbs.__call__.call(this, block, next);
|
|
|
|
for ( var i = 0; i < ord.length; i++ ) {
|
|
//D:this.debug( "Testing", ord[i] );
|
|
var res = cbs[ ord[i] ].call( this, block, next );
|
|
if ( res ) {
|
|
//D:this.debug(" matched");
|
|
if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) )
|
|
this.debug(ord[i], "didn't return a proper array");
|
|
//D:this.debug( "" );
|
|
return res;
|
|
}
|
|
}
|
|
|
|
// Uhoh! no match! Should we throw an error?
|
|
return [];
|
|
};
|
|
|
|
Markdown.prototype.processInline = function processInline( block ) {
|
|
return this.dialect.inline.__call__.call( this, String( block ) );
|
|
};
|
|
|
|
/**
|
|
* Markdown#toTree( source ) -> JsonML
|
|
* - source (String): markdown source to parse
|
|
*
|
|
* Parse `source` into a JsonML tree representing the markdown document.
|
|
**/
|
|
// custom_tree means set this.tree to `custom_tree` and restore old value on return
|
|
Markdown.prototype.toTree = function toTree( source, custom_root ) {
|
|
var blocks = source instanceof Array ? source : this.split_blocks( source );
|
|
|
|
// Make tree a member variable so its easier to mess with in extensions
|
|
var old_tree = this.tree;
|
|
try {
|
|
this.tree = custom_root || this.tree || [ "markdown" ];
|
|
|
|
blocks_loop:
|
|
while ( blocks.length ) {
|
|
var b = this.processBlock( blocks.shift(), blocks );
|
|
|
|
// Reference blocks and the like won't return any content
|
|
if ( !b.length )
|
|
continue blocks_loop;
|
|
|
|
this.tree.push.apply( this.tree, b );
|
|
}
|
|
return this.tree;
|
|
}
|
|
finally {
|
|
if ( custom_root )
|
|
this.tree = old_tree;
|
|
}
|
|
};
|
|
|
|
// Noop by default
|
|
Markdown.prototype.debug = function () {
|
|
var args = Array.prototype.slice.call( arguments);
|
|
args.unshift(this.debug_indent);
|
|
if ( typeof print !== "undefined" )
|
|
print.apply( print, args );
|
|
if ( typeof console !== "undefined" && typeof console.log !== "undefined" )
|
|
console.log.apply( null, args );
|
|
};
|
|
|
|
Markdown.prototype.loop_re_over_block = function( re, block, cb ) {
|
|
// Dont use /g regexps with this
|
|
var m,
|
|
b = block.valueOf();
|
|
|
|
while ( b.length && (m = re.exec(b) ) !== null ) {
|
|
b = b.substr( m[0].length );
|
|
cb.call(this, m);
|
|
}
|
|
return b;
|
|
};
|
|
|
|
// Build default order from insertion order.
|
|
Markdown.buildBlockOrder = function(d) {
|
|
var ord = [];
|
|
for ( var i in d ) {
|
|
if ( i === "__order__" || i === "__call__" )
|
|
continue;
|
|
ord.push( i );
|
|
}
|
|
d.__order__ = ord;
|
|
};
|
|
|
|
// Build patterns for inline matcher
|
|
Markdown.buildInlinePatterns = function(d) {
|
|
var patterns = [];
|
|
|
|
for ( var i in d ) {
|
|
// __foo__ is reserved and not a pattern
|
|
if ( i.match( /^__.*__$/) )
|
|
continue;
|
|
var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" )
|
|
.replace( /\n/, "\\n" );
|
|
patterns.push( i.length === 1 ? l : "(?:" + l + ")" );
|
|
}
|
|
|
|
patterns = patterns.join("|");
|
|
d.__patterns__ = patterns;
|
|
//print("patterns:", uneval( patterns ) );
|
|
|
|
var fn = d.__call__;
|
|
d.__call__ = function(text, pattern) {
|
|
if ( pattern !== undefined )
|
|
return fn.call(this, text, pattern);
|
|
else
|
|
return fn.call(this, text, patterns);
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
var extract_attr = MarkdownHelpers.extract_attr;
|
|
|
|
/**
|
|
* renderJsonML( jsonml[, options] ) -> String
|
|
* - jsonml (Array): JsonML array to render to XML
|
|
* - options (Object): options
|
|
*
|
|
* Converts the given JsonML into well-formed XML.
|
|
*
|
|
* The options currently understood are:
|
|
*
|
|
* - root (Boolean): wether or not the root node should be included in the
|
|
* output, or just its children. The default `false` is to not include the
|
|
* root itself.
|
|
*/
|
|
Markdown.renderJsonML = function( jsonml, options ) {
|
|
options = options || {};
|
|
// include the root element in the rendered output?
|
|
options.root = options.root || false;
|
|
|
|
var content = [];
|
|
|
|
if ( options.root ) {
|
|
content.push( render_tree( jsonml ) );
|
|
}
|
|
else {
|
|
jsonml.shift(); // get rid of the tag
|
|
if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) )
|
|
jsonml.shift(); // get rid of the attributes
|
|
|
|
while ( jsonml.length )
|
|
content.push( render_tree( jsonml.shift() ) );
|
|
}
|
|
|
|
return content.join( "\n\n" );
|
|
};
|
|
|
|
|
|
/**
|
|
* toHTMLTree( markdown, [dialect] ) -> JsonML
|
|
* toHTMLTree( md_tree ) -> JsonML
|
|
* - markdown (String): markdown string to parse
|
|
* - dialect (String | Dialect): the dialect to use, defaults to gruber
|
|
* - md_tree (Markdown.JsonML): parsed markdown tree
|
|
*
|
|
* Turn markdown into HTML, represented as a JsonML tree. If a string is given
|
|
* to this function, it is first parsed into a markdown tree by calling
|
|
* [[parse]].
|
|
**/
|
|
Markdown.toHTMLTree = function toHTMLTree( input, dialect , options ) {
|
|
|
|
// convert string input to an MD tree
|
|
if ( typeof input === "string" )
|
|
input = this.parse( input, dialect );
|
|
|
|
// Now convert the MD tree to an HTML tree
|
|
|
|
// remove references from the tree
|
|
var attrs = extract_attr( input ),
|
|
refs = {};
|
|
|
|
if ( attrs && attrs.references )
|
|
refs = attrs.references;
|
|
|
|
var html = convert_tree_to_html( input, refs , options );
|
|
merge_text_nodes( html );
|
|
return html;
|
|
};
|
|
|
|
/**
|
|
* toHTML( markdown, [dialect] ) -> String
|
|
* toHTML( md_tree ) -> String
|
|
* - markdown (String): markdown string to parse
|
|
* - md_tree (Markdown.JsonML): parsed markdown tree
|
|
*
|
|
* Take markdown (either as a string or as a JsonML tree) and run it through
|
|
* [[toHTMLTree]] then turn it into a well-formated HTML fragment.
|
|
**/
|
|
Markdown.toHTML = function toHTML( source , dialect , options ) {
|
|
var input = this.toHTMLTree( source , dialect , options );
|
|
|
|
return this.renderJsonML( input );
|
|
};
|
|
|
|
|
|
function escapeHTML( text ) {
|
|
return text.replace( /&/g, "&" )
|
|
.replace( /</g, "<" )
|
|
.replace( />/g, ">" )
|
|
.replace( /"/g, """ )
|
|
.replace( /'/g, "'" );
|
|
}
|
|
|
|
function render_tree( jsonml ) {
|
|
// basic case
|
|
if ( typeof jsonml === "string" )
|
|
return escapeHTML( jsonml );
|
|
|
|
var tag = jsonml.shift(),
|
|
attributes = {},
|
|
content = [];
|
|
|
|
if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) )
|
|
attributes = jsonml.shift();
|
|
|
|
while ( jsonml.length )
|
|
content.push( render_tree( jsonml.shift() ) );
|
|
|
|
var tag_attrs = "";
|
|
for ( var a in attributes )
|
|
tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"';
|
|
|
|
// be careful about adding whitespace here for inline elements
|
|
if ( tag === "img" || tag === "br" || tag === "hr" )
|
|
return "<"+ tag + tag_attrs + "/>";
|
|
else
|
|
return "<"+ tag + tag_attrs + ">" + content.join( "" ) + "</" + tag + ">";
|
|
}
|
|
|
|
function convert_tree_to_html( tree, references, options ) {
|
|
var i;
|
|
options = options || {};
|
|
|
|
// shallow clone
|
|
var jsonml = tree.slice( 0 );
|
|
|
|
if ( typeof options.preprocessTreeNode === "function" )
|
|
jsonml = options.preprocessTreeNode(jsonml, references);
|
|
|
|
// Clone attributes if they exist
|
|
var attrs = extract_attr( jsonml );
|
|
if ( attrs ) {
|
|
jsonml[ 1 ] = {};
|
|
for ( i in attrs ) {
|
|
jsonml[ 1 ][ i ] = attrs[ i ];
|
|
}
|
|
attrs = jsonml[ 1 ];
|
|
}
|
|
|
|
// basic case
|
|
if ( typeof jsonml === "string" )
|
|
return jsonml;
|
|
|
|
// convert this node
|
|
switch ( jsonml[ 0 ] ) {
|
|
case "header":
|
|
jsonml[ 0 ] = "h" + jsonml[ 1 ].level;
|
|
delete jsonml[ 1 ].level;
|
|
break;
|
|
case "bulletlist":
|
|
jsonml[ 0 ] = "ul";
|
|
break;
|
|
case "numberlist":
|
|
jsonml[ 0 ] = "ol";
|
|
break;
|
|
case "listitem":
|
|
jsonml[ 0 ] = "li";
|
|
break;
|
|
case "para":
|
|
jsonml[ 0 ] = "p";
|
|
break;
|
|
case "markdown":
|
|
jsonml[ 0 ] = "html";
|
|
if ( attrs )
|
|
delete attrs.references;
|
|
break;
|
|
case "code_block":
|
|
jsonml[ 0 ] = "pre";
|
|
i = attrs ? 2 : 1;
|
|
var code = [ "code" ];
|
|
code.push.apply( code, jsonml.splice( i, jsonml.length - i ) );
|
|
jsonml[ i ] = code;
|
|
break;
|
|
case "inlinecode":
|
|
jsonml[ 0 ] = "code";
|
|
break;
|
|
case "img":
|
|
jsonml[ 1 ].src = jsonml[ 1 ].href;
|
|
delete jsonml[ 1 ].href;
|
|
break;
|
|
case "linebreak":
|
|
jsonml[ 0 ] = "br";
|
|
break;
|
|
case "link":
|
|
jsonml[ 0 ] = "a";
|
|
break;
|
|
case "link_ref":
|
|
jsonml[ 0 ] = "a";
|
|
|
|
// grab this ref and clean up the attribute node
|
|
var ref = references[ attrs.ref ];
|
|
|
|
// if the reference exists, make the link
|
|
if ( ref ) {
|
|
delete attrs.ref;
|
|
|
|
// add in the href and title, if present
|
|
attrs.href = ref.href;
|
|
if ( ref.title )
|
|
attrs.title = ref.title;
|
|
|
|
// get rid of the unneeded original text
|
|
delete attrs.original;
|
|
}
|
|
// the reference doesn't exist, so revert to plain text
|
|
else {
|
|
return attrs.original;
|
|
}
|
|
break;
|
|
case "img_ref":
|
|
jsonml[ 0 ] = "img";
|
|
|
|
// grab this ref and clean up the attribute node
|
|
var ref = references[ attrs.ref ];
|
|
|
|
// if the reference exists, make the link
|
|
if ( ref ) {
|
|
delete attrs.ref;
|
|
|
|
// add in the href and title, if present
|
|
attrs.src = ref.href;
|
|
if ( ref.title )
|
|
attrs.title = ref.title;
|
|
|
|
// get rid of the unneeded original text
|
|
delete attrs.original;
|
|
}
|
|
// the reference doesn't exist, so revert to plain text
|
|
else {
|
|
return attrs.original;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// convert all the children
|
|
i = 1;
|
|
|
|
// deal with the attribute node, if it exists
|
|
if ( attrs ) {
|
|
// if there are keys, skip over it
|
|
for ( var key in jsonml[ 1 ] ) {
|
|
i = 2;
|
|
break;
|
|
}
|
|
// if there aren't, remove it
|
|
if ( i === 1 )
|
|
jsonml.splice( i, 1 );
|
|
}
|
|
|
|
for ( ; i < jsonml.length; ++i ) {
|
|
jsonml[ i ] = convert_tree_to_html( jsonml[ i ], references, options );
|
|
}
|
|
|
|
return jsonml;
|
|
}
|
|
|
|
|
|
// merges adjacent text nodes into a single node
|
|
function merge_text_nodes( jsonml ) {
|
|
// skip the tag name and attribute hash
|
|
var i = extract_attr( jsonml ) ? 2 : 1;
|
|
|
|
while ( i < jsonml.length ) {
|
|
// if it's a string check the next item too
|
|
if ( typeof jsonml[ i ] === "string" ) {
|
|
if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) {
|
|
// merge the second string into the first and remove it
|
|
jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ];
|
|
}
|
|
else {
|
|
++i;
|
|
}
|
|
}
|
|
// if it's not a string recurse
|
|
else {
|
|
merge_text_nodes( jsonml[ i ] );
|
|
++i;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
var DialectHelpers = {};
|
|
DialectHelpers.inline_until_char = function( text, want ) {
|
|
var consumed = 0,
|
|
nodes = [];
|
|
|
|
while ( true ) {
|
|
if ( text.charAt( consumed ) === want ) {
|
|
// Found the character we were looking for
|
|
consumed++;
|
|
return [ consumed, nodes ];
|
|
}
|
|
|
|
if ( consumed >= text.length ) {
|
|
// No closing char found. Abort.
|
|
return null;
|
|
}
|
|
|
|
var res = this.dialect.inline.__oneElement__.call(this, text.substr( consumed ) );
|
|
consumed += res[ 0 ];
|
|
// Add any returned nodes.
|
|
nodes.push.apply( nodes, res.slice( 1 ) );
|
|
}
|
|
};
|
|
|
|
// Helper function to make sub-classing a dialect easier
|
|
DialectHelpers.subclassDialect = function( d ) {
|
|
function Block() {}
|
|
Block.prototype = d.block;
|
|
function Inline() {}
|
|
Inline.prototype = d.inline;
|
|
|
|
return { block: new Block(), inline: new Inline() };
|
|
};
|
|
|
|
|
|
|
|
|
|
var forEach = MarkdownHelpers.forEach,
|
|
extract_attr = MarkdownHelpers.extract_attr,
|
|
mk_block = MarkdownHelpers.mk_block,
|
|
isEmpty = MarkdownHelpers.isEmpty,
|
|
inline_until_char = DialectHelpers.inline_until_char;
|
|
|
|
/**
|
|
* Gruber dialect
|
|
*
|
|
* The default dialect that follows the rules set out by John Gruber's
|
|
* markdown.pl as closely as possible. Well actually we follow the behaviour of
|
|
* that script which in some places is not exactly what the syntax web page
|
|
* says.
|
|
**/
|
|
var Gruber = {
|
|
block: {
|
|
atxHeader: function atxHeader( block, next ) {
|
|
var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ );
|
|
|
|
if ( !m )
|
|
return undefined;
|
|
|
|
var header = [ "header", { level: m[ 1 ].length } ];
|
|
Array.prototype.push.apply(header, this.processInline(m[ 2 ]));
|
|
|
|
if ( m[0].length < block.length )
|
|
next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) );
|
|
|
|
return [ header ];
|
|
},
|
|
|
|
setextHeader: function setextHeader( block, next ) {
|
|
var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ );
|
|
|
|
if ( !m )
|
|
return undefined;
|
|
|
|
var level = ( m[ 2 ] === "=" ) ? 1 : 2,
|
|
header = [ "header", { level : level }, m[ 1 ] ];
|
|
|
|
if ( m[0].length < block.length )
|
|
next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) );
|
|
|
|
return [ header ];
|
|
},
|
|
|
|
code: function code( block, next ) {
|
|
// | Foo
|
|
// |bar
|
|
// should be a code block followed by a paragraph. Fun
|
|
//
|
|
// There might also be adjacent code block to merge.
|
|
|
|
var ret = [],
|
|
re = /^(?: {0,3}\t| {4})(.*)\n?/;
|
|
|
|
// 4 spaces + content
|
|
if ( !block.match( re ) )
|
|
return undefined;
|
|
|
|
block_search:
|
|
do {
|
|
// Now pull out the rest of the lines
|
|
var b = this.loop_re_over_block(
|
|
re, block.valueOf(), function( m ) { ret.push( m[1] ); } );
|
|
|
|
if ( b.length ) {
|
|
// Case alluded to in first comment. push it back on as a new block
|
|
next.unshift( mk_block(b, block.trailing) );
|
|
break block_search;
|
|
}
|
|
else if ( next.length ) {
|
|
// Check the next block - it might be code too
|
|
if ( !next[0].match( re ) )
|
|
break block_search;
|
|
|
|
// Pull how how many blanks lines follow - minus two to account for .join
|
|
ret.push ( block.trailing.replace(/[^\n]/g, "").substring(2) );
|
|
|
|
block = next.shift();
|
|
}
|
|
else {
|
|
break block_search;
|
|
}
|
|
} while ( true );
|
|
|
|
return [ [ "code_block", ret.join("\n") ] ];
|
|
},
|
|
|
|
horizRule: function horizRule( block, next ) {
|
|
// this needs to find any hr in the block to handle abutting blocks
|
|
var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ );
|
|
|
|
if ( !m )
|
|
return undefined;
|
|
|
|
var jsonml = [ [ "hr" ] ];
|
|
|
|
// if there's a leading abutting block, process it
|
|
if ( m[ 1 ] ) {
|
|
var contained = mk_block( m[ 1 ], "", block.lineNumber );
|
|
jsonml.unshift.apply( jsonml, this.toTree( contained, [] ) );
|
|
}
|
|
|
|
// if there's a trailing abutting block, stick it into next
|
|
if ( m[ 3 ] )
|
|
next.unshift( mk_block( m[ 3 ], block.trailing, block.lineNumber + 1 ) );
|
|
|
|
return jsonml;
|
|
},
|
|
|
|
// There are two types of lists. Tight and loose. Tight lists have no whitespace
|
|
// between the items (and result in text just in the <li>) and loose lists,
|
|
// which have an empty line between list items, resulting in (one or more)
|
|
// paragraphs inside the <li>.
|
|
//
|
|
// There are all sorts weird edge cases about the original markdown.pl's
|
|
// handling of lists:
|
|
//
|
|
// * Nested lists are supposed to be indented by four chars per level. But
|
|
// if they aren't, you can get a nested list by indenting by less than
|
|
// four so long as the indent doesn't match an indent of an existing list
|
|
// item in the 'nest stack'.
|
|
//
|
|
// * The type of the list (bullet or number) is controlled just by the
|
|
// first item at the indent. Subsequent changes are ignored unless they
|
|
// are for nested lists
|
|
//
|
|
lists: (function( ) {
|
|
// Use a closure to hide a few variables.
|
|
var any_list = "[*+-]|\\d+\\.",
|
|
bullet_list = /[*+-]/,
|
|
// Capture leading indent as it matters for determining nested lists.
|
|
is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ),
|
|
indent_re = "(?: {0,3}\\t| {4})";
|
|
|
|
// TODO: Cache this regexp for certain depths.
|
|
// Create a regexp suitable for matching an li for a given stack depth
|
|
function regex_for_depth( depth ) {
|
|
|
|
return new RegExp(
|
|
// m[1] = indent, m[2] = list_type
|
|
"(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" +
|
|
// m[3] = cont
|
|
"(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})"
|
|
);
|
|
}
|
|
function expand_tab( input ) {
|
|
return input.replace( / {0,3}\t/g, " " );
|
|
}
|
|
|
|
// Add inline content `inline` to `li`. inline comes from processInline
|
|
// so is an array of content
|
|
function add(li, loose, inline, nl) {
|
|
if ( loose ) {
|
|
li.push( [ "para" ].concat(inline) );
|
|
return;
|
|
}
|
|
// Hmmm, should this be any block level element or just paras?
|
|
var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] === "para"
|
|
? li[li.length -1]
|
|
: li;
|
|
|
|
// If there is already some content in this list, add the new line in
|
|
if ( nl && li.length > 1 )
|
|
inline.unshift(nl);
|
|
|
|
for ( var i = 0; i < inline.length; i++ ) {
|
|
var what = inline[i],
|
|
is_str = typeof what === "string";
|
|
if ( is_str && add_to.length > 1 && typeof add_to[add_to.length-1] === "string" )
|
|
add_to[ add_to.length-1 ] += what;
|
|
else
|
|
add_to.push( what );
|
|
}
|
|
}
|
|
|
|
// contained means have an indent greater than the current one. On
|
|
// *every* line in the block
|
|
function get_contained_blocks( depth, blocks ) {
|
|
|
|
var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ),
|
|
replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"),
|
|
ret = [];
|
|
|
|
while ( blocks.length > 0 ) {
|
|
if ( re.exec( blocks[0] ) ) {
|
|
var b = blocks.shift(),
|
|
// Now remove that indent
|
|
x = b.replace( replace, "");
|
|
|
|
ret.push( mk_block( x, b.trailing, b.lineNumber ) );
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// passed to stack.forEach to turn list items up the stack into paras
|
|
function paragraphify(s, i, stack) {
|
|
var list = s.list;
|
|
var last_li = list[list.length-1];
|
|
|
|
if ( last_li[1] instanceof Array && last_li[1][0] === "para" )
|
|
return;
|
|
if ( i + 1 === stack.length ) {
|
|
// Last stack frame
|
|
// Keep the same array, but replace the contents
|
|
last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ) );
|
|
}
|
|
else {
|
|
var sublist = last_li.pop();
|
|
last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ), sublist );
|
|
}
|
|
}
|
|
|
|
// The matcher function
|
|
return function( block, next ) {
|
|
var m = block.match( is_list_re );
|
|
if ( !m )
|
|
return undefined;
|
|
|
|
function make_list( m ) {
|
|
var list = bullet_list.exec( m[2] )
|
|
? ["bulletlist"]
|
|
: ["numberlist"];
|
|
|
|
stack.push( { list: list, indent: m[1] } );
|
|
return list;
|
|
}
|
|
|
|
|
|
var stack = [], // Stack of lists for nesting.
|
|
list = make_list( m ),
|
|
last_li,
|
|
loose = false,
|
|
ret = [ stack[0].list ],
|
|
i;
|
|
|
|
// Loop to search over block looking for inner block elements and loose lists
|
|
loose_search:
|
|
while ( true ) {
|
|
// Split into lines preserving new lines at end of line
|
|
var lines = block.split( /(?=\n)/ );
|
|
|
|
// We have to grab all lines for a li and call processInline on them
|
|
// once as there are some inline things that can span lines.
|
|
var li_accumulate = "", nl = "";
|
|
|
|
// Loop over the lines in this block looking for tight lists.
|
|
tight_search:
|
|
for ( var line_no = 0; line_no < lines.length; line_no++ ) {
|
|
nl = "";
|
|
var l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; });
|
|
|
|
|
|
// TODO: really should cache this
|
|
var line_re = regex_for_depth( stack.length );
|
|
|
|
m = l.match( line_re );
|
|
//print( "line:", uneval(l), "\nline match:", uneval(m) );
|
|
|
|
// We have a list item
|
|
if ( m[1] !== undefined ) {
|
|
// Process the previous list item, if any
|
|
if ( li_accumulate.length ) {
|
|
add( last_li, loose, this.processInline( li_accumulate ), nl );
|
|
// Loose mode will have been dealt with. Reset it
|
|
loose = false;
|
|
li_accumulate = "";
|
|
}
|
|
|
|
m[1] = expand_tab( m[1] );
|
|
var wanted_depth = Math.floor(m[1].length/4)+1;
|
|
//print( "want:", wanted_depth, "stack:", stack.length);
|
|
if ( wanted_depth > stack.length ) {
|
|
// Deep enough for a nested list outright
|
|
//print ( "new nested list" );
|
|
list = make_list( m );
|
|
last_li.push( list );
|
|
last_li = list[1] = [ "listitem" ];
|
|
}
|
|
else {
|
|
// We aren't deep enough to be strictly a new level. This is
|
|
// where Md.pl goes nuts. If the indent matches a level in the
|
|
// stack, put it there, else put it one deeper then the
|
|
// wanted_depth deserves.
|
|
var found = false;
|
|
for ( i = 0; i < stack.length; i++ ) {
|
|
if ( stack[ i ].indent !== m[1] )
|
|
continue;
|
|
|
|
list = stack[ i ].list;
|
|
stack.splice( i+1, stack.length - (i+1) );
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
if (!found) {
|
|
//print("not found. l:", uneval(l));
|
|
wanted_depth++;
|
|
if ( wanted_depth <= stack.length ) {
|
|
stack.splice(wanted_depth, stack.length - wanted_depth);
|
|
//print("Desired depth now", wanted_depth, "stack:", stack.length);
|
|
list = stack[wanted_depth-1].list;
|
|
//print("list:", uneval(list) );
|
|
}
|
|
else {
|
|
//print ("made new stack for messy indent");
|
|
list = make_list(m);
|
|
last_li.push(list);
|
|
}
|
|
}
|
|
|
|
//print( uneval(list), "last", list === stack[stack.length-1].list );
|
|
last_li = [ "listitem" ];
|
|
list.push(last_li);
|
|
} // end depth of shenegains
|
|
nl = "";
|
|
}
|
|
|
|
// Add content
|
|
if ( l.length > m[0].length )
|
|
li_accumulate += nl + l.substr( m[0].length );
|
|
} // tight_search
|
|
|
|
if ( li_accumulate.length ) {
|
|
add( last_li, loose, this.processInline( li_accumulate ), nl );
|
|
// Loose mode will have been dealt with. Reset it
|
|
loose = false;
|
|
li_accumulate = "";
|
|
}
|
|
|
|
// Look at the next block - we might have a loose list. Or an extra
|
|
// paragraph for the current li
|
|
var contained = get_contained_blocks( stack.length, next );
|
|
|
|
// Deal with code blocks or properly nested lists
|
|
if ( contained.length > 0 ) {
|
|
// Make sure all listitems up the stack are paragraphs
|
|
forEach( stack, paragraphify, this);
|
|
|
|
last_li.push.apply( last_li, this.toTree( contained, [] ) );
|
|
}
|
|
|
|
var next_block = next[0] && next[0].valueOf() || "";
|
|
|
|
if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) {
|
|
block = next.shift();
|
|
|
|
// Check for an HR following a list: features/lists/hr_abutting
|
|
var hr = this.dialect.block.horizRule( block, next );
|
|
|
|
if ( hr ) {
|
|
ret.push.apply(ret, hr);
|
|
break;
|
|
}
|
|
|
|
// Make sure all listitems up the stack are paragraphs
|
|
forEach( stack, paragraphify, this);
|
|
|
|
loose = true;
|
|
continue loose_search;
|
|
}
|
|
break;
|
|
} // loose_search
|
|
|
|
return ret;
|
|
};
|
|
})(),
|
|
|
|
blockquote: function blockquote( block, next ) {
|
|
if ( !block.match( /^>/m ) )
|
|
return undefined;
|
|
|
|
var jsonml = [];
|
|
|
|
// separate out the leading abutting block, if any. I.e. in this case:
|
|
//
|
|
// a
|
|
// > b
|
|
//
|
|
if ( block[ 0 ] !== ">" ) {
|
|
var lines = block.split( /\n/ ),
|
|
prev = [],
|
|
line_no = block.lineNumber;
|
|
|
|
// keep shifting lines until you find a crotchet
|
|
while ( lines.length && lines[ 0 ][ 0 ] !== ">" ) {
|
|
prev.push( lines.shift() );
|
|
line_no++;
|
|
}
|
|
|
|
var abutting = mk_block( prev.join( "\n" ), "\n", block.lineNumber );
|
|
jsonml.push.apply( jsonml, this.processBlock( abutting, [] ) );
|
|
// reassemble new block of just block quotes!
|
|
block = mk_block( lines.join( "\n" ), block.trailing, line_no );
|
|
}
|
|
|
|
|
|
// if the next block is also a blockquote merge it in
|
|
while ( next.length && next[ 0 ][ 0 ] === ">" ) {
|
|
var b = next.shift();
|
|
block = mk_block( block + block.trailing + b, b.trailing, block.lineNumber );
|
|
}
|
|
|
|
// Strip off the leading "> " and re-process as a block.
|
|
var input = block.replace( /^> ?/gm, "" ),
|
|
old_tree = this.tree,
|
|
processedBlock = this.toTree( input, [ "blockquote" ] ),
|
|
attr = extract_attr( processedBlock );
|
|
|
|
// If any link references were found get rid of them
|
|
if ( attr && attr.references ) {
|
|
delete attr.references;
|
|
// And then remove the attribute object if it's empty
|
|
if ( isEmpty( attr ) )
|
|
processedBlock.splice( 1, 1 );
|
|
}
|
|
|
|
jsonml.push( processedBlock );
|
|
return jsonml;
|
|
},
|
|
|
|
referenceDefn: function referenceDefn( block, next) {
|
|
var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/;
|
|
// interesting matches are [ , ref_id, url, , title, title ]
|
|
|
|
if ( !block.match(re) )
|
|
return undefined;
|
|
|
|
// make an attribute node if it doesn't exist
|
|
if ( !extract_attr( this.tree ) )
|
|
this.tree.splice( 1, 0, {} );
|
|
|
|
var attrs = extract_attr( this.tree );
|
|
|
|
// make a references hash if it doesn't exist
|
|
if ( attrs.references === undefined )
|
|
attrs.references = {};
|
|
|
|
var b = this.loop_re_over_block(re, block, function( m ) {
|
|
|
|
if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" )
|
|
m[2] = m[2].substring( 1, m[2].length - 1 );
|
|
|
|
var ref = attrs.references[ m[1].toLowerCase() ] = {
|
|
href: m[2]
|
|
};
|
|
|
|
if ( m[4] !== undefined )
|
|
ref.title = m[4];
|
|
else if ( m[5] !== undefined )
|
|
ref.title = m[5];
|
|
|
|
} );
|
|
|
|
if ( b.length )
|
|
next.unshift( mk_block( b, block.trailing ) );
|
|
|
|
return [];
|
|
},
|
|
|
|
para: function para( block ) {
|
|
// everything's a para!
|
|
return [ ["para"].concat( this.processInline( block ) ) ];
|
|
}
|
|
},
|
|
|
|
inline: {
|
|
|
|
__oneElement__: function oneElement( text, patterns_or_re, previous_nodes ) {
|
|
var m,
|
|
res;
|
|
|
|
patterns_or_re = patterns_or_re || this.dialect.inline.__patterns__;
|
|
var re = new RegExp( "([\\s\\S]*?)(" + (patterns_or_re.source || patterns_or_re) + ")" );
|
|
|
|
m = re.exec( text );
|
|
if (!m) {
|
|
// Just boring text
|
|
return [ text.length, text ];
|
|
}
|
|
else if ( m[1] ) {
|
|
// Some un-interesting text matched. Return that first
|
|
return [ m[1].length, m[1] ];
|
|
}
|
|
|
|
var res;
|
|
if ( m[2] in this.dialect.inline ) {
|
|
res = this.dialect.inline[ m[2] ].call(
|
|
this,
|
|
text.substr( m.index ), m, previous_nodes || [] );
|
|
}
|
|
// Default for now to make dev easier. just slurp special and output it.
|
|
res = res || [ m[2].length, m[2] ];
|
|
return res;
|
|
},
|
|
|
|
__call__: function inline( text, patterns ) {
|
|
|
|
var out = [],
|
|
res;
|
|
|
|
function add(x) {
|
|
//D:self.debug(" adding output", uneval(x));
|
|
if ( typeof x === "string" && typeof out[out.length-1] === "string" )
|
|
out[ out.length-1 ] += x;
|
|
else
|
|
out.push(x);
|
|
}
|
|
|
|
while ( text.length > 0 ) {
|
|
res = this.dialect.inline.__oneElement__.call(this, text, patterns, out );
|
|
text = text.substr( res.shift() );
|
|
forEach(res, add );
|
|
}
|
|
|
|
return out;
|
|
},
|
|
|
|
// These characters are intersting elsewhere, so have rules for them so that
|
|
// chunks of plain text blocks don't include them
|
|
"]": function () {},
|
|
"}": function () {},
|
|
|
|
__escape__ : /^\\[\\`\*_{}\[\]()#\+.!\-]/,
|
|
|
|
"\\": function escaped( text ) {
|
|
// [ length of input processed, node/children to add... ]
|
|
// Only esacape: \ ` * _ { } [ ] ( ) # * + - . !
|
|
if ( this.dialect.inline.__escape__.exec( text ) )
|
|
return [ 2, text.charAt( 1 ) ];
|
|
else
|
|
// Not an esacpe
|
|
return [ 1, "\\" ];
|
|
},
|
|
|
|
"![": function image( text ) {
|
|
|
|
// Unlike images, alt text is plain text only. no other elements are
|
|
// allowed in there
|
|
|
|
// ![Alt text](/path/to/img.jpg "Optional title")
|
|
// 1 2 3 4 <--- captures
|
|
var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*([^")]*?)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ );
|
|
|
|
if ( m ) {
|
|
if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" )
|
|
m[2] = m[2].substring( 1, m[2].length - 1 );
|
|
|
|
m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0];
|
|
|
|
var attrs = { alt: m[1], href: m[2] || "" };
|
|
if ( m[4] !== undefined)
|
|
attrs.title = m[4];
|
|
|
|
return [ m[0].length, [ "img", attrs ] ];
|
|
}
|
|
|
|
// ![Alt text][id]
|
|
m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ );
|
|
|
|
if ( m ) {
|
|
// We can't check if the reference is known here as it likely wont be
|
|
// found till after. Check it in md tree->hmtl tree conversion
|
|
return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), original: m[0] } ] ];
|
|
}
|
|
|
|
// Just consume the '!['
|
|
return [ 2, "![" ];
|
|
},
|
|
|
|
"[": function link( text ) {
|
|
|
|
var orig = String(text);
|
|
// Inline content is possible inside `link text`
|
|
var res = inline_until_char.call( this, text.substr(1), "]" );
|
|
|
|
// No closing ']' found. Just consume the [
|
|
if ( !res )
|
|
return [ 1, "[" ];
|
|
|
|
var consumed = 1 + res[ 0 ],
|
|
children = res[ 1 ],
|
|
link,
|
|
attrs;
|
|
|
|
// At this point the first [...] has been parsed. See what follows to find
|
|
// out which kind of link we are (reference or direct url)
|
|
text = text.substr( consumed );
|
|
|
|
// [link text](/path/to/img.jpg "Optional title")
|
|
// 1 2 3 <--- captures
|
|
// This will capture up to the last paren in the block. We then pull
|
|
// back based on if there a matching ones in the url
|
|
// ([here](/url/(test))
|
|
// The parens have to be balanced
|
|
var m = text.match( /^\s*\([ \t]*([^"']*)(?:[ \t]+(["'])(.*?)\2)?[ \t]*\)/ );
|
|
if ( m ) {
|
|
var url = m[1];
|
|
consumed += m[0].length;
|
|
|
|
if ( url && url[0] === "<" && url[url.length-1] === ">" )
|
|
url = url.substring( 1, url.length - 1 );
|
|
|
|
// If there is a title we don't have to worry about parens in the url
|
|
if ( !m[3] ) {
|
|
var open_parens = 1; // One open that isn't in the capture
|
|
for ( var len = 0; len < url.length; len++ ) {
|
|
switch ( url[len] ) {
|
|
case "(":
|
|
open_parens++;
|
|
break;
|
|
case ")":
|
|
if ( --open_parens === 0) {
|
|
consumed -= url.length - len;
|
|
url = url.substring(0, len);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process escapes only
|
|
url = this.dialect.inline.__call__.call( this, url, /\\/ )[0];
|
|
|
|
attrs = { href: url || "" };
|
|
if ( m[3] !== undefined)
|
|
attrs.title = m[3];
|
|
|
|
link = [ "link", attrs ].concat( children );
|
|
return [ consumed, link ];
|
|
}
|
|
|
|
// [Alt text][id]
|
|
// [Alt text] [id]
|
|
m = text.match( /^\s*\[(.*?)\]/ );
|
|
|
|
if ( m ) {
|
|
|
|
consumed += m[ 0 ].length;
|
|
|
|
// [links][] uses links as its reference
|
|
attrs = { ref: ( m[ 1 ] || String(children) ).toLowerCase(), original: orig.substr( 0, consumed ) };
|
|
|
|
link = [ "link_ref", attrs ].concat( children );
|
|
|
|
// We can't check if the reference is known here as it likely wont be
|
|
// found till after. Check it in md tree->hmtl tree conversion.
|
|
// Store the original so that conversion can revert if the ref isn't found.
|
|
return [ consumed, link ];
|
|
}
|
|
|
|
// [id]
|
|
// Only if id is plain (no formatting.)
|
|
if ( children.length === 1 && typeof children[0] === "string" ) {
|
|
|
|
attrs = { ref: children[0].toLowerCase(), original: orig.substr( 0, consumed ) };
|
|
link = [ "link_ref", attrs, children[0] ];
|
|
return [ consumed, link ];
|
|
}
|
|
|
|
// Just consume the "["
|
|
return [ 1, "[" ];
|
|
},
|
|
|
|
|
|
"<": function autoLink( text ) {
|
|
var m;
|
|
|
|
if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) !== null ) {
|
|
if ( m[3] )
|
|
return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ];
|
|
else if ( m[2] === "mailto" )
|
|
return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ];
|
|
else
|
|
return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ];
|
|
}
|
|
|
|
return [ 1, "<" ];
|
|
},
|
|
|
|
"`": function inlineCode( text ) {
|
|
// Inline code block. as many backticks as you like to start it
|
|
// Always skip over the opening ticks.
|
|
var m = text.match( /(`+)(([\s\S]*?)\1)/ );
|
|
|
|
if ( m && m[2] )
|
|
return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ];
|
|
else {
|
|
// TODO: No matching end code found - warn!
|
|
return [ 1, "`" ];
|
|
}
|
|
},
|
|
|
|
" \n": function lineBreak() {
|
|
return [ 3, [ "linebreak" ] ];
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
// Meta Helper/generator method for em and strong handling
|
|
function strong_em( tag, md ) {
|
|
|
|
var state_slot = tag + "_state",
|
|
other_slot = tag === "strong" ? "em_state" : "strong_state";
|
|
|
|
function CloseTag(len) {
|
|
this.len_after = len;
|
|
this.name = "close_" + md;
|
|
}
|
|
|
|
return function ( text ) {
|
|
|
|
if ( this[state_slot][0] === md ) {
|
|
// Most recent em is of this type
|
|
//D:this.debug("closing", md);
|
|
this[state_slot].shift();
|
|
|
|
// "Consume" everything to go back to the recrusion in the else-block below
|
|
return[ text.length, new CloseTag(text.length-md.length) ];
|
|
}
|
|
else {
|
|
// Store a clone of the em/strong states
|
|
var other = this[other_slot].slice(),
|
|
state = this[state_slot].slice();
|
|
|
|
this[state_slot].unshift(md);
|
|
|
|
//D:this.debug_indent += " ";
|
|
|
|
// Recurse
|
|
var res = this.processInline( text.substr( md.length ) );
|
|
//D:this.debug_indent = this.debug_indent.substr(2);
|
|
|
|
var last = res[res.length - 1];
|
|
|
|
//D:this.debug("processInline from", tag + ": ", uneval( res ) );
|
|
|
|
var check = this[state_slot].shift();
|
|
if ( last instanceof CloseTag ) {
|
|
res.pop();
|
|
// We matched! Huzzah.
|
|
var consumed = text.length - last.len_after;
|
|
return [ consumed, [ tag ].concat(res) ];
|
|
}
|
|
else {
|
|
// Restore the state of the other kind. We might have mistakenly closed it.
|
|
this[other_slot] = other;
|
|
this[state_slot] = state;
|
|
|
|
// We can't reuse the processed result as it could have wrong parsing contexts in it.
|
|
return [ md.length, md ];
|
|
}
|
|
}
|
|
}; // End returned function
|
|
}
|
|
|
|
Gruber.inline["**"] = strong_em("strong", "**");
|
|
Gruber.inline["__"] = strong_em("strong", "__");
|
|
Gruber.inline["*"] = strong_em("em", "*");
|
|
Gruber.inline["_"] = strong_em("em", "_");
|
|
|
|
Markdown.dialects.Gruber = Gruber;
|
|
Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block );
|
|
Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline );
|
|
|
|
|
|
|
|
var Maruku = DialectHelpers.subclassDialect( Gruber ),
|
|
extract_attr = MarkdownHelpers.extract_attr,
|
|
forEach = MarkdownHelpers.forEach;
|
|
|
|
Maruku.processMetaHash = function processMetaHash( meta_string ) {
|
|
var meta = split_meta_hash( meta_string ),
|
|
attr = {};
|
|
|
|
for ( var i = 0; i < meta.length; ++i ) {
|
|
// id: #foo
|
|
if ( /^#/.test( meta[ i ] ) )
|
|
attr.id = meta[ i ].substring( 1 );
|
|
// class: .foo
|
|
else if ( /^\./.test( meta[ i ] ) ) {
|
|
// if class already exists, append the new one
|
|
if ( attr["class"] )
|
|
attr["class"] = attr["class"] + meta[ i ].replace( /./, " " );
|
|
else
|
|
attr["class"] = meta[ i ].substring( 1 );
|
|
}
|
|
// attribute: foo=bar
|
|
else if ( /\=/.test( meta[ i ] ) ) {
|
|
var s = meta[ i ].split( /\=/ );
|
|
attr[ s[ 0 ] ] = s[ 1 ];
|
|
}
|
|
}
|
|
|
|
return attr;
|
|
};
|
|
|
|
function split_meta_hash( meta_string ) {
|
|
var meta = meta_string.split( "" ),
|
|
parts = [ "" ],
|
|
in_quotes = false;
|
|
|
|
while ( meta.length ) {
|
|
var letter = meta.shift();
|
|
switch ( letter ) {
|
|
case " " :
|
|
// if we're in a quoted section, keep it
|
|
if ( in_quotes )
|
|
parts[ parts.length - 1 ] += letter;
|
|
// otherwise make a new part
|
|
else
|
|
parts.push( "" );
|
|
break;
|
|
case "'" :
|
|
case '"' :
|
|
// reverse the quotes and move straight on
|
|
in_quotes = !in_quotes;
|
|
break;
|
|
case "\\" :
|
|
// shift off the next letter to be used straight away.
|
|
// it was escaped so we'll keep it whatever it is
|
|
letter = meta.shift();
|
|
/* falls through */
|
|
default :
|
|
parts[ parts.length - 1 ] += letter;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return parts;
|
|
}
|
|
|
|
Maruku.block.document_meta = function document_meta( block ) {
|
|
// we're only interested in the first block
|
|
if ( block.lineNumber > 1 )
|
|
return undefined;
|
|
|
|
// document_meta blocks consist of one or more lines of `Key: Value\n`
|
|
if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) )
|
|
return undefined;
|
|
|
|
// make an attribute node if it doesn't exist
|
|
if ( !extract_attr( this.tree ) )
|
|
this.tree.splice( 1, 0, {} );
|
|
|
|
var pairs = block.split( /\n/ );
|
|
for ( var p in pairs ) {
|
|
var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ),
|
|
key = m[ 1 ].toLowerCase(),
|
|
value = m[ 2 ];
|
|
|
|
this.tree[ 1 ][ key ] = value;
|
|
}
|
|
|
|
// document_meta produces no content!
|
|
return [];
|
|
};
|
|
|
|
Maruku.block.block_meta = function block_meta( block ) {
|
|
// check if the last line of the block is an meta hash
|
|
var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ );
|
|
if ( !m )
|
|
return undefined;
|
|
|
|
// process the meta hash
|
|
var attr = this.dialect.processMetaHash( m[ 2 ] ),
|
|
hash;
|
|
|
|
// if we matched ^ then we need to apply meta to the previous block
|
|
if ( m[ 1 ] === "" ) {
|
|
var node = this.tree[ this.tree.length - 1 ];
|
|
hash = extract_attr( node );
|
|
|
|
// if the node is a string (rather than JsonML), bail
|
|
if ( typeof node === "string" )
|
|
return undefined;
|
|
|
|
// create the attribute hash if it doesn't exist
|
|
if ( !hash ) {
|
|
hash = {};
|
|
node.splice( 1, 0, hash );
|
|
}
|
|
|
|
// add the attributes in
|
|
for ( var a in attr )
|
|
hash[ a ] = attr[ a ];
|
|
|
|
// return nothing so the meta hash is removed
|
|
return [];
|
|
}
|
|
|
|
// pull the meta hash off the block and process what's left
|
|
var b = block.replace( /\n.*$/, "" ),
|
|
result = this.processBlock( b, [] );
|
|
|
|
// get or make the attributes hash
|
|
hash = extract_attr( result[ 0 ] );
|
|
if ( !hash ) {
|
|
hash = {};
|
|
result[ 0 ].splice( 1, 0, hash );
|
|
}
|
|
|
|
// attach the attributes to the block
|
|
for ( var a in attr )
|
|
hash[ a ] = attr[ a ];
|
|
|
|
return result;
|
|
};
|
|
|
|
Maruku.block.definition_list = function definition_list( block, next ) {
|
|
// one or more terms followed by one or more definitions, in a single block
|
|
var tight = /^((?:[^\s:].*\n)+):\s+([\s\S]+)$/,
|
|
list = [ "dl" ],
|
|
i, m;
|
|
|
|
// see if we're dealing with a tight or loose block
|
|
if ( ( m = block.match( tight ) ) ) {
|
|
// pull subsequent tight DL blocks out of `next`
|
|
var blocks = [ block ];
|
|
while ( next.length && tight.exec( next[ 0 ] ) )
|
|
blocks.push( next.shift() );
|
|
|
|
for ( var b = 0; b < blocks.length; ++b ) {
|
|
var m = blocks[ b ].match( tight ),
|
|
terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ),
|
|
defns = m[ 2 ].split( /\n:\s+/ );
|
|
|
|
// print( uneval( m ) );
|
|
|
|
for ( i = 0; i < terms.length; ++i )
|
|
list.push( [ "dt", terms[ i ] ] );
|
|
|
|
for ( i = 0; i < defns.length; ++i ) {
|
|
// run inline processing over the definition
|
|
list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) );
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
return undefined;
|
|
}
|
|
|
|
return [ list ];
|
|
};
|
|
|
|
// splits on unescaped instances of @ch. If @ch is not a character the result
|
|
// can be unpredictable
|
|
|
|
Maruku.block.table = function table ( block ) {
|
|
|
|
var _split_on_unescaped = function( s, ch ) {
|
|
ch = ch || '\\s';
|
|
if ( ch.match(/^[\\|\[\]{}?*.+^$]$/) )
|
|
ch = '\\' + ch;
|
|
var res = [ ],
|
|
r = new RegExp('^((?:\\\\.|[^\\\\' + ch + '])*)' + ch + '(.*)'),
|
|
m;
|
|
while ( ( m = s.match( r ) ) ) {
|
|
res.push( m[1] );
|
|
s = m[2];
|
|
}
|
|
res.push(s);
|
|
return res;
|
|
};
|
|
|
|
var leading_pipe = /^ {0,3}\|(.+)\n {0,3}\|\s*([\-:]+[\-| :]*)\n((?:\s*\|.*(?:\n|$))*)(?=\n|$)/,
|
|
// find at least an unescaped pipe in each line
|
|
no_leading_pipe = /^ {0,3}(\S(?:\\.|[^\\|])*\|.*)\n {0,3}([\-:]+\s*\|[\-| :]*)\n((?:(?:\\.|[^\\|])*\|.*(?:\n|$))*)(?=\n|$)/,
|
|
i,
|
|
m;
|
|
if ( ( m = block.match( leading_pipe ) ) ) {
|
|
// remove leading pipes in contents
|
|
// (header and horizontal rule already have the leading pipe left out)
|
|
m[3] = m[3].replace(/^\s*\|/gm, '');
|
|
} else if ( ! ( m = block.match( no_leading_pipe ) ) ) {
|
|
return undefined;
|
|
}
|
|
|
|
var table = [ "table", [ "thead", [ "tr" ] ], [ "tbody" ] ];
|
|
|
|
// remove trailing pipes, then split on pipes
|
|
// (no escaped pipes are allowed in horizontal rule)
|
|
m[2] = m[2].replace(/\|\s*$/, '').split('|');
|
|
|
|
// process alignment
|
|
var html_attrs = [ ];
|
|
forEach (m[2], function (s) {
|
|
if (s.match(/^\s*-+:\s*$/))
|
|
html_attrs.push({align: "right"});
|
|
else if (s.match(/^\s*:-+\s*$/))
|
|
html_attrs.push({align: "left"});
|
|
else if (s.match(/^\s*:-+:\s*$/))
|
|
html_attrs.push({align: "center"});
|
|
else
|
|
html_attrs.push({});
|
|
});
|
|
|
|
// now for the header, avoid escaped pipes
|
|
m[1] = _split_on_unescaped(m[1].replace(/\|\s*$/, ''), '|');
|
|
for (i = 0; i < m[1].length; i++) {
|
|
table[1][1].push(['th', html_attrs[i] || {}].concat(
|
|
this.processInline(m[1][i].trim())));
|
|
}
|
|
|
|
// now for body contents
|
|
forEach (m[3].replace(/\|\s*$/mg, '').split('\n'), function (row) {
|
|
var html_row = ['tr'];
|
|
row = _split_on_unescaped(row, '|');
|
|
for (i = 0; i < row.length; i++)
|
|
html_row.push(['td', html_attrs[i] || {}].concat(this.processInline(row[i].trim())));
|
|
table[2].push(html_row);
|
|
}, this);
|
|
|
|
return [table];
|
|
};
|
|
|
|
Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) {
|
|
if ( !out.length )
|
|
return [ 2, "{:" ];
|
|
|
|
// get the preceeding element
|
|
var before = out[ out.length - 1 ];
|
|
|
|
if ( typeof before === "string" )
|
|
return [ 2, "{:" ];
|
|
|
|
// match a meta hash
|
|
var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ );
|
|
|
|
// no match, false alarm
|
|
if ( !m )
|
|
return [ 2, "{:" ];
|
|
|
|
// attach the attributes to the preceeding element
|
|
var meta = this.dialect.processMetaHash( m[ 1 ] ),
|
|
attr = extract_attr( before );
|
|
|
|
if ( !attr ) {
|
|
attr = {};
|
|
before.splice( 1, 0, attr );
|
|
}
|
|
|
|
for ( var k in meta )
|
|
attr[ k ] = meta[ k ];
|
|
|
|
// cut out the string and replace it with nothing
|
|
return [ m[ 0 ].length, "" ];
|
|
};
|
|
|
|
|
|
Markdown.dialects.Maruku = Maruku;
|
|
Markdown.dialects.Maruku.inline.__escape__ = /^\\[\\`\*_{}\[\]()#\+.!\-|:]/;
|
|
Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block );
|
|
Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline );
|
|
|
|
|
|
// Include all our depndencies and;
|
|
expose.Markdown = Markdown;
|
|
expose.parse = Markdown.parse;
|
|
expose.toHTML = Markdown.toHTML;
|
|
expose.toHTMLTree = Markdown.toHTMLTree;
|
|
expose.renderJsonML = Markdown.renderJsonML;
|
|
|
|
})(function() {
|
|
window.markdown = {};
|
|
return window.markdown;
|
|
}());
|
|
|
|
},{"util":30}]},{},[1])
|
|
(1)
|
|
}); |