Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial Commit.

v1.0
  • Loading branch information...
commit 2ddc9fd11a48aae43b9b21fb8dee5872bb34572c 0 parents
Ryan J Daw authored
Showing with 52,856 additions and 0 deletions.
  1. +2 −0  .gitattributes
  2. +2 −0  .gitignore
  3. +21 −0 LICENSE
  4. +4 −0 README.md
  5. +22 −0 api-browser.konf
  6. +7 −0 api-client.konf
  7. +22 −0 api-legacy.konf
  8. +18 −0 api-node.konf
  9. +136 −0 api/analytics.js
  10. 0  api/blank.js
  11. +264 −0 api/cont.js
  12. +367 −0 api/data.js
  13. +120 −0 api/data2.js
  14. +131 −0 api/deferrable.js
  15. +177 −0 api/enhance.js
  16. +138 −0 api/externals.js
  17. +311 −0 api/main.js
  18. +12 −0 api/noconflict.js
  19. +78 −0 api/stack.js
  20. +174 −0 api/tmpl.js
  21. +181 −0 api/util.js
  22. +1 −0  init.js
  23. +1,011 −0 lib/backbone.js
  24. +94 −0 lib/base_konf.konf
  25. +14 −0 lib/base_legacy_konf.konf
  26. +267 −0 lib/debug.js
  27. +37 −0 lib/defaultConf.konf
  28. +207 −0 lib/detect.konf
  29. +525 −0 lib/dust-core.js
  30. +132 −0 lib/fy/bubble.html
  31. +108 −0 lib/fy/bubble.js
  32. +126 −0 lib/fy/bubble2.js
  33. +61 −0 lib/fy/menu.html
  34. +64 −0 lib/fy/menu.js
  35. +42 −0 lib/fy/slide.html
  36. +245 −0 lib/fy/slide.js
  37. +8,376 −0 lib/jquery-1.5.2.js
  38. +8,981 −0 lib/jquery-1.6.2.js
  39. +9,046 −0 lib/jquery-1.6.4.js
  40. +8,865 −0 lib/jquery-1.6.js
  41. +5,144 −0 lib/jquery-base-1.5.2.js
  42. +1 −0  lib/jquery-latest.js
  43. +99 −0 lib/jquery/jquery.cookie.js
  44. +29 −0 lib/jquery/jquery.mapattributes.js
  45. +32 −0 lib/jquery/jquery.outerhtml.js
  46. +76 −0 lib/jquery/jquery.tap.js
  47. +480 −0 lib/json2.js
  48. +811 −0 lib/livereload.js
  49. +855 −0 lib/underscore.js
  50. +862 −0 lib/zepto.js
  51. +456 −0 scss/_normalize.scss
  52. +22 −0 scss/_reset.scss
  53. +24 −0 scss/modules/_bubble.scss
  54. +63 −0 scss/modules/_grid.scss
  55. +75 −0 scss/modules/_slide.scss
  56. +46 −0 test/detect-test.html
  57. +71 −0 test/enhance-test.html
  58. +167 −0 test/externals-test.html
  59. +13 −0 test/fixtures-externals/plaintext.html
  60. +11 −0 test/fixtures-unmobify/basic.html
  61. +28 −0 test/fixtures-unmobify/escaped.html
  62. +33 −0 test/fixtures-unmobify/legacy.html
  63. +22 −0 test/fixtures-unmobify/plaintext-leading-content.html
  64. +26 −0 test/fixtures-unmobify/plaintext-missing-body.html
  65. +32 −0 test/fixtures-unmobify/plaintext-missing-closing-head.html
  66. +33 −0 test/fixtures-unmobify/plaintext.html
  67. +226 −0 test/qunit/qunit.css
  68. +1,597 −0 test/qunit/qunit.js
  69. +17 −0 test/test-server.js
  70. +226 −0 test/unmobify-test.html
  71. +258 −0 test/util-test.html
  72. +89 −0 timing.js
  73. +108 −0 tmpl/analytics.tmpl
  74. +19 −0 tmpl/base_mobileHead.tmpl
  75. +58 −0 tmpl/base_root.tmpl
  76. +358 −0 unmobify.js
2  .gitattributes
@@ -0,0 +1,2 @@
+.* export-ignore
+test export-ignore
2  .gitignore
@@ -0,0 +1,2 @@
+*.swp
+.DS_STORE
21 LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) Mobify R&D Inc.
+http://www.mobify.com/
+
+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.
4 README.md
@@ -0,0 +1,4 @@
+Mobify.js
+=========
+
+For information on using Mobify.js see [http://portal.mobify.com/docs/](http://portal.mobify.com/docs/).
22 api-browser.konf
@@ -0,0 +1,22 @@
+{%rebase}
+
+{>"lib/jquery/jquery.cookie.js"/}
+{>"lib/jquery/jquery.outerhtml.js"/}
+{>"lib/jquery/jquery.mapattributes.js"/}
+{>"lib/dust-core.js"/}
+
+{>"api/analytics.js"/}
+{>"api/util.js"/}
+{>"api/externals.js"/}
+{>"unmobify.js"/}
+
+{>"api/data2.js"/}
+{>"api/stack.js"/}
+{>"api/cont.js"/}
+
+{>"api/tmpl.js"/}
+
+{>"api/enhance.js"/}
+{>"api/main.js"/}
+
+{/rebase}
7 api-client.konf
@@ -0,0 +1,7 @@
+{%rebase}
+
+{>"api/analytics.js"/}
+{>"api/externals.js"/}
+{>"api/enhance.js"/}
+
+{/rebase}
22 api-legacy.konf
@@ -0,0 +1,22 @@
+{%rebase}
+
+{>"lib/jquery/jquery.cookie.js"/}
+{>"lib/jquery/jquery.outerhtml.js"/}
+{>"lib/jquery/jquery.mapattributes.js"/}
+{>"lib/dust-core.js"/}
+
+{>"api/analytics.js"/}
+{>"api/util.js"/}
+{>"api/externals.js"/}
+{>"unmobify.js"/}
+
+{>"api/deferrable.js"/}
+{>"api/data.js"/}
+
+{>"api/tmpl.js"/}
+
+{>"api/enhance.js"/}
+
+{>"api/main.js"/}
+
+{/rebase}
18 api-node.konf
@@ -0,0 +1,18 @@
+{%rebase}
+
+{>"lib/jquery/jquery.cookie.js"/}
+{>"lib/jquery/jquery.outerhtml.js"/}
+{>"lib/jquery/jquery.mapattributes.js"/}
+{>"lib/dust-core.js"/}
+
+{>"api/util.js"/}
+
+{>"api/data2.js"/}
+{>"api/stack.js"/}
+{>"api/cont.js"/}
+
+{>"api/tmpl.js"/}
+
+{>"api/main.js"/}
+
+{/rebase}
136 api/analytics.js
@@ -0,0 +1,136 @@
+/* DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED */
+/* DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED */
+
+/* This is now only used by api-legacy js konfs. Look at analytics.tmpl in the
+ template directory for up to date info. */
+
+/* DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED */
+/* DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED */
+
+
+// TODO: It would be nice if these scripts were more natural in the mobify.js flow.
+// TODO: Rename these functions, they're not just about GA anymore.
+(function($, _) {
+ window._gaq = window._gaq || [];
+
+ var ga = Mobify.ga = {
+
+ init: function() {
+ // Load time rounded to nearest 100ms.
+ var ed = Mobify.evaluatedData;
+ var start;
+ try {
+ start = Mobify.timing.points[0][0];
+ } catch (err) {
+ start = undefined;
+ }
+
+ var loadTime = Math.round((Mobify.timing.addPoint('Done') - start) / 100) * 100;
+ var template = ed.bodyType || ed.rootPageType || 'miss';
+ //var buildDate = (ed.siteConfig.buildDate || (ed && ed.buildDate)) + '';
+ var buildDate = (
+ (ed.siteConfig && ed.siteConfig.buildDate) ||
+ (ed.buildDate)
+ ) + '';
+
+ // http://code.google.com/apis/analytics/docs/tracking/gaTrackingCustomVariables.html
+ // _setCustomVar scope levels:
+ // 1 => Visitor
+ // 2 => Session
+ // 3 => Page (default)
+
+ // TODO: If they have multiple domains we need to configure a single tracking.
+
+ // this looks weird but it's populated down in the push: definition
+ var ga_args = [['_setAccount', null]];
+ // If we have site or konf specified args, populate them here, otherwise assume domain 'none'
+ // Corollory: this means site specified arguments should ALWAYS include _setDomainName
+
+ var ga_domain = (function(hostname, domain_dict){
+ var domain = 'none';
+ if (! domain_dict) return domain;
+ var hostname_parts = hostname.split('.');
+
+ while (hostname_parts.length > 0) {
+ domain = domain_dict[hostname_parts.join('_')];
+ if (!! domain) return domain;
+ hostname_parts = hostname_parts.slice(1); // cut off the head and continue
+ }
+ // fallback, domain is set to 'none'
+ return domain;
+ })(window.location.hostname, (!! ed.siteConfig) ? ed.siteConfig.ga_domains : false );
+ ga_args.push(['_setDomainName', ((ga_domain === 'none') ? '' : '.') + ga_domain]);
+
+ var site_args = ed.gaOptions || ((!! (ed.siteConfig && ed.siteConfig.ga_options)) ? ed.siteConfig.ga_options : []);
+
+ // Our custom variables, some of which aren't currently being populated properly (timing, build_dt)
+ var ga_final = [
+ ['_setCustomVar', 1, 'loadTime', '' + loadTime],
+ ['_setCustomVar', 2, 't', template],
+ ['_setCustomVar', 3, 'build_dt', buildDate],
+ ['_setCustomVar', 4, 'mobi', 'y', 1],
+ ['_trackPageview'],
+ ['_trackPageLoadTime']
+ ];
+
+ var ga_args_array = ga_args.concat(site_args, ga_final);
+ ga.push.apply(this, ga_args_array);
+
+ var insertAt = document.getElementsByTagName('script')[0] || document.getElementsByTagName('head')[0];
+ var isSSL = location.protocol[4] == 's';
+
+ // JB: Would this ever really happen?
+ // PM: If clients aren't using GA in their site, yes. (Many ecommerce sites don't use GA.)
+ // QA Tracking. Load the QA script if its not already loaded.
+ if (!window._gat) {
+ var gaScript = document.createElement('script');
+ gaScript.onload = gaScript.onreadystatechange = ga.load;
+ gaScript.src = '//' + (isSSL ? 'ssl' : 'www') + '.google-analytics.com/ga.js';
+ insertAt.parentNode.insertBefore(gaScript, insertAt);
+ } else {
+ ga.load();
+ }
+
+ // Quantcast Tracking
+ var _qevents = window._qevents = window._qevents || [];
+ var qcScript = document.createElement('script');
+ qcScript.src = '//' + (isSSL ? 'secure' : 'edge') + '.quantserve.com/quant.js';
+ insertAt.parentNode.insertBefore(qcScript, insertAt);
+
+ _qevents.push({qacct:"p-eb0xvejp1OUw6"});
+ },
+
+ load: function() {
+ if (ga.loaded) return;
+ ga.loaded = true;
+ ga.push.apply(null, ga.q);
+ // Don't queue anymore.
+ ga.queue = ga.push;
+ },
+
+ loaded: false,
+
+ // Queue arguments to be pushed to _gaq on ga.load.
+ queue: function() {
+ [].push.apply(ga.q, [].slice.call(arguments));
+ },
+
+ q: [],
+
+ // Iterates through arguments and pushes them to _gaq.
+ // Replaces null values with gaId.
+ push: function() {
+ var ed = Mobify.evaluatedData;
+ var args = [].slice.call(arguments);
+ _.each(ed.gaId || ((!! (ed.siteConfig && ed.siteConfig.ga_account)) ? [ed.siteConfig.ga_account] : false) || [], function(gaId, i) {
+ var prefix = 'MOBIFY' + i;
+ _.each(args, function(arg, j) {
+ var data = arg.slice(0);
+ data[0] = prefix + '.' + data[0];
+ if (data[1] === null) data[1] = gaId;
+ _gaq.push(data);
+ });
+ });
+ }
+ };
+})(Mobify.$, Mobify._);
0  api/blank.js
No changes.
264 api/cont.js
@@ -0,0 +1,264 @@
+(function($, _, Mobify, undefined) {
+ var decodeAssignmentRe = /^([?!]?)(.*)$/
+ ,Location = window.Location
+ ,Stack = Mobify.data2.stack
+ ,Async = Mobify.data2.Async
+ ,Cont = Mobify.data2.cont = function(head, parent, idx, len, newEnv) {
+ Stack.call(this, head, parent, idx, len);
+
+ if (!parent) {
+ var root = this.root = this;
+ this.handlers = {}
+ this.pending = 0;
+ } else {
+ this.root = parent.root;
+ }
+
+ var oldEnv = parent && parent.env();
+ if (newEnv) {
+ this.env(oldEnv.extend(newEnv, idx, len));
+ } else if (!oldEnv) {
+ this.env(new Stack(newEnv || {}));
+ }
+ };
+
+ _.extend(Cont, {
+ importance : {'!' : 1, '?' : -1, '' : 0}
+ ,decodeAssignment : function(selector) {
+ parse = selector.toString().match(decodeAssignmentRe);
+ return {
+ importance: this.importance[parse[1]]
+ ,selector: parse[2]
+ }
+ }
+ });
+
+ Cont.prototype = _.extend(new Stack(), {
+ extend: function(head, idx, len, env) {
+ return new Cont(head, this, idx, len, env);
+ }
+ ,env: function(value) {
+ return (value !== undefined)
+ ? this.set('env', value)
+ : this.get('env');
+ }
+ ,source: function(value) {
+ return (value !== undefined)
+ ? this.set('source', value)
+ : this.get('source');
+ }
+ ,all: function() {
+ return _(this.env()).find(function(stack) {
+ return !stack.tail.tail;
+ }).head;
+ }
+ ,blankTarget: function() {
+ var source = this.source();
+ if (_.isArray(source)) return [];
+ if ($.isPlainObject(source)) return {};
+ }
+ ,_eval : function(source) {
+ var root = this.root;
+
+ var innerCont = this.extend({source: source}, this.index, this.length);
+ //innerCont.head.root = innerCont;
+
+ var result = innerCont.eval();
+ return result;
+ }
+ ,eval: function(source) {
+ if (source !== undefined) {
+ return this._eval(source);
+ }
+ var blank = this.blankTarget();
+ var value = blank
+ ? this.evalCollection(blank)
+ : this.evalLeaf()
+ ,root = this.root;
+ if (!this.parent && (this === root)) {
+ if (root.pending) {
+ if (!root.incomplete) {
+ root.incomplete = true;
+ this.on('assignReference', function() {
+ if (!root.pending) {
+ root.emit('complete', [this.all(), this]);
+ }
+ });
+ }
+ } else root.emit('complete', [value, this]);
+ }
+ return value;
+ }
+ ,evalLeaf : function() {
+ var source = this.source();
+ try {
+ if (!source) return source;
+
+ return (_.isFunction(source) && !source._async)
+ ? source.call(this.env().head, this)
+ : source;
+ } catch (e) { return e; }
+ }
+ ,evalCollection : function(value) {
+ var source = this.source(),
+ sourceLength = source.length,
+ continuation = this;
+
+ _.each(source, function(sourceFragment, idx) {
+ if (sourceFragment && (sourceFragment.jquery || sourceFragment.nodeType)
+ && (typeof idx == "string") && idx.indexOf('$')) {
+ var root = continuation.root;
+ var forgotten = root.forgotten = root.forgotten || [];
+ forgotten.push([idx, sourceFragment]);
+ }
+ continuation
+ .extend({source: sourceFragment}, idx, sourceLength, value)
+ .evalReference();
+ });
+ return value;
+ }
+
+ ,evalReference : function() {
+
+ var ref, value, cont = this
+ ,assignment = Cont.decodeAssignment(this.index);
+
+ if (assignment.importance >= this.get('laziness')) {
+ this.ref = ref = this.env().ref(assignment.selector, true);
+ if (!ref) {
+ debug.warn(assignment.selector
+ , " has a syntax error or points to object that does not exist");
+ return;
+ }
+ } else return;
+ value = this.eval();
+ if (value && value._async) {
+
+ ref.value = value;
+ if (ref.target && ref.key) {
+ ref.target[ref.key] = value;
+ }
+ var root = cont.root;
+
+ value.onresult(function(value) {
+ root.pending -= 1;
+ cont.assignReference(assignment, ref, value);
+ });
+
+ root.pending += 1;
+ } else this.assignReference(assignment, ref, value);
+ return value;
+ }
+ ,assignReference: function(assignment, ref, value) {
+ if (!(value instanceof Error)) {
+ if (ref.target && ref.key) {
+ ref.target[ref.key] = value;
+ } else if (this.tail) {
+ value = new Error(assignment.selector + " value can't be saved to " + ref.crumbs);
+ }
+ }
+ this.emit('assignReference', [assignment, ref, value]);
+ if (Mobify.config.isDebug) Mobify.timing.addSelector(ref.crumbs);
+
+ return value;
+ }
+ ,choose : function() {
+ var cont = this
+ ,root = cont.root
+ ,forgotten = root.forgotten = root.forgotten || []
+ ,branches = arguments
+ ,choices = root.choices = root.choices || {}
+ ,chosen = _.detect(branches, function(branch, idx) {
+ var attempt = new Mobify.data2.cont({source: branch, laziness: 1});
+ attempt.root = attempt;
+ attempt.env(cont.env().extend({}));
+
+ attempt.on('assignReference', Mobify.data2.gatherEmpties).eval();
+
+ [].push.apply(root.forgotten, attempt.forgotten || []);
+
+ return !(attempt.warnings && _.keys(attempt.warnings).length);
+ })
+ ,chosenCont = cont.extend({source: chosen}, cont.index, cont.of);
+
+ if (chosen) {
+ return choices[cont.ref.crumbs] = chosenCont.eval();
+ }
+ }
+ ,map: function(source, evaluatable) {
+ var sourceLength = source.length
+ ,continuation = this
+ ,result;
+
+ result = _.map(source, function(sourceFragment, idx) {
+ var cont = continuation
+ .extend({source: evaluatable}, idx, sourceLength, {
+ $: sourceFragment.tagName && $(sourceFragment).anchor()
+ , KEY: idx, LEN: sourceLength, THIS: sourceFragment
+ });
+ return cont.eval();
+ });
+ return result;
+
+ }
+ ,ajax: function(params, evaluatable) {
+ return Async(this, function(cont, async) {
+ $.ajax(cont.eval(params))
+ .success( function(responseData, status, xhr) {
+ if (!evaluatable) {
+ async.finish(responseData);
+ } else if (typeof responseData !== "string") {
+ cont.env(cont.env().extend({THIS: responseData}));
+ async.finish(cont.eval(evaluatable));
+ } else {
+ var context = $(Mobify.externals.disable(responseData));
+ cont.env(cont.env().extend({THIS: context, $: context.anchor()}));
+ async.finish(cont.eval(evaluatable));
+ }
+ }).error(function() {
+ async.finish(null);
+ })
+ });
+ }
+ ,tmpl: function(template, data) {
+ var args = arguments;
+
+ return Async(this, function(cont, async) {
+ if (args.length == 1) data = cont.all();
+ dust.render(template, data, function(err, out) {
+ if (err) {
+ async.finish(out);
+ debug.die(err);
+ } else async.finish(out);
+ });
+ });
+ }
+ ,data: function(selector, value) {
+ var get = (value === undefined)
+ ,ref = this.env().ref(selector);
+
+ if (!ref) return;
+
+ return get
+ ? ref.value
+ : ref.target[ref.key] = value;
+ }
+ ,on: function(event, handler) {
+ var allHandlers = this.root.handlers,
+ handlers = allHandlers[event] = allHandlers[event] || [];
+
+ handlers.push(handler);
+ return this;
+ }
+ ,emit: function(event, args) {
+ var current = this
+ ,continuation = this
+ ,allHandlers = this.root.handlers;
+
+ _.each(allHandlers[event] || [], function(handler) {
+ handler.apply(continuation, args);
+ });
+ }
+ });
+
+})(Mobify.$, Mobify._, Mobify);
367 api/data.js
@@ -0,0 +1,367 @@
+(function($, _) {
+
+ // Processing for data that will be fed to templating engine
+ // Data takes form of a regular JSON object, and is evaluated by:
+ // * Walking to inspect values inside arrays and objects
+ // * Returning >value< from {BASIC : >value< } expression without further processing
+ // * Replacing {CSS : >value< } with result of evaluation of selector through jQuery selector engine
+
+/* Feature use example
+play: {
+ "$root": _$('#main-nav'),
+ a: _$('a'),
+ b: M._data('a').parent(),
+ extra: M._map(
+ _$("> div", M._data('*sliderPanel')), {
+ cat: "orange",
+ product: M._data('*@this').attr('id'),
+ text: M._data('*@idx')
+ }
+ )
+} */
+
+var jQuery = $;
+
+ var isCollection = function(leaf) {
+ if (!leaf) {
+ return false;
+ }
+ return _.isArray(leaf)
+ || ($.isPlainObject(leaf)
+ && !leaf.deferrable)
+ && !(window.Location && (leaf instanceof window.Location));
+ },
+
+ evaluateLeaf = function(state, src) {
+ var ctx = state.ctx;
+ var env = state.env;
+ var result = src;
+
+ // console.log(ctx, env, result);
+
+ if (!result) {
+ return result;
+ }
+ if (_.isFunction(result)) {
+ try {
+ result = result.call(this, state);
+ } catch (e) {
+ debug.error(e);
+ result = undefined;
+ }
+ }
+
+ if (result && result.deferrable) {
+ result = result._eval(state);
+ }
+
+ return result;
+ },
+
+ dataSelectorRe = /(\.|\*|\?)/i,
+
+ evalBasicDataSelector = function(state, selector, address) {
+ var env = state.env,
+ result = env.stack,
+ getAddress,
+ token,
+ mandatory = true,
+ predicate = '.',
+
+ selector = selector.split(dataSelectorRe);
+
+ for (var i = 0; i < selector.length; i +=1 ) {
+ token = selector[i];
+ getAddress = address && (i === selector.length - 1);
+ switch (token) {
+ case "":
+ continue;
+ break;
+ case '?':
+ mandatory = false;
+ break;
+ case '.': case '*':
+ predicate = token;
+ break;
+ default:
+ if (predicate === ".") {
+ if ("head" in result) {
+ result = result.head ? result.head : env.global;
+ }
+
+ if (getAddress) {
+ return [result, token, mandatory];
+ } else {
+ result = result[token];
+ }
+ } else if (predicate === "*") {
+ while (true) {
+ if (!result || !("head" in result)) {
+ result = env.global;
+ break;
+ }
+ if (result.head && (token in result.head)) {
+ result = result.head;
+ break;
+ }
+ result = result.tail;
+ }
+
+ if (getAddress) {
+ return [result, token, mandatory];
+ } else {
+ result = result[token];
+ }
+ }
+ break;
+ }
+ }
+ if (!getAddress) return result;
+ },
+
+ dataSelectorIncludeRe = /([\[\]\{\}])/i,
+
+ evalDataSelector = function(state, selector, address) {
+ var stack = [[]];
+ var token, result;
+
+ if (_.isNumber(selector)) {
+ selector = selector.toString();
+ }
+ if (_.isString(selector)) {
+ selector = selector.split(dataSelectorIncludeRe);
+ }
+ for (var i = 0; i < selector.length; i +=1 ) {
+ token = selector[i];
+ switch (token) {
+ case '':
+ continue;
+ break;
+ case '{' : case '[' :
+ stack.push([]);
+ break;
+ case '}' : case ']' :
+ result = evalBasicDataSelector(state, stack.pop().join(''));
+ if (_.isUndefined(result)) {
+ return;
+ }
+ stack[stack.length - 1].push(result);
+ break;
+ default:
+ stack[stack.length - 1].push(token);
+ }
+ }
+ return evalBasicDataSelector(state, stack.pop().join(''), address)
+ },
+
+ evaluateNode = function(state, src, key, len) {
+ var ctx = state.ctx;
+ var env = state.env;
+ var srcIsCollection = isCollection(src);
+ var computedKey = evalDataSelector(state, key, true);
+ var parentDest = computedKey[0];
+ var destKey = computedKey[1];
+ var mandatory = computedKey[2];
+ var path = ctx.get('path').concat(key);
+ var innerEnv, innerCtx, dest;
+
+ if (srcIsCollection && _.isUndefined(parentDest[destKey])) {
+ dest = parentDest[destKey] = _.isArray(src) ? [] : {};
+ }
+
+ innerEnv = env.push(dest, destKey, len),
+ innerCtx = ctx.push({
+ src: src,
+ dest: dest,
+ path: path
+ }, destKey, len);
+
+ if (srcIsCollection) {
+ evaluateCollection({ctx: innerCtx, env: innerEnv});
+ return dest;
+ } else {
+ var result = evaluateLeaf({ctx: innerCtx, env: env}, src);
+ parentDest[destKey] = result;
+ // if (mandatory && isFalsey(result)
+ if (mandatory && ((result === null) || (result === undefined) || (result === '')
+ || ($.isPlainObject(result) && _.isEmpty(result))
+ || ((typeof result.length != 'undefined') && !result.length))) {
+ state.ctx.get('warnings').push([path.join('.'), result]);
+ }
+ }
+ },
+
+ evaluateCollection = function(state) {
+ _.each(state.ctx.get('src'), function(src, key, parentSrc) {
+ evaluateNode(state, src, key, parentSrc.length);
+ });
+ },
+
+ M = {
+ $ : Mobify.$,
+ _ : Mobify._,
+ _$: Mobify.deferrable.jQuery,
+ map: function(state, value, src) {
+ if (value && value.deferrable) {
+ value = value._eval(state);
+ }
+
+ if (value.jquery) value = value.toArray();
+ var newDest = [],
+ newState = {
+ ctx: state.ctx.push({src: src, dest: newDest})
+ };
+
+ _.each(value, function(v, key) {
+ if (v.nodeType) v = $(v);
+ newState.env = state.env.push({"@this" : v, "$root": v, "@idx" : key}).push(newDest);
+ evaluateNode(newState, src, key, value.length);
+ });
+ return newDest;
+ },
+ _map: function(value, src) {
+ return function(state) {
+ return M.map(state, value, src);
+ }
+ },
+ data: function(state, selector, value) {
+ var set = !_.isUndefined(value);
+ var address, result;
+ if (set) {
+ address = evalBasicDataSelector(state, selector, true);
+ if (value && value.deferrable) {
+ value = value._eval(state);
+ }
+ address[0][address[1]] = value;
+ } else {
+ value = evalBasicDataSelector(state, selector, false);
+ if (value && value.deferrable) {
+ value = value._eval(state);
+ }
+ }
+ return value;
+ },
+ _data: function(selector, value) {
+ return Mobify.deferrable.constructor(Mobify.deferrable.actuals, "data", arguments);
+ },
+
+ verbatim: _.identity,
+
+ choose: function() {
+ var map = arguments,
+ str = Mobify.config.location.pathname.slice(1);
+
+ return function(state) {
+ var result = _(map).detect(function(block) {
+ var value,
+ key = block[1],
+ detector = block[0];
+
+ if (_.isString(detector)) {
+ return $(detector, state.env.get('$root')).length;
+ } else if (detector.deferrable) {
+ value = detector._eval(state);
+ return (value.jQuery || _.isArray(value))
+ ? value.length
+ : value;
+ } else if (detector instanceof RegExp) {
+ return detector.test(str);
+ } else {
+ return detector(state);
+ }
+ });
+ if (!result || !("1" in result)) {
+ debug.group('All extracted data');
+ debug.log(state.ctx.get('destRoot'));
+ debug.groupEnd();
+
+ debug.die("error: " + state.ctx.get('path').pop() + " did not match any of choose function selectors");
+ }
+
+ result = evaluateLeaf(state, result[1]);
+
+ state.ctx.get('choices').push([state.ctx.get('path').join('.') + ' chose ' + result]);
+
+ return result;
+ }
+ },
+ cond: function(picker) {
+ var map = _.toArray(arguments).slice(1);
+
+ return function(state) {
+ var result,
+ target = evaluateLeaf(state, picker),
+ match = _(map).detect(function(block) {
+ return (target === block[0])
+ });
+
+ if (!match || !("1" in match)) {
+ debug.group('All extracted data');
+ debug.log(state.ctx.get('destRoot'));
+ debug.groupEnd();
+
+ debug.die("error: cond function in " + state.ctx.get('path').pop() + " was given ", target, ", which did not match any of conditions");
+ }
+
+ match = match[1];
+
+ result = evaluateNode(state, match, state.ctx.stack.index);
+ state.ctx.get('branches').push([state.ctx.get('path').join('.') + ' conditional was ' + target + ', producing ', result]);
+
+ return result;
+ }
+ }
+ }, data = {
+ M : M,
+ evaluate: function(state) {
+ var result = {},
+ pending = [],
+ destRoot = {},
+ ctx = dust.makeBase({
+ src: state.data,
+ destRoot: destRoot,
+ dest: destRoot,
+ path: [],
+ warnings: [],
+ branches: [],
+ choices: [],
+ getPath: function() { return this.path; }
+ }),
+ env = dust.makeBase({'$root': state.data.$html}).push(destRoot);
+
+
+ evaluateCollection({ctx: ctx, env: env});
+
+ debug.group('Choices');
+ _.map(ctx.get('choices'), function(log) {
+ debug.log.apply(debug, log);
+ })
+ debug.groupEnd();
+
+ debug.group('Branches');
+ _.map(ctx.get('branches'), function(log) {
+ debug.log.apply(debug, log);
+ })
+ debug.groupEnd();
+
+ if (ctx.get('warnings').length) {
+ debug.group('Unfilled values');
+ _.map(ctx.get('warnings'), function(warning) {
+ debug.warn.apply(debug, warning);
+ })
+ debug.groupEnd();
+ }
+
+ debug.group('All extracted data');
+ debug.log(destRoot);
+ debug.groupEnd();
+
+ return destRoot;
+ }
+
+
+ };
+
+ Mobify.data = data;
+
+})(Mobify.$, Mobify._);
120 api/data2.js
@@ -0,0 +1,120 @@
+(function($, _) {
+
+ var gatherEmpties = function(assignment, ref, value) {
+ var root = this.root
+ ,warnings = root.warnings = root.warnings || {}
+ ,overwrites = root.overwrites = root.overwrites || {};
+
+ var isEmpty = (value === null) || (value === undefined) || (value === '')
+ || ($.isPlainObject(value) && _.isEmpty(value))
+ || ((typeof value.length != 'undefined') && !value.length)
+ || (value instanceof Error)
+ || (!value && (this.get('laziness') > 0));
+
+ if (ref && (assignment.importance > -1)) {
+ if (isEmpty) {
+ warnings[ref.crumbs] = value;
+ } else {
+ delete warnings[ref.crumbs];
+ }
+ if ((ref.value !== undefined) && (!ref.value._async)) {
+ overwrites[ref.crumbs] = value;
+ }
+ }
+ }
+
+ ,logResult = function(value) {
+ if (!this.tail) {
+ debug.logGroup('warn', 'Unfilled values', this.warnings);
+ debug.logGroup('warn', 'Missing -> Wrappers', this.forgotten);
+ debug.logGroup('log', 'Overwrites', this.overwrites);
+ debug.logGroup('log', 'Choices', this.choices);
+
+ debug.group('All extracted data');
+ debug.log(value);
+ debug.groupEnd();
+ }
+ }
+ ,Async = function(cont, start) {
+ var async = function(chunk) {
+ var args = arguments;
+ return chunk.map(function(chunk) {
+ var handler = function(result) {
+ if (args[3] === "exists")
+ return chunk.exists(result, args[1], args[2]).end();
+
+ if (args[3] === "notexists")
+ return chunk.notexists(result, args[1], args[2]).end();
+
+ return (args[2] === null)
+ ? chunk.reference(result, args[1], args[3].auto, args[3].filters).end()
+ : chunk.section(result, args[1], args[2], args[3]).end();
+ }
+ listeners.push(handler);
+ })
+ };
+ var listeners = [], result, done;
+ async.onresult = function(f) {
+ if (done) {
+ f.call(async, result);
+ } else {
+ listeners.push(f);
+ }
+ }
+ async.finish = function(value) {
+ result = value;
+ done = true;
+ _.each(listeners, function(f) {
+ f.call(async, value)
+ });
+ }
+ async._async = true;
+
+ start.call(cont.env().head, cont, async);
+
+ return done ? result : async;
+ }
+
+ // TODO: If we want to Mobify this subb'd jQuery when / how should we do it?
+ ,anchor = function($root) {
+ var anchored = $.sub();
+
+ anchored.context = function() {
+ return $root || (Mobify.conf.data ? Mobify.conf.data.$html : '<div>');
+ }
+
+ anchored.fn.init = function(selector, context, rootQuery) {
+ $root = $root || (Mobify.conf.data && Mobify.conf.data.$html);
+
+ //Zepto won't have $.fn.init
+ return ($.fn.init || $).call(this, selector, context || anchored.context(), rootQuery);
+ };
+
+ anchored.fn.init.prototype = $.fn;
+
+ return anchored;
+ }
+
+ $.fn.anchor = function() {
+ return anchor(this);
+ };
+
+ Mobify.data2 = {
+ gatherEmpties: gatherEmpties
+ ,makeCont: function(opts) {
+ var cont = new Mobify.data2.cont(_.defaults(opts, {laziness: -1 }));
+ if (Mobify.config.isDebug) {
+ cont
+ .on('assignReference', gatherEmpties)
+ .on('complete', logResult);
+ }
+ return cont;
+ }
+ ,Async: Async
+ ,M: {
+ $ : anchor()
+ ,_ : _
+ ,async : Async
+ }
+ };
+})(Mobify.$, Mobify._);
131 api/deferrable.js
@@ -0,0 +1,131 @@
+(function($, _) {
+ var _toString = function() {
+ return this._toString
+ ? this._toString()
+ : '"' + this.toString() + '"';
+ };
+
+ var deferrable = Mobify.deferrable = {
+ actuals: {
+ _$: function(state, selector, context, rootQuery) {
+ return $(selector, context || state.env.get('$root'), rootQuery);
+ },
+ data: function() {
+ var M = Mobify.data.M;
+ return M.data.apply(M, arguments);
+ },
+ _toString: function() { return ''; }
+ },
+ constructor: function(target, name, args) {
+ return new this.fn._init(target, name, args);
+ },
+ jQuery: function() {
+ return Mobify.deferrable.constructor(Mobify.deferrable.actuals, "_$", arguments);
+ },
+ chain: function(name) {
+ return function() {
+ return Mobify.deferrable.constructor(this, name, arguments);
+ }
+ },
+ _throw: {}
+ }
+
+
+
+ deferrable.fn = {
+ deferrable: "deferrable",
+
+ // Make sure that _.isArray does not consider deferrables to be arrays
+ callee : true,
+ _toString : function() {
+ var sTarget = this.target && _toString.apply(this.target);
+ sTarget = sTarget ? sTarget + '.' : "";
+
+ var aArgs = _.map(this.args, function(arg) {
+ return _toString.apply(arg);
+ });
+ var sArgs = this.args.length ? aArgs.join(', ') : "";
+ return sTarget + this.name + '(' + sArgs + ')';
+ },
+ _init: function(target, name, args) {
+ this.target = target;
+ this.name = name;
+ this.args = _.toArray(args);
+ },
+
+ _eval: function(state) {
+ var result = this.__eval(state);
+ Mobify.timing.addPoint(' Selector ' + this._toString());
+ if (result == deferrable._throw) {
+ return;
+ }
+ return result;
+ },
+
+ __eval: function(state) {
+ var result,
+ target = this.target,
+ name = this.name,
+ args = _(this.args).map(function(arg) {
+ if (arg && arg.deferrable) {
+ var evaluatedArg = arg.__eval(state);
+ if (evaluatedArg == deferrable._throw) {
+ result = deferrable._throw;
+ return deferrable._throw;
+ }
+ return evaluatedArg;
+ } else if (_.isFunction(arg)) {
+ return function() {
+ return arg.apply(this, [state].concat(_.toArray(arguments)))
+ }
+ } else return arg;
+ });
+
+ if (target.deferrable) {
+ target = target.__eval(state);
+ if (target == deferrable._throw) {
+ return target;
+ }
+ }
+
+ if (!result) {
+ try {
+ if (target === deferrable.actuals) {
+ result = target[name].apply(target, [state].concat(args));
+ } else if (name == "_get") {
+ result = target[args[0]];
+ } else {
+ result = target[name].apply(target, args);
+ }
+ } catch(err) {
+ result = deferrable._throw;
+ }
+ }
+
+ return result;
+ }
+ };
+
+ var arrayMethods = "pop|push|reverse|shift|sort|splice|unshift|concat|join|slice|toSource|toString|indexOf|lastIndexOf|filter|forEach|every|map|some|reduce|reduceRight".split("|"),
+ stringMethods = "charAt|charCodeAt|concat|indexOf|lastIndexOf|localeCompare|match|quote|replace|search|slice|split|substr|substring|toLocaleLowerCase|toLocaleUpperCase|toLowerCase|toSource|toString|toUpperCase|trim|trimLeft|trimRight|valueOf".split("|"),
+ elementMethods = "insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|getElementsByTagName|getAttribute|querySelectorAll|webkitMatchesSelector|getElementsByClassName|contains|getBoundingClientRect|querySelector|hasAttribute|getAttributeNode|getAttributeNS|getElementsByTagNameNS|removeAttributeNS|getClientRects|scrollByPages|setAttributeNode|setAttributeNS|hasAttributeNS|blur|scrollIntoViewIfNeeded|setAttribute|scrollByLines|removeAttribute|setAttributeNodeNS|removeAttributeNode|getAttributeNodeNS|focus|scrollIntoView|addEventListener|appendChild|cloneNode|removeEventListener|compareDocumentPosition|insertBefore|removeChild|hasAttributes|isSupported|isEqualNode|dispatchEvent|isDefaultNamespace|hasChildNodes|normalize|replaceChild|isSameNode|lookupPrefix|lookupNamespaceURI".split('|'),
+ replacedMethods = [];
+
+ var x = $();
+ for (var i in x) {
+ if (_.isFunction(x[i])) {
+ replacedMethods.push(i);
+ }
+ }
+
+ var all = replacedMethods.concat(['_get'], arrayMethods, stringMethods, elementMethods);
+ _.each(all, function(x) {
+ deferrable.fn[x] = deferrable.chain(x);
+ });
+
+ deferrable.fn._init.prototype = deferrable.fn;
+
+})(Mobify.$, Mobify._);
+
+
+
177 api/enhance.js
@@ -0,0 +1,177 @@
+// Polyfills the `orientationchange` event.
+// Exposes Touch, OS, HD and Orientation properties on `Mobify.config`.
+// x-desktop, x-ios, x-android, x-blackberry, x-webos, x-nokia
+// x-notouch, x-touch
+// x-landscape, x-portrait
+// x-sd, x-hd x-hd15 x-hd20
+//
+// TODO: Windows Phone
+// http://windowsteamblog.com/windows_phone/b/wpdev/archive/2011/03/22/targeting-mobile-optimized-css-at-windows-phone-7.aspx
+(function(window, document, $) {
+
+// ###
+// # Orientation
+// ###
+
+// Android `orientation` support is broken.
+$.support.orientation = 'orientation' in window && 'onorientationchange' in window
+ && !/android/i.test(navigator.userAgent);
+
+var prevWidth
+ , prevOrientation
+ // Returns 'landscape' or 'portrait' based on the current orientation.
+ getOrientation = function() {
+ var docEl = document.documentElement;
+ return ($.support.orientation
+ // 0 in portrait, 1 in landscape
+ ? orientation % 180
+ // false in portrait, true in landscape
+ : docEl.clientWidth > docEl.clientHeight)
+ ? 'landscape'
+ : 'portrait';
+ }
+
+ // Some Android browsers (HTC Sensation) don't update widths immediately,
+ // so wait to trigger the event.
+ , orientationHandler = function() {
+ function triggerEvent() {
+ var width = document.documentElement.clientWidth
+ , orientation;
+
+ if (width == prevWidth) {
+ return setTimeout(triggerEvent, 250);
+ }
+
+ prevWidth = width;
+
+ orientation = getOrientation();
+ if (orientation != prevOrientation) {
+ prevOrientation = orientation;
+ $(window).trigger('orientationchange');
+ }
+ }
+
+ triggerEvent();
+ }
+
+// Polyfill `orientationchange` event.
+$.event.special.orientationchange = {
+ setup: function() {
+ if ($.support.orientation) return false;
+ $(window).bind('resize', orientationHandler);
+ },
+ teardown: function() {
+ if ($.support.orientation) return false;
+ $(window).unbind('resize', orientationHandler);
+ },
+ add: function(handleObj) {
+ var handler = handleObj.handler;
+ handleObj.handler = function(e) {
+ e.orientation = getOrientation();
+ return handler.apply(this, arguments);
+ };
+ }
+}
+
+// ###
+// # Device Properties
+// ###
+
+var $test = $('<div>', {id: 'mc-test'})
+ , style = $test[0].style
+
+ // Touch:
+ , touch = 'ontouchend' in document
+
+ // OS: ios, android, nokia, blackberry, webos, desktop
+ , osMatch = /(ip(od|ad|hone)|android|nokia|blackberry|webos)/gi.exec(navigator.userAgent)
+ , os = (osMatch && (osMatch[2] ? 'ios' : osMatch[1].toLowerCase())) || 'desktop'
+
+ // Device Pixel Ratio: 1, 1.5, 2.0
+ , dpr = 1
+ , q = [
+ 'screen and (-webkit-min-device-pixel-ratio:1.5)',
+ 'screen and (-webkit-min-device-pixel-ratio:2)',
+ ];
+
+// Use `devicePixelRatio` if available, falling back to querying using
+// `matchMedia` or manual media queries.
+if ('devicePixelRatio' in window) {
+ dpr = devicePixelRatio
+} else if (window.matchMedia) {
+ dpr = (matchMedia(q[1]).matches && 2) || (matchMedia(q[0]).matches && 1.5);
+} else {
+ var testHTML = '<style>'
+ + '@media ' + q[0] + '{#mc-test{color:red}}'
+ + '@media ' + q[1] + '{#mc-test{color:blue}}'
+ + '</style>'
+ , color
+ , m;
+
+ $test.hide().html(testHTML).appendTo(document.documentElement);
+
+ color = $test.css('color');
+
+ $test.remove();
+
+ // red - rgb(255,0,0) - q[0] - 1.5
+ // blue - rgb(0,0,255) - q[1] - 2.0
+ if (m = /255(\))?/gi.exec(color)) {
+ dpr = (m[1] && 2) || 1.5;
+ }
+}
+
+
+// ###
+// # Mobify.config
+// ###
+
+// Expose Touch, OS, HD and Orientation properties on `Mobify.config` for
+// use in templating.
+
+var config = Mobify.config;
+config.os = os;
+config.touch = touch;
+config.orientation = getOrientation();
+
+if (dpr > 1) {
+ config.HD = '@2x';
+ config.pixelRatio = dpr;
+} else {
+ config.HD = '';
+}
+
+// ###
+// # Mobify.enhance
+// ###
+
+// Update orientation class on `orientationchange`.
+// Add classes for Touch, OS, HD and Orientation to the HTML element.
+// .os
+// .orientation
+// .touch or .no-touch
+
+// ???
+// .sd or .hd .hd15 .hd2
+// .dpr1 .dpr15 .dpr2
+Mobify.enhance = function() {
+
+ var prevOrientation = getOrientation()
+ , classes = [os, (!touch ? 'no' : '') + 'touch', prevOrientation];
+
+ if (dpr > 1) {
+ classes.push('hd' + (dpr + '').replace(/[^\w]/, ''), 'hd');
+ } else {
+ classes.push('sd');
+ }
+
+ $('html').addClass('x-' + classes.join(' x-'));
+
+ $(window).bind('orientationchange', function() {
+ var orientation = getOrientation();
+ $('html').removeClass('x-' + prevOrientation).addClass('x-' + orientation);
+ prevOrientation = orientation;
+ })
+};
+
+})(this, document, Mobify.$);
138 api/externals.js
@@ -0,0 +1,138 @@
+(function($, _, Mobify, document) {
+ var attributesToEnable = {
+ link: ['href'],
+ img: ['src'],
+ iframe: ['src'],
+ style: ['media'],
+ script: ['src', 'type']
+ }
+ // JB: Why is `src` in attributes to kill?
+ , attributesToKill = {img: ['src', 'width', 'height']}
+ , attributesToDisable = _({}).extend(attributesToEnable, attributesToKill)
+ // Cache for expressions generated by `getAttrRe`.
+ , attrExps = {}
+ , tagRe = new RegExp(
+ '<('
+ + _.keys(attributesToEnable).join('|')
+ + ')([\\s\\S]*?)>', 'gi')
+ , openingScriptRe = new RegExp('(<script[\\s\\S]*?>)', 'gi')
+ , timing = Mobify.timing;
+
+ function getAttrRe(srcAttr) {
+ // <space><attr>='...'|"..."
+ return new RegExp(
+ '\\s+((?:'
+ + srcAttr
+ + ")\\s*=\\s*(?:'([\\s\\S])+?'|\"([\\s\\S])+?\"))", 'gi');
+ }
+
+ function deactivate(whole, tag, tail) {
+ tag = tag.toLowerCase();
+
+ var srcAttr = attributesToDisable[tag].join('|')
+ , attrRe = attrExps[srcAttr] = attrExps[srcAttr] || getAttrRe(srcAttr)
+ , killer = '';
+
+ // Give <script|style> an unknown type.
+ if (tag == 'script') {
+ killer = ' type="text/mobify-script"';
+ } else if (tag == 'style') {
+ killer = ' media="mobify-media"';
+ }
+
+ var result = '<' + tag + killer + tail.replace(attrRe, ' x-$1') + '>';
+ return result;
+ }
+
+ Mobify.externals = {
+ // Disables external attributes in HTML eg. <img x-src="src" />
+ // Doesn't disable attributes inside <script>, but don't get confused by
+ // <!-- <script> -->
+ disable: function(htmlStr) {
+ var splitRe = /(<!--[\s\S]*?-->)|(?=<\/script)/i
+ var bits = htmlStr.split(splitRe)
+ var ret = _(bits).map(function(fragment) {
+ // Fragment may be empty or just a comment, no need to escape those.
+ if (!fragment) return ''
+ if (/^<!--/.test(fragment)) return fragment
+
+ // parsed = [before, <script>, script contents]
+ // Disable before and the <script> itself.
+ var parsed = fragment.split(openingScriptRe)
+ parsed[0] = parsed[0].replace(tagRe, deactivate)
+ if (parsed[1]) parsed[1] = parsed[1].replace(tagRe, deactivate)
+ return parsed
+ });
+
+ return _.flatten(ret).join('');
+ },
+
+ // Enable external resources by re-enabling their attributes.
+ enable: function(htmlStr) {
+ return htmlStr
+ .replace(/\sx-(href|src|media|type)/g, ' $1')
+ .replace(/\s(type="text\/mobify-script"|media="mobify-media")/g, '');
+ },
+
+ // 1. Find the escaped nodes.
+ // 2. Get the original markup from the nodes.
+ // 3. Disable the markup.
+ // 4. Construct the shadow DOM.
+ grabContent: function(context) {
+ var context = context || document
+ , headMarkup = Mobify.headMarkup
+ , bodyMarkup = Mobify.bodyMarkup
+ , bodyOpenTag = Mobify.bodyOpenTagMarkup
+ , $sourceHead = $('head', context).first()
+ , $sourceBody = $('body', context).first()
+ , $sourceHtml = $('html', context).first();
+
+ if (!headMarkup && !bodyMarkup) {
+ // Extract escaped markup out of the DOM.
+ var rawMarkup = this.unescapeStyleHacks($sourceHead, $sourceBody);
+
+ // Store the escaped markup for unmobify() calls.
+ headMarkup = Mobify.headMarkup = rawMarkup[0];
+ bodyMarkup = Mobify.bodyMarkup = rawMarkup[1];
+ bodyOpenTag = Mobify.bodyOpenTagMarkup = rawMarkup[2];
+
+ timing.addPoint('Recovered Markup');
+ }
+
+ // Disable attributes that can cause loading of external resources
+ var disabledHeadMarkup = this.disable(headMarkup)
+ , disabledBodyMarkup = this.disable(bodyMarkup);
+
+ timing.addPoint('Disabled Markup');
+
+ // Reinflate HTML strings back into declawed DOM nodes.
+ var $head = $(document.createElement('head'))
+ .attr($sourceHead.mapAttributes())
+ .append(disabledHeadMarkup);
+
+ // jQuery can't create <body> with attributes, so
+ // create a <div> and take the attributes from that.
+ var bodyAttrs = bodyOpenTag
+ ? $(bodyOpenTag.replace(/^<body/i, '<div')).mapAttributes()
+ : $sourceBody.mapAttributes();
+
+ var $body = $(document.createElement('body'))
+ .attr(bodyAttrs)
+ .append(disabledBodyMarkup);
+
+ var $html = $(document.createElement('html'))
+ .attr($sourceHtml.mapAttributes())
+ .append($head)
+ .append($body);
+
+ timing.addPoint('Built Passive DOM');
+
+ // JB: Populate contents outside of $html eg. doctype.
+ return {
+ $head: $head,
+ $body: $body,
+ $html: $html
+ };
+ }
+ };
+})(Mobify.$, Mobify._, Mobify, document);
311 api/main.js
@@ -0,0 +1,311 @@
+/*
+
+Processing order description
+
+1. Escaping
+
+The Mobify tags identities whether a browser should recieve the transformation.
+If so, it escapes the document, allowing for markup capture without loading
+external resources.
+
+2. Source DOM Construction
+
+The escaped markup is retrieved from the DOM as a string. The escaped markup is
+transformed into a DOM node after resource loading attributes are escaped.
+
+3. Data select
+
+A data object is created by select nodes from the source DOM using DOM methods.
+
+4. Markup generation.
+
+A dust template is rendered with the data as a context, producing the final HTML.
+
+5. Document replacement
+
+The current document is replaced by using document.open/write/close. This makes
+the browser behave as if the templated HTML was the regular source.
+
+*/
+(function(window, document, Mobify, undefined) {
+ var config = Mobify.config
+ , $ = Mobify.$
+ , _ = Mobify._
+ , timing = Mobify.timing;
+
+ $.extend(config, {
+ isDebug: $.cookie('mobify-debug'),
+ location: window.location
+ });
+
+ // V6 tags don't set state, so we set it here.
+ // https://github.com/mobify/portal_app/issues/186
+ //
+ // mobify-path=<mobifyjs-path>
+ // mobify-all
+ // mobify-debug=<int>
+ if (config.tagVersion > 5) {
+ var hash = location.hash
+ , match
+
+ match = /mobify-path=([^&;]+)/g.exec(hash)
+ if (match) {
+ if (/mobify-all/.test(hash)) {
+ document.cookie = 'mobify-path=' + match[1] + '; path=/'
+ } else {
+ document.cookie = 'mobify-path=1; path=/'
+ sessionStorage['mobify-path'] = match[1]
+ }
+ }
+
+ match = /mobify-debug=([^&;]+)/g.exec(hash)
+ if (match) {
+ document.cookie = 'mobify-debug=' + match[1] + '; path=/'
+ }
+ }
+
+ // If loaded with tagInjector or Preview, set debug, otherwise debug is off.
+ // Setting `mobify-debug` cookie overrides defaults.
+ if (config.isDebug === null) {
+ config.isDebug = /^(?:https?:\/\/[^\/]+)?\/__\/|^\/\/(trust|preview).mobify.com\//.test(config.path)
+ ? 2 : 0;
+ }
+ config.isDebug = +config.isDebug;
+
+ // configFile my already exists if rendering server side, so only grab mobify.js script tag
+ // if configFile is undefined.
+ if (!config.configFile) {
+ // V6 moved mobify.js to the first script.
+ config.configFile = config.tagVersion > 5
+ ? $('script[src*="mobify.js"]').first().attr('src')
+ : $('script[src*="mobify.js"]').last().attr('src');
+ }
+
+ config.configDir = config.configFile.replace(/\/[^\/]*$/, '/');
+
+ if (window.enableStudioJS) {
+ Mobify.studioJS = {
+ get : function(key, callback) {
+ var handler = function(ev) {
+ if ((ev.data.command === 'html')
+ && (ev.data.dest === 'page')
+ && (ev.data.key === key)
+ && (ev.source === window)) {
+ window.removeEventListener("message", handler, false);
+ callback(ev.data.value, ev.data.key);
+ }
+ }
+ window.addEventListener("message", handler, false);
+ },
+ set: function(key, value) {
+ Mobify.studioJS[key] = value;
+ window.postMessage({
+ dest : 'extension',
+ command : 'html',
+ key: key,
+ value: value
+ }, '*');
+ }
+ };
+ }
+
+ Mobify.load = function(evaluationSource) {
+ var conf = Mobify.conf = Mobify.conf.call(Mobify.data2 && Mobify.data2.M, Mobify.data && Mobify.data.M)
+
+ timing.addPoint('Setup Conf');
+
+ // if evaluatedSource is undefined, grabContent, and if that is also undefined, unmobify
+ if (evaluationSource === undefined) {
+ evaluationSource = Mobify.externals.grabContent();
+ if (evaluationSource === undefined) return Mobify.unmobify();
+ }
+
+ evaluationSource.config = config;
+
+ // Deprecated.
+ if (Mobify.data) {
+ evaluationSource.mobileViewport = "width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no";
+ }
+
+ Mobify.conf.data = $.extend(evaluationSource, conf.data);
+ Mobify.evaluatedData = undefined;
+
+ // `DOMContentLoaded` will fire for the original `document` so hold the
+ // ready event until the new document loads.
+ $.holdReady(true);
+
+ // Extract data (interesting DOM nodes, or nested arrays, objects and primitives)
+ // See data.js for description of data extraction process and configuration map.
+ if (Mobify.data2) {
+
+ var cont = Mobify.data2
+ .makeCont({source: evaluationSource})
+ .on('complete', Mobify.acceptData);
+
+ Mobify.timing.addPoint('Prepared conf for evaluation');
+ Mobify.timing.addSelector('Start');
+ cont.eval();
+ } else {
+ Mobify.templateData(Mobify.data.evaluate({ data: evaluationSource }));
+ }
+ };
+
+ // acceptData added to the Mobify object so it can be overridden in server-side transformations.
+ Mobify.acceptData = function(data, cont) {
+ if (!Mobify.evaluatedData) {
+ Mobify.evaluatedData = data;
+ Mobify.evaluatedCont = cont;
+ timing.addPoint('Evaluated Conf');
+ }
+
+ var enabled = Mobify.externals.enable(data.OUTPUTHTML || '');
+ timing.addPoint('Enabled Markup');
+
+ // JB: When would the `DOMContentLoaded` branch be fired?
+ // If we requested more data to render the template, the document.readyState
+ // may be set before we render.
+ if (/complete|loaded|interactive/.test(document.readyState)) {
+ emitMarkup(enabled);
+ } else {
+ document.addEventListener('DOMContentLoaded', function DOMContentLoaded() {
+ document.removeEventListener('DOMContentLoaded', DOMContentLoaded, false);
+ emitMarkup(enabled);
+ }, false);
+ }
+ };
+
+ var emitMarkup = function(markup) {
+ timing.addPoint('DOMContentLoaded');
+
+ // If SASS compilation fails, the stylesheet will be blank except a SASS error.
+ // Set the document to visible to see these errors.
+ // if (config.isDebug) $('html').css('visibility', 'visible');
+ $('html').css('visibility', 'visible');
+
+ // iOS 4.3 does not clear listeners on `document` after `document.open`.
+ // Manually remove listeners added by the v4 tags.
+ Mobify.beforeload && document.removeEventListener('beforeload', Mobify.beforeload, true);
+
+ if (!markup) {
+ debug.warn('OUTPUTHTML is empty, unmobifying.');
+ return Mobify.unmobify();
+ }
+
+ // `document.open` clears events bound to `document`.
+ document.open();
+
+ // On `DOMContentLoaded` of the new `document`, fire the ready event on our
+ // jQuery. (the `DOMContentLoaded` it binds to fires early)
+ document.addEventListener('DOMContentLoaded', function DOMContentLoaded() {
+ document.removeEventListener('DOMContentLoaded', DOMContentLoaded, false);
+ $.holdReady(false);
+ }, false);
+
+ // In Webkit, `document.write` immediately executes inline scripts
+ // not preceded by an external resource.
+ document.write(markup);
+ timing.addPoint('Wrote Document');
+
+ Mobify.postDocWrite();
+
+ // Positioning this after the last `document.write`.
+ document.close();
+ if (Mobify.studioJS) {
+ Mobify.studioJS.get('renderHTML', function(markup) {
+ oldEmitMarkup(markup);
+ });
+ }
+
+ // Bind `orientationchange` listener and add `html` classes.
+ Mobify.enhance();
+
+ };
+ var oldEmitMarkup;
+ if (Mobify.studioJS) {
+ oldEmitMarkup = emitMarkup;
+ emitMarkup = function(markup) {
+ Mobify.studioJS.get('renderHTML', function(markup) {
+ oldEmitMarkup(markup);
+ });
+ Mobify.studioJS.set('resultHTML', markup || Mobify.studioJS.sourceHTML);
+ }
+ }
+
+ // Data1's templating func.
+ Mobify.templateData = function(evaluatedData) {
+ Mobify.evaluatedData = evaluatedData;
+ timing.addPoint('Evaluated Conf');
+
+ if (evaluatedData.unmobify) return Mobify.unmobify();
+
+ // Render data into template, receiving an HTML string.
+ dust.render('root', evaluatedData, function(err, out) {
+ if (err) debug.die(err);
+ evaluatedData.OUTPUTHTML = out;
+ Mobify.acceptData(evaluatedData);
+ });
+ };
+
+ // Bookkeeping after document.write is complete.
+ Mobify.postDocWrite = function() {
+ // Legacy google analytics call
+ if (Mobify.ga && Mobify.ga.init) {
+ Mobify.ga.init();
+ }
+
+ if (config.isDebug) {
+ timing.logPoints();
+ }
+
+ // JB: Livereload crashes Mobile Safari.
+ if (config.isDebug > 1) {
+ var m = (config.configFile.match(/^(?:https?:)?\/\/([^\/:]*)/) || '');
+
+ var hostname = m[1] || location.hostname;
+
+ if (!window.LiveReload) {
+ document.write('<script defer src="//cdn.mobify.com/livereload.js#'
+ + hostname + '"></script>');
+ }
+
+ if (config.isDebug > 2) {
+ Mobify.weinre(hostname + ':8081');
+ }
+ }
+ }
+
+ Mobify.weinre = function(host) {
+ if (window.WeinreServerURL) return;
+
+ var weinreScript = document.createElement('script');
+ weinreScript.src = 'http://' + host + '/target/target-script-min.js#anonymous';
+ document.body.appendChild(weinreScript);
+ }
+
+ // Kickstart processing, and submit analytics upon its success or failure
+ Mobify.init = function(mode) {
+ if (!/complete|loaded/.test(document.readyState)) {
+ window.setTimeout(Mobify.init, 100);
+ return;
+ }
+
+ if (mode == "livereload") {
+ window.setTimeout(Mobify.load, 0);
+ return;
+ }
+ config.started = true;
+
+ // Protection against Mobify.snippet calling `document.write` after
+ // `DOMContentLoaded`.
+ Mobify.snippet = $.noop;
+
+ Mobify.load();
+ };
+
+ if (Mobify.config.configFile.match(/\?livereload=\d+$/)) {
+ setTimeout(Mobify.load, 0);
+ }
+
+})(this, document, Mobify);
+
+Mobify.timing.addPoint('Walked Mobify.js');
12 api/noconflict.js
@@ -0,0 +1,12 @@
+$.extend(Mobify, {
+ $: $.noConflict(true),
+ _: _.noConflict()
+});
+window.debug = window.console;
+// detect.js schedules a desktopAnalytics() call for tracking on normal
+// untransformed desktop pages. This data can be used to determine what pages
+// should be transformed first.
+// However, if mobify.js was actually loaded, we are able to perform much
+// more detailed analytics information gathering, and ought to cancel that
+// desktopAnalytics call. The simplest way to do so is by nooping it.
+Mobify.desktopAnalytics = function(){};
78 api/stack.js
@@ -0,0 +1,78 @@
+(function($, _) {
+ var Stack = Mobify.data2.stack = function(head, parent, idx, len) {
+ this.tail = parent;
+ this.head = head;
+ this.index = idx;
+ this.len = len;
+ }
+ ,refRe = /^[^!?.]*(\.[^!?.]+)*$/i;
+
+ Stack.prototype = {
+ _mobifyStack: true
+ ,extend : function(head, idx, len) {
+ return new Stack(head, this, idx, len);
+ }
+ ,crumbs: function() {
+ var crumbs = _(this).chain().pluck('index')
+ .reject(function(idx) { return idx == undefined})
+ .reverse().value();
+
+ crumbs.toString = function() { return this.join('.') };
+ return crumbs;
+ }
+ ,get: function(key) {
+ var stack = this;
+ while (!(key in stack.head) && stack.tail)
+ stack = stack.tail;
+ return stack.head && stack.head[key];
+ }
+ ,set: function(key, value) {
+ return this.head[key] = value;
+ }
+ ,ref: function(selector, preferLocalAssignment) {
+
+ var parsed = (selector || "").toString().match(refRe)
+ ,stack = this, tokens, token, crumbs, head;
+
+ if (!parsed) return;
+
+ tokens = parsed[0].split('.');
+ token = tokens[0];
+
+ if (token
+ // If token is numeric and stack.head is an array, we should
+ // disable ascending logic and force local assignment. Otherwise,
+ // we risk ending up with unwanted overwrites in cases of nested arrays
+ && (isNaN(token) || !_.isArray(stack.head))) {
+ while (!(token in stack.head) && stack.tail)
+ stack = stack.tail;
+ }
+ if (!(token in stack.head) && preferLocalAssignment) stack = this;
+
+ crumbs = stack.crumbs();
+ crumbs.pop();
+ crumbs.push.apply(crumbs, tokens);
+
+ head = stack.head;
+
+ var value = head,
+ i = 0;
+ while ((tok = tokens[i++]) !== undefined) {
+ if (tok == "") {
+ value = head;
+ } else {
+ head = value;
+ token = tok;
+ value = head ? head[tok] : undefined;
+ }
+ }
+
+ if ((head && (token in head)) || preferLocalAssignment) return {
+ target: head
+ ,value: value
+ ,key: token
+ ,crumbs: crumbs
+ }
+ }
+ };
+})(Mobify.$, Mobify._);
174 api/tmpl.js
@@ -0,0 +1,174 @@
+(function($, _) {
+ var Async = Mobify.data2 && Mobify.data2.Async;
+
+ var Context = dust.makeBase({}).constructor,
+ Chunk = dust.stream('', {}).head.constructor,
+ oldExists = Chunk.prototype.exists,
+ oldNotExists = Chunk.prototype.notexists,
+ oldBlock = Chunk.prototype.block;
+
+ Chunk.prototype.exists = function(elem, context, bodies) {
+ if (typeof elem === "function") {
+ elem = elem(this, context, bodies, 'exists');
+ if (elem instanceof Chunk) {
+ return elem;
+ }
+ }
+ return oldExists.call(this, elem, context, bodies);
+ };
+
+ Chunk.prototype.notexists = function(elem, context, bodies) {
+ if (typeof elem === "function") {
+ elem = elem(this, context, bodies, 'notexists');
+ if (elem instanceof Chunk) {
+ return elem;
+ }
+ }
+ return oldNotExists.call(this, elem, context, bodies);
+ };
+
+ Chunk.prototype.block = function(elem, context, bodies) {
+ var topElem = elem ? elem.shift() : undefined;
+ if (topElem) {
+ context = new context.constructor(
+ context.stack
+ ,_.extend(
+ context.global || {},
+ { '_SUPER_' : function(_elem, context, _bodies) {
+ return _elem.block(elem, context, bodies);
+ }})
+ ,context.blocks);
+ }
+
+ return oldBlock.call(this, topElem, context, bodies);
+ };
+
+
+ var descend = function(ctx, down, i) {
+ while (ctx && i < down.length) {
+ if (ctx._async) {
+ var unwrap = Async($.noop);
+ ctx.onresult.push(function(result) {
+ unwrap.result(descend(result, down, i));
+ });
+ return unwrap;
+ }
+ ctx = ctx[down[i]];
+ i++;
+ }
+
+ return ctx;
+ }
+
+ Context.prototype.getAscendablePath = function(cur, down) {
+ var ctx = this.stack;
+
+ if (cur) return this.getPath(cur, down);
+ if (!ctx.isObject) return undefined;
+
+ ctx = this.get(down[0]);
+
+ return descend(ctx, down, 1);
+ };
+ Context.prototype.getBlock = function(key) {
+ var blocks = this.blocks;
+
+ if (!blocks) return [];
+
+ blocks = _.compact(_.pluck(blocks, key));
+ return blocks;
+ }
+
+ // Additional dust filters
+ // html returns node outerHTML
+ // innerHTML returns node innerHTML
+ // openTag and closeTag return first opening and last closing tags from a string
+ $.extend(dust.filters, {
+ h: function(value) {
+ if (_.isArray(value)) {
+ return _.map(value, dust.filters.h).join('');
+ }
+
+ if (value && value.nodeType) {
+ value = $(value);
+ }
+
+ return (value.outerHTML && value.outerHTML.apply)
+ ? value.outerHTML()
+ : dust.escapeHtml(value);
+ },
+
+ html: function(nodes) {
+ var wrapper = $(document.createElement('div')).append(
+ $(nodes).filter(function(i, el) {
+ return el && el.nodeType;
+ })
+ );
+ return wrapper.html();
+ },
+
+ innerHTML: function(node) {
+ if (node && node.jquery) {
+ node = node.toArray();
+ }
+ if (_.isArray(node)) {
+ var res = [];
+ for (var i = 0; i < node.length; i+= 1) {
+ res.push($(node).html());
+ }
+ return res.join('');
+ } else {
+ return $(node).html();
+ }
+ },
+
+ openTag: function(node) {
+ if (!node) return '';
+ if (node.length) {
+ node = node[0];
+ }
+ var attrs = $(node).mapAttributes();
+ //alert(JSON.stringify(attrs));
+ var attrStr = "", val;
+ for (var key in attrs) {
+ attrStr += ' ' + key + '="' + attrs[key] + '"';
+ }
+
+ var res = '<' + node.nodeName.toLowerCase() + attrStr + '>';
+ return res;
+ },
+
+ closeTag: function(node) {
+ var firstNode = node.length ? node[0] : node;
+ return firstNode ? '</' + firstNode.nodeName.toLowerCase() + '>' : "";
+ }
+ });
+
+ $.extend(dust.helpers, {
+ first: function(chunk, context, bodies) {
+ if (context.stack.index === 0) {
+ return bodies.block(chunk, context);
+ }
+ if (bodies['else']) return bodies['else'](chunk, context);
+ return chunk;
+ },
+ last: function(chunk, context, bodies) {
+ if (context.stack.index === context.stack.of - 1) {
+ return bodies.block(chunk, context);
+ }
+ if (bodies['else']) return bodies['else'](chunk, context);
+ return chunk;
+ }
+ })
+
+ var oldIsArray = dust.isArray;
+ dust.isArray = function(arr) {
+ return (arr && arr.jquery) || oldIsArray(arr);
+ }
+
+ var oldLoad = dust.load;
+ dust.load = function(name, chunk, context) {
+ return name ? oldLoad.apply(this, arguments) : chunk;
+ }
+
+})(Mobify.$, Mobify._);
181 api/util.js
@@ -0,0 +1,181 @@
+(function(window) {
+
+ var Mobify = window.Mobify
+ , $ = Mobify.$
+ , _ = Mobify._
+ , config = Mobify.config
+ , math = Math
+ , undefined;
+
+ // ###
+ // # Logging
+ // ###
+
+ debug.die = function() {
+ var args = _.toArray(arguments);
+ debug.group('(T_T) Fatal error (T_T)')
+ debug.error.apply(debug, args);
+ debug.groupEnd();
+
+ // unmobify() sets `Mobify.bail` when run. If set, we died in unmobify,
+ // and running it again won't help.
+ if (!config.isDebug && !Mobify.bail) {
+ Mobify.unmobify();
+ }
+
+ throw args;
+ };
+
+ debug.logGroup = function(fn, title, obj) {
+ var noneWritten = true;
+ _.each(obj, function(value, key) {
+ noneWritten && debug.group(title);
+ if (typeof key == "number") {
+ debug[fn].apply(window, value);
+ } else {
+ debug[fn](key, value);
+ }
+
+ noneWritten = false;
+ });
+
+ noneWritten || debug.groupEnd();
+ };
+
+ // ###
+ // # Utils
+ // ###
+
+ // Set optout cookie and reload to goto desktop.
+ // V3.0: mobify=0
+ // V3.X: mobify-js=-1
+ // V6.X: mobify-path=
+ //
+ // `url`: Optional url to redirect to after opting out.
+ Mobify.desktop = function(url) {
+ var tagVersion = config.tagVersion
+ , val = tagVersion > 5 ? '-path=' : (tagVersion ? '-js=-1' : '=0')
+
+ document.cookie = 'mobify' + val + '; path=/;';
+
+ if (url) {
+ location = url;
+ } else {
+ location.reload();
+ }
+ };
+
+ // i18n function converts in a list of language types and data and returns
+ // a function that allows you to grab translation keys from that data
+ Mobify.i18n = function(list, data) {
+ list.push("DEFAULT");
+
+ var i18nlookup = function(key) {
+ for(var i = 0; i < list.length; i++) {
+ var value = data[list[i]][key];
+ if (value) return value;
+ }
+ }
+ return i18nlookup;
+ };
+
+ // ###
+ // # SERVICES
+ // ###
+
+ // 1) Set a device-width viewport
+ // 2) Set a border or outline on the body
+ // 3) get document.body.clientWidth
+ // 4) Give me a goddamn prize
+ (function() {
+ var absolutify = document.createElement('a')
+ , hosts = [
+ '//ir0.mobify.com'
+ , '//ir1.mobify.com'
+ , '//ir2.mobify.com'
+ , '//ir3.mobify.com'
+ ]
+
+ // Hash `url` into a well distributed int.
+ , URLHash = Mobify.URLHash = function(url) {
+ var hc, len = url.length;
+
+ // Let's hash on 8 different character codes, chosen
+ // progresively back from the end of the URL, and xor 'em
+ hc = url.charCodeAt(len - 2 % len) ^ url.charCodeAt(len - 3 % len)
+ ^ url.charCodeAt(len - 5 % len) ^ url.charCodeAt(len - 7 % len)
+ ^ url.charCodeAt(len - 11 % len) ^ url.charCodeAt(len - 13 % len)
+ ^ url.charCodeAt(len - 17 % len) ^ url.charCodeAt(len - 19 % len)
+
+ // A little linear congruential generator action to shuffle
+ // things up, inspired by libc's random number generator
+ hc = (((hc * 1103515245) % 4294967296 + 12345) % 4294967296);
+ hc = (hc < 0) ? hc + 4294967296: hc;
+ return hc;
+ }
+
+ // Returns a URL suitable for use with irX.mobify.com.
+ // :host/:format:quality/:width/:height/:url
+ , getImageURL = Mobify.getImageURL = function(url, opts) {
+ opts = opts || {}
+
+ var host = hosts[URLHash(url) % hosts.length]
+ , bits = [host];
+
+ if (opts.format) {
+ bits.push(opts.format + (opts.quality || ''));
+ }
+
+ if (opts.maxWidth) {
+ bits.push(opts.maxWidth)
+
+ if (opts.maxHeight) {
+ bits.push(opts.maxHeight);
+ }
+ }
+
+ bits.push(url);
+ return bits.join('/');
+ }
+
+ // Alter the `src` of child images to pass through
+ // irX.mobify.com. Return the set of altered elements.
+ , resizeImages = $.fn.resizeImages = function(options) {
+ var opts = $.extend(resizeImages.defaults, typeof options == 'object' && options)
+ , dpr = window.devicePixelRatio;
+
+ if (typeof options == 'number') {
+ opts.maxWidth = options;
+ }
+
+ // https://github.com/Modernizr/Modernizr/pull/443
+ if (dpr) {
+ if (opts.maxWidth) {
+ opts.maxWidth = math.ceil(opts.maxWidth * dpr);
+ }
+
+ if (opts.maxHeight) {
+ opts.maxHeight = math.ceil(opts.maxHeight * dpr);
+ }
+ }
+
+ var $imgs = this.filter(opts.selector).add(this.find(opts.selector));
+ return $imgs.each(function() {
+ var attr = this.getAttribute(opts.attribute);
+ if (attr) {
+ absolutify.href = attr;
+ // This is slow, but its nice because it preloads the asset.
+ //this.src = getImageURL(absolutify.href, opts);
+ this.setAttribute('x-src', getImageURL(absolutify.href, opts))
+ // this.removeAttribute(opts.attribute);
+ }
+ });
+ };
+
+ resizeImages.defaults = {
+ selector: 'img[x-src]',
+ attribute: 'x-src'
+ };
+
+ })();
+})(this);
1  init.js
@@ -0,0 +1 @@
+Mobify.init();
1,011 lib/backbone.js
@@ -0,0 +1,1011 @@
+// Backbone.js 0.3.3
+// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://documentcloud.github.com/backbone
+
+(function(){
+
+ // Initial Setup
+ // -------------
+
+ // The top-level namespace. All public Backbone classes and modules will
+ // be attached to this. Exported for both CommonJS and the browser.
+ var Backbone;
+ if (typeof exports !== 'undefined') {
+ Backbone = exports;
+ } else {
+ Backbone = this.Backbone = {};
+ }
+
+ // Current version of the library. Keep in sync with `package.json`.
+ Backbone.VERSION = '0.3.3';
+
+ // Require Underscore, if we're on the server, and it's not already present.
+ var _ = this._;
+ if (!_ && (typeof require !== 'undefined')) _ = require("underscore")._;
+
+ // For Backbone's purposes, either jQuery or Zepto owns the `$` variable.
+ var $ = this.jQuery || this.Zepto;
+
+ // Turn on `emulateHTTP` to use support legacy HTTP servers. Setting this option will
+ // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a
+ // `X-Http-Method-Override` header.
+ Backbone.emulateHTTP = false;
+
+ // Turn on `emulateJSON` to support legacy servers that can't deal with direct
+ // `application/json` requests ... will encode the body as
+ // `application/x-www-form-urlencoded` instead and will send the model in a
+ // form param named `model`.
+ Backbone.emulateJSON = false;
+
+ // Backbone.Events
+ // -----------------
+