Просмотр исходного кода

Merge branch 'master' of github.com:sharelatex/web-sharelatex

Henry Oswald 12 лет назад
Родитель
Сommit
347671c89b
100 измененных файлов с 349 добавлено и 2771 удалено
  1. 1 1
      services/web/.gitignore
  2. 1 1
      services/web/Gruntfile.coffee
  3. 32 0
      services/web/README.md
  4. 1 0
      services/web/app/coffee/infrastructure/ExpressLocals.coffee
  5. 1 1
      services/web/app/templates/email/emailTemplate.html
  6. 1 1
      services/web/app/templates/email/shared_project_email_template.html
  7. 1 1
      services/web/app/views/general/sidebar.jade
  8. 1 1
      services/web/app/views/subscriptions/plans.jade
  9. 8 26
      services/web/app/views/templates.jade
  10. 0 41
      services/web/public/app.build.js
  11. 0 1429
      services/web/public/backbone.js
  12. 0 0
      services/web/public/brand/logo/banner-plain.png
  13. 0 0
      services/web/public/brand/logo/banner.png
  14. 0 0
      services/web/public/brand/logo/icon_128.png
  15. 0 0
      services/web/public/brand/logo/logo-128.png
  16. 0 0
      services/web/public/brand/logo/logo-32.png
  17. 0 0
      services/web/public/brand/logo/logo-64.png
  18. 0 0
      services/web/public/brand/logo/logosmall.png
  19. 0 0
      services/web/public/brand/pictos/code.png
  20. 0 0
      services/web/public/brand/pictos/collaborators.png
  21. 0 0
      services/web/public/brand/pictos/history.png
  22. 0 0
      services/web/public/brand/pictos/projects.png
  23. 0 0
      services/web/public/brand/pictos/settings.png
  24. 0 0
      services/web/public/brand/pictos/subscription.png
  25. 276 0
      services/web/public/brand/plans.less
  26. 0 1
      services/web/public/coffee/history/HistoryManager.coffee
  27. 0 17
      services/web/public/coffee/ide/MainAreaManager.coffee
  28. 1 2
      services/web/public/coffee/project-members/ProjectMembersManager.coffee
  29. 0 1
      services/web/public/coffee/settings/SettingsManager.coffee
  30. BIN
      services/web/public/img/about/coffeescript_logo.png
  31. BIN
      services/web/public/img/about/nodejs_logo.jpg
  32. BIN
      services/web/public/img/about/redis_logo.png
  33. BIN
      services/web/public/img/about/scribtex_logo.jpeg
  34. BIN
      services/web/public/img/add.png
  35. BIN
      services/web/public/img/aqua.jpg
  36. BIN
      services/web/public/img/arrow.png
  37. BIN
      services/web/public/img/arrow1.png
  38. BIN
      services/web/public/img/backward.png
  39. BIN
      services/web/public/img/book.png
  40. BIN
      services/web/public/img/clock.png
  41. BIN
      services/web/public/img/create-folder.png
  42. BIN
      services/web/public/img/documentation.png
  43. BIN
      services/web/public/img/favicon.png
  44. BIN
      services/web/public/img/folder-large.png
  45. BIN
      services/web/public/img/folder-open.png
  46. BIN
      services/web/public/img/forward.png
  47. BIN
      services/web/public/img/galaxy.jpg
  48. BIN
      services/web/public/img/icons/24/code.png
  49. BIN
      services/web/public/img/icons/24/collaborators.png
  50. BIN
      services/web/public/img/icons/24/history.png
  51. BIN
      services/web/public/img/icons/24/settings.png
  52. 3 0
      services/web/public/img/iconshock/README.txt
  53. 0 0
      services/web/public/img/iconshock/fit-to-height.png
  54. 0 0
      services/web/public/img/iconshock/fit-to-width.png
  55. 0 0
      services/web/public/img/iconshock/save.png
  56. 0 0
      services/web/public/img/iconshock/zoom-in.png
  57. 0 0
      services/web/public/img/iconshock/zoom-out.png
  58. BIN
      services/web/public/img/keyboard.png
  59. BIN
      services/web/public/img/list/45.png
  60. BIN
      services/web/public/img/list/48.png
  61. BIN
      services/web/public/img/list/50.png
  62. BIN
      services/web/public/img/list/56.png
  63. BIN
      services/web/public/img/log.png
  64. BIN
      services/web/public/img/logo/logo.gif
  65. BIN
      services/web/public/img/logo/main.acorn
  66. BIN
      services/web/public/img/nide.png
  67. 0 0
      services/web/public/img/nide/doc.png
  68. 0 0
      services/web/public/img/nide/down-arrow.png
  69. 0 0
      services/web/public/img/nide/folder.png
  70. 0 0
      services/web/public/img/nide/right-arrow.png
  71. BIN
      services/web/public/img/project1.png
  72. BIN
      services/web/public/img/remove.png
  73. BIN
      services/web/public/img/sad.png
  74. BIN
      services/web/public/img/screen_CS3.png
  75. BIN
      services/web/public/img/search.png
  76. BIN
      services/web/public/img/settings.png
  77. BIN
      services/web/public/img/share/facebook_button.png
  78. BIN
      services/web/public/img/share/mail.png
  79. BIN
      services/web/public/img/share/twitter_button.png
  80. 0 0
      services/web/public/img/silk/arrow-up.png
  81. 0 0
      services/web/public/img/silk/created.png
  82. 0 0
      services/web/public/img/silk/deleted.png
  83. 0 0
      services/web/public/img/silk/moved.png
  84. 22 0
      services/web/public/img/silk/readme.txt
  85. 0 0
      services/web/public/img/social/facebook-32.png
  86. 0 0
      services/web/public/img/social/link-32.png
  87. 0 0
      services/web/public/img/social/mail-32.png
  88. 0 0
      services/web/public/img/social/twitter-32.png
  89. BIN
      services/web/public/img/textures/concrete_wall.png
  90. BIN
      services/web/public/img/textures/polaroid.png
  91. BIN
      services/web/public/img/txture.png
  92. BIN
      services/web/public/img/user.png
  93. BIN
      services/web/public/img/users/3.png
  94. BIN
      services/web/public/img/users/numbers.png
  95. BIN
      services/web/public/img/users/numbers/1.png
  96. BIN
      services/web/public/img/users/numbers/2.png
  97. BIN
      services/web/public/img/users/user1.png
  98. BIN
      services/web/public/img/users/user2.png
  99. BIN
      services/web/public/img/washi.png
  100. 0 1248
      services/web/public/js/libs/fileuploader.js

+ 1 - 1
services/web/.gitignore

@@ -57,7 +57,7 @@ public/js/gui.js
 public/js/admin.js
 public/js/history/*
 public/stylesheets/mainStyle.css
-public/stylesheets/plans.css
+public/brand/plans.css
 public/minjs/
 
 public/js/main.js

+ 1 - 1
services/web/Gruntfile.coffee

@@ -74,7 +74,7 @@ module.exports = (grunt) ->
 					"public/stylesheets/mainStyle.css": "public/stylesheets/mainStyle.less"
 			plans:
 				files:
-					"public/stylesheets/plans.css": "public/stylesheets/less/plans.less"
+					"public/brand/plans.css": "public/brand/plans.less"
 
 		requirejs:
 			compile:

+ 32 - 0
services/web/README.md

@@ -0,0 +1,32 @@
+web-sharelatex
+==============
+
+web-sharelatex is the front-end web service of the open-source web-based collaborative LaTeX editor,
+ShareLaTeX. It serves all the HTML pages, CSS and javascript to the client. web-sharelatex also contains 
+a lot of logic around creating and editing projects, and account management.
+
+The rest of the ShareLaTeX stack, along with information about contributing can be found in the 
+[sharelatex/sharelatex](https://github.com/sharelatex/sharelatex) repository.
+
+License and Credits
+-------------------
+
+### Code
+
+All code is currently proprietary but will soon be licensed under a copyleft license.
+
+### Artwork
+
+#### Silk icon set 1.3
+
+We gratefully acknowledge [Mark James](http://www.famfamfam.com/lab/icons/silk/) for
+releasing his Silk icon set under the Creative Commons Attribution 2.5 license. Some
+of these icons are used within ShareLaTeX inside the `public/img/silk` directory
+
+#### IconShock icons
+
+We gratefully acknowledge [IconShock](http://www.iconshock.com) for use of the icons
+in the `public/img/iconshock` directory found via
+[findicons.com](http://findicons.com/icon/498089/height?id=526085#)
+
+

+ 1 - 0
services/web/app/coffee/infrastructure/ExpressLocals.coffee

@@ -22,6 +22,7 @@ for path in [
     "#{jsPath}libs/pdf.js",
     "#{jsPath}libs/pdf.worker.js",
     "/stylesheets/mainStyle.css"
+    "/brand/plans.css"
 ]
 	filePath = Path.join __dirname, "../../../", "public#{path}"
 	content = fs.readFileSync filePath

+ 1 - 1
services/web/app/templates/email/emailTemplate.html

@@ -336,7 +336,7 @@
                                             <td class="headerContent">
                                             
                                             	<!-- // Begin Module: Standard Header Image \\ -->
-                                            	<img src="https://www.sharelatex.com/img/logo/banner-plain.png" style="max-width:600px;" id="headerImage campaign-icon" mc:label="header_image" mc:edit="header_image" mc:allowdesigner mc:allowtext />
+                                            	<img src="https://www.sharelatex.com/brand/logo/banner-plain.png" style="max-width:600px;" id="headerImage campaign-icon" mc:label="header_image" mc:edit="header_image" mc:allowdesigner mc:allowtext />
                                             	<!-- // End Module: Standard Header Image \\ -->
                                             
                                             </td>

+ 1 - 1
services/web/app/templates/email/shared_project_email_template.html

@@ -327,7 +327,7 @@
                                 	<table border="0" cellpadding="0" cellspacing="0" width="600" id="templateHeader">
                                         <tr>
                                             <td class="headerContent">
-                                            	<img src="<%=view_data.sharelatex_url%>/img/logo/banner-plain.png" style="max-width:600px;" id="headerImage" />
+                                            	<img src="<%=view_data.sharelatex_url%>/brand/logo/banner-plain.png" style="max-width:600px;" id="headerImage" />
                                             </td>
                                         </tr>
                                     </table>

+ 1 - 1
services/web/app/views/general/sidebar.jade

@@ -20,5 +20,5 @@
 					a(href="/user/bonus").plain-link Recommend ShareLaTeX to your friends and we'll upgrade your account for free.
 				p
 					a(href="/user/bonus").btn.btn-success Get free stuff
-			img(src="/img/logo/lion-64.png").lion
+			img(src="/brand/logo/logo-64.png").lion
 

+ 1 - 1
services/web/app/views/subscriptions/plans.jade

@@ -151,7 +151,7 @@ block content
 						.tab-pane.student-pricing
 
 			include ../general/small-footer
-			link(rel='stylesheet', href='/stylesheets/plans.css?fingerprint='+fingerprint('/stylesheets/mainStyle.css'))	
+			link(rel='stylesheet', href='/brand/plans.css?fingerprint='+fingerprint('/brand/mainStyle.css'))	
 
 			script
 				mixpanel.track("Page Viewed", { name: "plans" })

+ 8 - 26
services/web/app/views/templates.jade

@@ -40,11 +40,11 @@
 				ul.dropdown-menu
 					li
 						a.js-new-file(href="#")
-							img(src="/img/doc.png")
+							img(src="/img/nide/doc.png")
 							| New File
 					li
 						a.js-new-folder(href="#")
-							img(src="/img/folder.png")
+							img(src="/img/nide/folder.png")
 							| New Folder
 					li
 						a.js-upload-file(href="#")
@@ -70,8 +70,8 @@
 	script(type="text/template")#folderTemplate
 		.entity-list-item(class="entity-{{ type }}", entity-type="{{ type }}", id="{{ id }}")
 			.toggle.js-toggle
-				img(src="/img/right-arrow.png").js-closed
-				img(src="/img/down-arrow.png").js-open
+				img(src="/img/nide/right-arrow.png").js-closed
+				img(src="/img/nide/down-arrow.png").js-open
 			.clickable.js-clickable
 				i(class="sprite-{{ type }}")
 				span.name {{ name }}
@@ -156,18 +156,6 @@
 	script(type="text/template")#loadingIndicatorTemplate
 		.loading
 
-	script(type="text/template")#settingsSideBarLinkTemplate
-		ul
-			li.root.project#settings(title='Show Project Settings')
-				img(src='/img/settings.png') 
-				span Settings
-
-	script(type="text/template")#usersSideBarLinkTemplate
-		ul
-			li.root.project#projectCollaberators(title='Show Project Collaborators')
-				img(src='/img/user.png') 
-				span Collaborators
-
 	script(type="text/template")#pdfSideBarLinkTemplate
 		ul
 			li.root.project#pdf(title='Show PDF', alt='pdf')
@@ -220,13 +208,13 @@
 			.pdfjs-list-view
 			.btn-group
 				button.btn.btn-info.js-fit-height
-					img(src="/img/fit-to-height.png")
+					img(src="/img/iconshock/fit-to-height.png")
 				button.btn.btn-info.js-fit-width
-					img(src="/img/fit-to-width.png")
+					img(src="/img/iconshock/fit-to-width.png")
 				button.btn.btn-info.js-zoom-out
-					img(src="/img/zoom-out.png")
+					img(src="/img/iconshock/zoom-out.png")
 				button.btn.btn-info.js-zoom-in
-					img(src="/img/zoom-in.png")
+					img(src="/img/iconshock/zoom-in.png")
 			.progress.progress-info
 				.bar
 				span Loading
@@ -329,12 +317,6 @@
 	script(type="text/template")#historyPanelTemplate
 		#revisionHistoryArea.fullEditorArea
 
-	script(type="text/template")#historySideBarLinkTemplate
-		ul
-			li.root.project#showHistory(title='Show project history', alt='history')
-				img(src='/img/clock.png') 
-				span History
-
 	script(type='text/template')#revisionAreaTemplate
 		#historyAreaWrapper
 			#historySideBar

+ 0 - 41
services/web/public/app.build.js

@@ -1,41 +0,0 @@
-({
-	appDir: "js",
-	baseUrl: "./",
-	dir: "minjs",
-	inlineText:false,
-	preserveLicenseComments:false,
-
-	paths : {
-		"underscore": "libs/underscore",
-		"jquery": "libs/jquery"
-	},
-	shim: {
-		"libs/backbone": {
-			deps: ["libs/underscore"]
-		},
-		"libs/pdfListView/PdfListView": {
-			deps: ["libs/pdf"]
-		},
-		"libs/pdf": {
-			deps: ["libs/compatibility"]
-		}
-	},
-
-	skipDirOptimize: true,
-
-	modules: [
-		{
-			name: "main",
-			exclude: ["jquery"]
-		}, {
-			name: "ide",
-			exclude: ["jquery"]
-		}, {
-			name: "home",
-			exclude: ["jquery"]
-		}, {
-			name: "list",
-			exclude: ["jquery"]
-		}
-	]
-})

+ 0 - 1429
services/web/public/backbone.js

@@ -1,1429 +0,0 @@
-//     Backbone.js 0.9.2
-
-//     (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
-//     Backbone may be freely distributed under the MIT license.
-//     For all details and documentation:
-//     http://backbonejs.org
-
-(function(root, factory) {
-  // Set up Backbone appropriately for the environment.
-  if (typeof exports !== 'undefined') {
-    // Node/CommonJS, no need for jQuery in that case.
-    factory(root, exports, require('underscore'));
-  } else if (typeof define === 'function' && define.amd) {
-    // AMD
-    define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
-      // Export global even in AMD case in case this script is loaded with
-      // others that may still expect a global Backbone.
-      root.Backbone = factory(root, exports, _, $);
-    });
-  } else {
-    // Browser globals
-    root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender));
-  }
-}(this, function(root, Backbone, _, $) {
-
-  // Initial Setup
-  // -------------
-
-  // Save the previous value of the `Backbone` variable, so that it can be
-  // restored later on, if `noConflict` is used.
-  var previousBackbone = root.Backbone;
-
-  // Create a local reference to slice/splice.
-  var slice = Array.prototype.slice;
-  var splice = Array.prototype.splice;
-
-  // Current version of the library. Keep in sync with `package.json`.
-  Backbone.VERSION = '0.9.2';
-
-  // Set the JavaScript library that will be used for DOM manipulation and
-  // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
-  // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
-  // alternate JavaScript library (or a mock library for testing your views
-  // outside of a browser).
-  Backbone.setDomLibrary = function(lib) {
-    $ = lib;
-  };
-
-  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
-  // to its previous owner. Returns a reference to this Backbone object.
-  Backbone.noConflict = function() {
-    root.Backbone = previousBackbone;
-    return Backbone;
-  };
-
-  // Turn on `emulateHTTP` to 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
-  // -----------------
-
-  // Regular expression used to split event strings
-  var eventSplitter = /\s+/;
-
-  // A module that can be mixed in to *any object* in order to provide it with
-  // custom events. You may bind with `on` or remove with `off` callback functions
-  // to an event; trigger`-ing an event fires all callbacks in succession.
-  //
-  //     var object = {};
-  //     _.extend(object, Backbone.Events);
-  //     object.on('expand', function(){ alert('expanded'); });
-  //     object.trigger('expand');
-  //
-  var Events = Backbone.Events = {
-
-    // Bind one or more space separated events, `events`, to a `callback`
-    // function. Passing `"all"` will bind the callback to all events fired.
-    on: function(events, callback, context) {
-
-      var calls, event, node, tail, list;
-      if (!callback) return this;
-      events = events.split(eventSplitter);
-      calls = this._callbacks || (this._callbacks = {});
-
-      // Create an immutable callback list, allowing traversal during
-      // modification.  The tail is an empty object that will always be used
-      // as the next node.
-      while (event = events.shift()) {
-        list = calls[event];
-        node = list ? list.tail : {};
-        node.next = tail = {};
-        node.context = context;
-        node.callback = callback;
-        calls[event] = {tail: tail, next: list ? list.next : node};
-      }
-
-      return this;
-    },
-
-    // Remove one or many callbacks. If `context` is null, removes all callbacks
-    // with that function. If `callback` is null, removes all callbacks for the
-    // event. If `events` is null, removes all bound callbacks for all events.
-    off: function(events, callback, context) {
-      var event, calls, node, tail, cb, ctx;
-
-      // No events, or removing *all* events.
-      if (!(calls = this._callbacks)) return;
-      if (!(events || callback || context)) {
-        delete this._callbacks;
-        return this;
-      }
-
-      // Loop through the listed events and contexts, splicing them out of the
-      // linked list of callbacks if appropriate.
-      events = events ? events.split(eventSplitter) : _.keys(calls);
-      while (event = events.shift()) {
-        node = calls[event];
-        delete calls[event];
-        if (!node || !(callback || context)) continue;
-        // Create a new list, omitting the indicated callbacks.
-        tail = node.tail;
-        while ((node = node.next) !== tail) {
-          cb = node.callback;
-          ctx = node.context;
-          if ((callback && cb !== callback) || (context && ctx !== context)) {
-            this.on(event, cb, ctx);
-          }
-        }
-      }
-
-      return this;
-    },
-
-    // Trigger one or many events, firing all bound callbacks. Callbacks are
-    // passed the same arguments as `trigger` is, apart from the event name
-    // (unless you're listening on `"all"`, which will cause your callback to
-    // receive the true name of the event as the first argument).
-    trigger: function(events) {
-      var event, node, calls, tail, args, all, rest;
-      if (!(calls = this._callbacks)) return this;
-      all = calls.all;
-      events = events.split(eventSplitter);
-      rest = slice.call(arguments, 1);
-
-      // For each event, walk through the linked list of callbacks twice,
-      // first to trigger the event, then to trigger any `"all"` callbacks.
-      while (event = events.shift()) {
-        if (node = calls[event]) {
-          tail = node.tail;
-          while ((node = node.next) !== tail) {
-            node.callback.apply(node.context || this, rest);
-          }
-        }
-        if (node = all) {
-          tail = node.tail;
-          args = [event].concat(rest);
-          while ((node = node.next) !== tail) {
-            node.callback.apply(node.context || this, args);
-          }
-        }
-      }
-
-      return this;
-    }
-
-  };
-
-  // Aliases for backwards compatibility.
-  Events.bind   = Events.on;
-  Events.unbind = Events.off;
-
-  // Backbone.Model
-  // --------------
-
-  // Create a new model, with defined attributes. A client id (`cid`)
-  // is automatically generated and assigned for you.
-  var Model = Backbone.Model = function(attributes, options) {
-    var defaults;
-    attributes || (attributes = {});
-    if (options && options.parse) attributes = this.parse(attributes);
-    if (defaults = getValue(this, 'defaults')) {
-      attributes = _.extend({}, defaults, attributes);
-    }
-    if (options && options.collection) this.collection = options.collection;
-    this.attributes = {};
-    this._escapedAttributes = {};
-    this.cid = _.uniqueId('c');
-    this.changed = {};
-    this._silent = {};
-    this._pending = {};
-    this.set(attributes, {silent: true});
-    // Reset change tracking.
-    this.changed = {};
-    this._silent = {};
-    this._pending = {};
-    this._previousAttributes = _.clone(this.attributes);
-    this.initialize.apply(this, arguments);
-  };
-
-  // Attach all inheritable methods to the Model prototype.
-  _.extend(Model.prototype, Events, {
-
-    // A hash of attributes whose current and previous value differ.
-    changed: null,
-
-    // A hash of attributes that have silently changed since the last time
-    // `change` was called.  Will become pending attributes on the next call.
-    _silent: null,
-
-    // A hash of attributes that have changed since the last `'change'` event
-    // began.
-    _pending: null,
-
-    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
-    // CouchDB users may want to set this to `"_id"`.
-    idAttribute: 'id',
-
-    // Initialize is an empty function by default. Override it with your own
-    // initialization logic.
-    initialize: function(){},
-
-    // Return a copy of the model's `attributes` object.
-    toJSON: function(options) {
-      return _.clone(this.attributes);
-    },
-
-    // Get the value of an attribute.
-    get: function(attr) {
-      return this.attributes[attr];
-    },
-
-    // Get the HTML-escaped value of an attribute.
-    escape: function(attr) {
-      var html;
-      if (html = this._escapedAttributes[attr]) return html;
-      var val = this.get(attr);
-      return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
-    },
-
-    // Returns `true` if the attribute contains a value that is not null
-    // or undefined.
-    has: function(attr) {
-      return this.get(attr) != null;
-    },
-
-    // Set a hash of model attributes on the object, firing `"change"` unless
-    // you choose to silence it.
-    set: function(key, value, options) {
-      var attrs, attr, val;
-
-      // Handle both `"key", value` and `{key: value}` -style arguments.
-      if (_.isObject(key) || key == null) {
-        attrs = key;
-        options = value;
-      } else {
-        attrs = {};
-        attrs[key] = value;
-      }
-
-      // Extract attributes and options.
-      options || (options = {});
-      if (!attrs) return this;
-      if (attrs instanceof Model) attrs = attrs.attributes;
-      if (options.unset) for (attr in attrs) attrs[attr] = void 0;
-
-      // Run validation.
-      if (!this._validate(attrs, options)) return false;
-
-      // Check for changes of `id`.
-      if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
-
-      var changes = options.changes = {};
-      var now = this.attributes;
-      var escaped = this._escapedAttributes;
-      var prev = this._previousAttributes || {};
-
-      // For each `set` attribute...
-      for (attr in attrs) {
-        val = attrs[attr];
-
-        // If the new and current value differ, record the change.
-        if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
-          delete escaped[attr];
-          (options.silent ? this._silent : changes)[attr] = true;
-        }
-
-        // Update or delete the current value.
-        options.unset ? delete now[attr] : now[attr] = val;
-
-        // If the new and previous value differ, record the change.  If not,
-        // then remove changes for this attribute.
-        if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
-          this.changed[attr] = val;
-          if (!options.silent) this._pending[attr] = true;
-        } else {
-          delete this.changed[attr];
-          delete this._pending[attr];
-        }
-      }
-
-      // Fire the `"change"` events.
-      if (!options.silent) this.change(options);
-      return this;
-    },
-
-    // Remove an attribute from the model, firing `"change"` unless you choose
-    // to silence it. `unset` is a noop if the attribute doesn't exist.
-    unset: function(attr, options) {
-      (options || (options = {})).unset = true;
-      return this.set(attr, null, options);
-    },
-
-    // Clear all attributes on the model, firing `"change"` unless you choose
-    // to silence it.
-    clear: function(options) {
-      (options || (options = {})).unset = true;
-      return this.set(_.clone(this.attributes), options);
-    },
-
-    // Fetch the model from the server. If the server's representation of the
-    // model differs from its current attributes, they will be overriden,
-    // triggering a `"change"` event.
-    fetch: function(options) {
-      options = options ? _.clone(options) : {};
-      var model = this;
-      var success = options.success;
-      options.success = function(resp, status, xhr) {
-        if (!model.set(model.parse(resp, xhr), options)) return false;
-        if (success) success(model, resp);
-      };
-      options.error = Backbone.wrapError(options.error, model, options);
-      return (this.sync || Backbone.sync).call(this, 'read', this, options);
-    },
-
-    // Set a hash of model attributes, and sync the model to the server.
-    // If the server returns an attributes hash that differs, the model's
-    // state will be `set` again.
-    save: function(key, value, options) {
-      var attrs, current;
-
-      // Handle both `("key", value)` and `({key: value})` -style calls.
-      if (_.isObject(key) || key == null) {
-        attrs = key;
-        options = value;
-      } else {
-        attrs = {};
-        attrs[key] = value;
-      }
-      options = options ? _.clone(options) : {};
-
-      // If we're "wait"-ing to set changed attributes, validate early.
-      if (options.wait) {
-        if (!this._validate(attrs, options)) return false;
-        current = _.clone(this.attributes);
-      }
-
-      // Regular saves `set` attributes before persisting to the server.
-      var silentOptions = _.extend({}, options, {silent: true});
-      if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
-        return false;
-      }
-
-      // After a successful server-side save, the client is (optionally)
-      // updated with the server-side state.
-      var model = this;
-      var success = options.success;
-      options.success = function(resp, status, xhr) {
-        var serverAttrs = model.parse(resp, xhr);
-        if (options.wait) {
-          delete options.wait;
-          serverAttrs = _.extend(attrs || {}, serverAttrs);
-        }
-        if (!model.set(serverAttrs, options)) return false;
-        if (success) {
-          success(model, resp);
-        } else {
-          model.trigger('sync', model, resp, options);
-        }
-      };
-
-      // Finish configuring and sending the Ajax request.
-      options.error = Backbone.wrapError(options.error, model, options);
-      var method = this.isNew() ? 'create' : 'update';
-      var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
-      if (options.wait) this.set(current, silentOptions);
-      return xhr;
-    },
-
-    // Destroy this model on the server if it was already persisted.
-    // Optimistically removes the model from its collection, if it has one.
-    // If `wait: true` is passed, waits for the server to respond before removal.
-    destroy: function(options) {
-      options = options ? _.clone(options) : {};
-      var model = this;
-      var success = options.success;
-
-      var triggerDestroy = function() {
-        model.trigger('destroy', model, model.collection, options);
-      };
-
-      if (this.isNew()) {
-        triggerDestroy();
-        return false;
-      }
-
-      options.success = function(resp) {
-        if (options.wait) triggerDestroy();
-        if (success) {
-          success(model, resp);
-        } else {
-          model.trigger('sync', model, resp, options);
-        }
-      };
-
-      options.error = Backbone.wrapError(options.error, model, options);
-      var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
-      if (!options.wait) triggerDestroy();
-      return xhr;
-    },
-
-    // Default URL for the model's representation on the server -- if you're
-    // using Backbone's restful methods, override this to change the endpoint
-    // that will be called.
-    url: function() {
-      var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
-      if (this.isNew()) return base;
-      return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
-    },
-
-    // **parse** converts a response into the hash of attributes to be `set` on
-    // the model. The default implementation is just to pass the response along.
-    parse: function(resp, xhr) {
-      return resp;
-    },
-
-    // Create a new model with identical attributes to this one.
-    clone: function() {
-      return new this.constructor(this.attributes);
-    },
-
-    // A model is new if it has never been saved to the server, and lacks an id.
-    isNew: function() {
-      return this.id == null;
-    },
-
-    // Call this method to manually fire a `"change"` event for this model and
-    // a `"change:attribute"` event for each changed attribute.
-    // Calling this will cause all objects observing the model to update.
-    change: function(options) {
-      options || (options = {});
-      var changing = this._changing;
-      this._changing = true;
-
-      // Silent changes become pending changes.
-      for (var attr in this._silent) this._pending[attr] = true;
-
-      // Silent changes are triggered.
-      var changes = _.extend({}, options.changes, this._silent);
-      this._silent = {};
-      for (var attr in changes) {
-        this.trigger('change:' + attr, this, this.get(attr), options);
-      }
-      if (changing) return this;
-
-      // Continue firing `"change"` events while there are pending changes.
-      while (!_.isEmpty(this._pending)) {
-        this._pending = {};
-        this.trigger('change', this, options);
-        // Pending and silent changes still remain.
-        for (var attr in this.changed) {
-          if (this._pending[attr] || this._silent[attr]) continue;
-          delete this.changed[attr];
-        }
-        this._previousAttributes = _.clone(this.attributes);
-      }
-
-      this._changing = false;
-      return this;
-    },
-
-    // Determine if the model has changed since the last `"change"` event.
-    // If you specify an attribute name, determine if that attribute has changed.
-    hasChanged: function(attr) {
-      if (!arguments.length) return !_.isEmpty(this.changed);
-      return _.has(this.changed, attr);
-    },
-
-    // Return an object containing all the attributes that have changed, or
-    // false if there are no changed attributes. Useful for determining what
-    // parts of a view need to be updated and/or what attributes need to be
-    // persisted to the server. Unset attributes will be set to undefined.
-    // You can also pass an attributes object to diff against the model,
-    // determining if there *would be* a change.
-    changedAttributes: function(diff) {
-      if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
-      var val, changed = false, old = this._previousAttributes;
-      for (var attr in diff) {
-        if (_.isEqual(old[attr], (val = diff[attr]))) continue;
-        (changed || (changed = {}))[attr] = val;
-      }
-      return changed;
-    },
-
-    // Get the previous value of an attribute, recorded at the time the last
-    // `"change"` event was fired.
-    previous: function(attr) {
-      if (!arguments.length || !this._previousAttributes) return null;
-      return this._previousAttributes[attr];
-    },
-
-    // Get all of the attributes of the model at the time of the previous
-    // `"change"` event.
-    previousAttributes: function() {
-      return _.clone(this._previousAttributes);
-    },
-
-    // Check if the model is currently in a valid state. It's only possible to
-    // get into an *invalid* state if you're using silent changes.
-    isValid: function() {
-      return !this.validate(this.attributes);
-    },
-
-    // Run validation against the next complete set of model attributes,
-    // returning `true` if all is well. If a specific `error` callback has
-    // been passed, call that instead of firing the general `"error"` event.
-    _validate: function(attrs, options) {
-      if (options.silent || !this.validate) return true;
-      attrs = _.extend({}, this.attributes, attrs);
-      var error = this.validate(attrs, options);
-      if (!error) return true;
-      if (options && options.error) {
-        options.error(this, error, options);
-      } else {
-        this.trigger('error', this, error, options);
-      }
-      return false;
-    }
-
-  });
-
-  // Backbone.Collection
-  // -------------------
-
-  // Provides a standard collection class for our sets of models, ordered
-  // or unordered. If a `comparator` is specified, the Collection will maintain
-  // its models in sort order, as they're added and removed.
-  var Collection = Backbone.Collection = function(models, options) {
-    options || (options = {});
-    if (options.model) this.model = options.model;
-    if (options.comparator) this.comparator = options.comparator;
-    this._reset();
-    this.initialize.apply(this, arguments);
-    if (models) this.reset(models, {silent: true, parse: options.parse});
-  };
-
-  // Define the Collection's inheritable methods.
-  _.extend(Collection.prototype, Events, {
-
-    // The default model for a collection is just a **Backbone.Model**.
-    // This should be overridden in most cases.
-    model: Model,
-
-    // Initialize is an empty function by default. Override it with your own
-    // initialization logic.
-    initialize: function(){},
-
-    // The JSON representation of a Collection is an array of the
-    // models' attributes.
-    toJSON: function(options) {
-      return this.map(function(model){ return model.toJSON(options); });
-    },
-
-    // Add a model, or list of models to the set. Pass **silent** to avoid
-    // firing the `add` event for every new model.
-    add: function(models, options) {
-      var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
-      options || (options = {});
-      models = _.isArray(models) ? models.slice() : [models];
-
-      // Begin by turning bare objects into model references, and preventing
-      // invalid models or duplicate models from being added.
-      for (i = 0, length = models.length; i < length; i++) {
-        if (!(model = models[i] = this._prepareModel(models[i], options))) {
-          throw new Error("Can't add an invalid model to a collection");
-        }
-        cid = model.cid;
-        id = model.id;
-        if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
-          dups.push(i);
-          continue;
-        }
-        cids[cid] = ids[id] = model;
-      }
-
-      // Remove duplicates.
-      i = dups.length;
-      while (i--) {
-        models.splice(dups[i], 1);
-      }
-
-      // Listen to added models' events, and index models for lookup by
-      // `id` and by `cid`.
-      for (i = 0, length = models.length; i < length; i++) {
-        (model = models[i]).on('all', this._onModelEvent, this);
-        this._byCid[model.cid] = model;
-        if (model.id != null) this._byId[model.id] = model;
-      }
-
-      // Insert models into the collection, re-sorting if needed, and triggering
-      // `add` events unless silenced.
-      this.length += length;
-      index = options.at != null ? options.at : this.models.length;
-      splice.apply(this.models, [index, 0].concat(models));
-      if (this.comparator) this.sort({silent: true});
-      if (options.silent) return this;
-      for (i = 0, length = this.models.length; i < length; i++) {
-        if (!cids[(model = this.models[i]).cid]) continue;
-        options.index = i;
-        model.trigger('add', model, this, options);
-      }
-      return this;
-    },
-
-    // Remove a model, or a list of models from the set. Pass silent to avoid
-    // firing the `remove` event for every model removed.
-    remove: function(models, options) {
-      var i, l, index, model;
-      options || (options = {});
-      models = _.isArray(models) ? models.slice() : [models];
-      for (i = 0, l = models.length; i < l; i++) {
-        model = this.getByCid(models[i]) || this.get(models[i]);
-        if (!model) continue;
-        delete this._byId[model.id];
-        delete this._byCid[model.cid];
-        index = this.indexOf(model);
-        this.models.splice(index, 1);
-        this.length--;
-        if (!options.silent) {
-          options.index = index;
-          model.trigger('remove', model, this, options);
-        }
-        this._removeReference(model);
-      }
-      return this;
-    },
-
-    // Add a model to the end of the collection.
-    push: function(model, options) {
-      model = this._prepareModel(model, options);
-      this.add(model, options);
-      return model;
-    },
-
-    // Remove a model from the end of the collection.
-    pop: function(options) {
-      var model = this.at(this.length - 1);
-      this.remove(model, options);
-      return model;
-    },
-
-    // Add a model to the beginning of the collection.
-    unshift: function(model, options) {
-      model = this._prepareModel(model, options);
-      this.add(model, _.extend({at: 0}, options));
-      return model;
-    },
-
-    // Remove a model from the beginning of the collection.
-    shift: function(options) {
-      var model = this.at(0);
-      this.remove(model, options);
-      return model;
-    },
-
-    // Get a model from the set by id.
-    get: function(id) {
-      if (id == null) return void 0;
-      return this._byId[id.id != null ? id.id : id];
-    },
-
-    // Get a model from the set by client id.
-    getByCid: function(cid) {
-      return cid && this._byCid[cid.cid || cid];
-    },
-
-    // Get the model at the given index.
-    at: function(index) {
-      return this.models[index];
-    },
-
-    // Return models with matching attributes. Useful for simple cases of `filter`.
-    where: function(attrs) {
-      if (_.isEmpty(attrs)) return [];
-      return this.filter(function(model) {
-        for (var key in attrs) {
-          if (attrs[key] !== model.get(key)) return false;
-        }
-        return true;
-      });
-    },
-
-    // Force the collection to re-sort itself. You don't need to call this under
-    // normal circumstances, as the set will maintain sort order as each item
-    // is added.
-    sort: function(options) {
-      options || (options = {});
-      if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
-      var boundComparator = _.bind(this.comparator, this);
-      if (this.comparator.length == 1) {
-        this.models = this.sortBy(boundComparator);
-      } else {
-        this.models.sort(boundComparator);
-      }
-      if (!options.silent) this.trigger('reset', this, options);
-      return this;
-    },
-
-    // Pluck an attribute from each model in the collection.
-    pluck: function(attr) {
-      return _.map(this.models, function(model){ return model.get(attr); });
-    },
-
-    // When you have more items than you want to add or remove individually,
-    // you can reset the entire set with a new list of models, without firing
-    // any `add` or `remove` events. Fires `reset` when finished.
-    reset: function(models, options) {
-      models  || (models = []);
-      options || (options = {});
-      for (var i = 0, l = this.models.length; i < l; i++) {
-        this._removeReference(this.models[i]);
-      }
-      this._reset();
-      this.add(models, _.extend({silent: true}, options));
-      if (!options.silent) this.trigger('reset', this, options);
-      return this;
-    },
-
-    // Fetch the default set of models for this collection, resetting the
-    // collection when they arrive. If `add: true` is passed, appends the
-    // models to the collection instead of resetting.
-    fetch: function(options) {
-      options = options ? _.clone(options) : {};
-      if (options.parse === undefined) options.parse = true;
-      var collection = this;
-      var success = options.success;
-      options.success = function(resp, status, xhr) {
-        collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
-        if (success) success(collection, resp);
-      };
-      options.error = Backbone.wrapError(options.error, collection, options);
-      return (this.sync || Backbone.sync).call(this, 'read', this, options);
-    },
-
-    // Create a new instance of a model in this collection. Add the model to the
-    // collection immediately, unless `wait: true` is passed, in which case we
-    // wait for the server to agree.
-    create: function(model, options) {
-      var coll = this;
-      options = options ? _.clone(options) : {};
-      model = this._prepareModel(model, options);
-      if (!model) return false;
-      if (!options.wait) coll.add(model, options);
-      var success = options.success;
-      options.success = function(nextModel, resp, xhr) {
-        if (options.wait) coll.add(nextModel, options);
-        if (success) {
-          success(nextModel, resp);
-        } else {
-          nextModel.trigger('sync', model, resp, options);
-        }
-      };
-      model.save(null, options);
-      return model;
-    },
-
-    // **parse** converts a response into a list of models to be added to the
-    // collection. The default implementation is just to pass it through.
-    parse: function(resp, xhr) {
-      return resp;
-    },
-
-    // Proxy to _'s chain. Can't be proxied the same way the rest of the
-    // underscore methods are proxied because it relies on the underscore
-    // constructor.
-    chain: function () {
-      return _(this.models).chain();
-    },
-
-    // Reset all internal state. Called when the collection is reset.
-    _reset: function(options) {
-      this.length = 0;
-      this.models = [];
-      this._byId  = {};
-      this._byCid = {};
-    },
-
-    // Prepare a model or hash of attributes to be added to this collection.
-    _prepareModel: function(model, options) {
-      options || (options = {});
-      if (!(model instanceof Model)) {
-        var attrs = model;
-        options.collection = this;
-        model = new this.model(attrs, options);
-        if (!model._validate(model.attributes, options)) model = false;
-      } else if (!model.collection) {
-        model.collection = this;
-      }
-      return model;
-    },
-
-    // Internal method to remove a model's ties to a collection.
-    _removeReference: function(model) {
-      if (this == model.collection) {
-        delete model.collection;
-      }
-      model.off('all', this._onModelEvent, this);
-    },
-
-    // Internal method called every time a model in the set fires an event.
-    // Sets need to update their indexes when models change ids. All other
-    // events simply proxy through. "add" and "remove" events that originate
-    // in other collections are ignored.
-    _onModelEvent: function(event, model, collection, options) {
-      if ((event == 'add' || event == 'remove') && collection != this) return;
-      if (event == 'destroy') {
-        this.remove(model, options);
-      }
-      if (model && event === 'change:' + model.idAttribute) {
-        delete this._byId[model.previous(model.idAttribute)];
-        this._byId[model.id] = model;
-      }
-      this.trigger.apply(this, arguments);
-    }
-
-  });
-
-  // Underscore methods that we want to implement on the Collection.
-  var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
-    'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
-    'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
-    'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
-    'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
-
-  // Mix in each Underscore method as a proxy to `Collection#models`.
-  _.each(methods, function(method) {
-    Collection.prototype[method] = function() {
-      return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
-    };
-  });
-
-  // Backbone.Router
-  // -------------------
-
-  // Routers map faux-URLs to actions, and fire events when routes are
-  // matched. Creating a new one sets its `routes` hash, if not set statically.
-  var Router = Backbone.Router = function(options) {
-    options || (options = {});
-    if (options.routes) this.routes = options.routes;
-    this._bindRoutes();
-    this.initialize.apply(this, arguments);
-  };
-
-  // Cached regular expressions for matching named param parts and splatted
-  // parts of route strings.
-  var namedParam    = /:\w+/g;
-  var splatParam    = /\*\w+/g;
-  var escapeRegExp  = /[-[\]{}()+?.,\\^$|#\s]/g;
-
-  // Set up all inheritable **Backbone.Router** properties and methods.
-  _.extend(Router.prototype, Events, {
-
-    // Initialize is an empty function by default. Override it with your own
-    // initialization logic.
-    initialize: function(){},
-
-    // Manually bind a single named route to a callback. For example:
-    //
-    //     this.route('search/:query/p:num', 'search', function(query, num) {
-    //       ...
-    //     });
-    //
-    route: function(route, name, callback) {
-      Backbone.history || (Backbone.history = new History);
-      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
-      if (!callback) callback = this[name];
-      Backbone.history.route(route, _.bind(function(fragment) {
-        var args = this._extractParameters(route, fragment);
-        callback && callback.apply(this, args);
-        this.trigger.apply(this, ['route:' + name].concat(args));
-        Backbone.history.trigger('route', this, name, args);
-      }, this));
-      return this;
-    },
-
-    // Simple proxy to `Backbone.history` to save a fragment into the history.
-    navigate: function(fragment, options) {
-      Backbone.history.navigate(fragment, options);
-    },
-
-    // Bind all defined routes to `Backbone.history`. We have to reverse the
-    // order of the routes here to support behavior where the most general
-    // routes can be defined at the bottom of the route map.
-    _bindRoutes: function() {
-      if (!this.routes) return;
-      var routes = [];
-      for (var route in this.routes) {
-        routes.unshift([route, this.routes[route]]);
-      }
-      for (var i = 0, l = routes.length; i < l; i++) {
-        this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
-      }
-    },
-
-    // Convert a route string into a regular expression, suitable for matching
-    // against the current location hash.
-    _routeToRegExp: function(route) {
-      route = route.replace(escapeRegExp, '\\$&')
-                   .replace(namedParam, '([^\/]+)')
-                   .replace(splatParam, '(.*?)');
-      return new RegExp('^' + route + '$');
-    },
-
-    // Given a route, and a URL fragment that it matches, return the array of
-    // extracted parameters.
-    _extractParameters: function(route, fragment) {
-      return route.exec(fragment).slice(1);
-    }
-
-  });
-
-  // Backbone.History
-  // ----------------
-
-  // Handles cross-browser history management, based on URL fragments. If the
-  // browser does not support `onhashchange`, falls back to polling.
-  var History = Backbone.History = function() {
-    this.handlers = [];
-    _.bindAll(this, 'checkUrl');
-  };
-
-  // Cached regex for cleaning leading hashes and slashes .
-  var routeStripper = /^[#\/]/;
-
-  // Cached regex for detecting MSIE.
-  var isExplorer = /msie [\w.]+/;
-
-  // Has the history handling already been started?
-  History.started = false;
-
-  // Set up all inheritable **Backbone.History** properties and methods.
-  _.extend(History.prototype, Events, {
-
-    // The default interval to poll for hash changes, if necessary, is
-    // twenty times a second.
-    interval: 50,
-
-    // Gets the true hash value. Cannot use location.hash directly due to bug
-    // in Firefox where location.hash will always be decoded.
-    getHash: function(windowOverride) {
-      var loc = windowOverride ? windowOverride.location : window.location;
-      var match = loc.href.match(/#(.*)$/);
-      return match ? match[1] : '';
-    },
-
-    // Get the cross-browser normalized URL fragment, either from the URL,
-    // the hash, or the override.
-    getFragment: function(fragment, forcePushState) {
-      if (fragment == null) {
-        if (this._hasPushState || forcePushState) {
-          fragment = window.location.pathname;
-          var search = window.location.search;
-          if (search) fragment += search;
-        } else {
-          fragment = this.getHash();
-        }
-      }
-      if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
-      return fragment.replace(routeStripper, '');
-    },
-
-    // Start the hash change handling, returning `true` if the current URL matches
-    // an existing route, and `false` otherwise.
-    start: function(options) {
-      if (History.started) throw new Error("Backbone.history has already been started");
-      History.started = true;
-
-      // Figure out the initial configuration. Do we need an iframe?
-      // Is pushState desired ... is it available?
-      this.options          = _.extend({}, {root: '/'}, this.options, options);
-      this._wantsHashChange = this.options.hashChange !== false;
-      this._wantsPushState  = !!this.options.pushState;
-      this._hasPushState    = !!(this.options.pushState && window.history && window.history.pushState);
-      var fragment          = this.getFragment();
-      var docMode           = document.documentMode;
-      var oldIE             = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
-
-      if (oldIE) {
-        this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
-        this.navigate(fragment);
-      }
-
-      // Depending on whether we're using pushState or hashes, and whether
-      // 'onhashchange' is supported, determine how we check the URL state.
-      if (this._hasPushState) {
-        $(window).bind('popstate', this.checkUrl);
-      } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
-        $(window).bind('hashchange', this.checkUrl);
-      } else if (this._wantsHashChange) {
-        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
-      }
-
-      // Determine if we need to change the base url, for a pushState link
-      // opened by a non-pushState browser.
-      this.fragment = fragment;
-      var loc = window.location;
-      var atRoot  = loc.pathname == this.options.root;
-
-      // If we've started off with a route from a `pushState`-enabled browser,
-      // but we're currently in a browser that doesn't support it...
-      if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
-        this.fragment = this.getFragment(null, true);
-        window.location.replace(this.options.root + '#' + this.fragment);
-        // Return immediately as browser will do redirect to new url
-        return true;
-
-      // Or if we've started out with a hash-based route, but we're currently
-      // in a browser where it could be `pushState`-based instead...
-      } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
-        this.fragment = this.getHash().replace(routeStripper, '');
-        window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
-      }
-
-      if (!this.options.silent) {
-        return this.loadUrl();
-      }
-    },
-
-    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
-    // but possibly useful for unit testing Routers.
-    stop: function() {
-      $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
-      clearInterval(this._checkUrlInterval);
-      History.started = false;
-    },
-
-    // Add a route to be tested when the fragment changes. Routes added later
-    // may override previous routes.
-    route: function(route, callback) {
-      this.handlers.unshift({route: route, callback: callback});
-    },
-
-    // Checks the current URL to see if it has changed, and if it has,
-    // calls `loadUrl`, normalizing across the hidden iframe.
-    checkUrl: function(e) {
-      var current = this.getFragment();
-      if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
-      if (current == this.fragment) return false;
-      if (this.iframe) this.navigate(current);
-      this.loadUrl() || this.loadUrl(this.getHash());
-    },
-
-    // Attempt to load the current URL fragment. If a route succeeds with a
-    // match, returns `true`. If no defined routes matches the fragment,
-    // returns `false`.
-    loadUrl: function(fragmentOverride) {
-      var fragment = this.fragment = this.getFragment(fragmentOverride);
-      var matched = _.any(this.handlers, function(handler) {
-        if (handler.route.test(fragment)) {
-          handler.callback(fragment);
-          return true;
-        }
-      });
-      return matched;
-    },
-
-    // Save a fragment into the hash history, or replace the URL state if the
-    // 'replace' option is passed. You are responsible for properly URL-encoding
-    // the fragment in advance.
-    //
-    // The options object can contain `trigger: true` if you wish to have the
-    // route callback be fired (not usually desirable), or `replace: true`, if
-    // you wish to modify the current URL without adding an entry to the history.
-    navigate: function(fragment, options) {
-      if (!History.started) return false;
-      if (!options || options === true) options = {trigger: options};
-      var frag = (fragment || '').replace(routeStripper, '');
-      if (this.fragment == frag) return;
-
-      // If pushState is available, we use it to set the fragment as a real URL.
-      if (this._hasPushState) {
-        if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
-        this.fragment = frag;
-        window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
-
-      // If hash changes haven't been explicitly disabled, update the hash
-      // fragment to store history.
-      } else if (this._wantsHashChange) {
-        this.fragment = frag;
-        this._updateHash(window.location, frag, options.replace);
-        if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
-          // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
-          // When replace is true, we don't want this.
-          if(!options.replace) this.iframe.document.open().close();
-          this._updateHash(this.iframe.location, frag, options.replace);
-        }
-
-      // If you've told us that you explicitly don't want fallback hashchange-
-      // based history, then `navigate` becomes a page refresh.
-      } else {
-        window.location.assign(this.options.root + fragment);
-      }
-      if (options.trigger) this.loadUrl(fragment);
-    },
-
-    // Update the hash location, either replacing the current entry, or adding
-    // a new one to the browser history.
-    _updateHash: function(location, fragment, replace) {
-      if (replace) {
-        location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
-      } else {
-        location.hash = fragment;
-      }
-    }
-  });
-
-  // Backbone.View
-  // -------------
-
-  // Creating a Backbone.View creates its initial element outside of the DOM,
-  // if an existing element is not provided...
-  var View = Backbone.View = function(options) {
-    this.cid = _.uniqueId('view');
-    this._configure(options || {});
-    this._ensureElement();
-    this.initialize.apply(this, arguments);
-    this.delegateEvents();
-  };
-
-  // Cached regex to split keys for `delegate`.
-  var delegateEventSplitter = /^(\S+)\s*(.*)$/;
-
-  // List of view options to be merged as properties.
-  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
-
-  // Set up all inheritable **Backbone.View** properties and methods.
-  _.extend(View.prototype, Events, {
-
-    // The default `tagName` of a View's element is `"div"`.
-    tagName: 'div',
-
-    // jQuery delegate for element lookup, scoped to DOM elements within the
-    // current view. This should be prefered to global lookups where possible.
-    $: function(selector) {
-      return this.$el.find(selector);
-    },
-
-    // Initialize is an empty function by default. Override it with your own
-    // initialization logic.
-    initialize: function(){},
-
-    // **render** is the core function that your view should override, in order
-    // to populate its element (`this.el`), with the appropriate HTML. The
-    // convention is for **render** to always return `this`.
-    render: function() {
-      return this;
-    },
-
-    // Remove this view from the DOM. Note that the view isn't present in the
-    // DOM by default, so calling this method may be a no-op.
-    remove: function() {
-      this.$el.remove();
-      return this;
-    },
-
-    // For small amounts of DOM Elements, where a full-blown template isn't
-    // needed, use **make** to manufacture elements, one at a time.
-    //
-    //     var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
-    //
-    make: function(tagName, attributes, content) {
-      var el = document.createElement(tagName);
-      if (attributes) $(el).attr(attributes);
-      if (content != null) $(el).html(content);
-      return el;
-    },
-
-    // Change the view's element (`this.el` property), including event
-    // re-delegation.
-    setElement: function(element, delegate) {
-      if (this.$el) this.undelegateEvents();
-      this.$el = (element instanceof $) ? element : $(element);
-      this.el = this.$el[0];
-      if (delegate !== false) this.delegateEvents();
-      return this;
-    },
-
-    // Set callbacks, where `this.events` is a hash of
-    //
-    // *{"event selector": "callback"}*
-    //
-    //     {
-    //       'mousedown .title':  'edit',
-    //       'click .button':     'save'
-    //       'click .open':       function(e) { ... }
-    //     }
-    //
-    // pairs. Callbacks will be bound to the view, with `this` set properly.
-    // Uses event delegation for efficiency.
-    // Omitting the selector binds the event to `this.el`.
-    // This only works for delegate-able events: not `focus`, `blur`, and
-    // not `change`, `submit`, and `reset` in Internet Explorer.
-    delegateEvents: function(events) {
-      if (!(events || (events = getValue(this, 'events')))) return;
-      this.undelegateEvents();
-      for (var key in events) {
-        var method = events[key];
-        if (!_.isFunction(method)) method = this[events[key]];
-        if (!method) throw new Error('Method "' + events[key] + '" does not exist');
-        var match = key.match(delegateEventSplitter);
-        var eventName = match[1], selector = match[2];
-        method = _.bind(method, this);
-        eventName += '.delegateEvents' + this.cid;
-        if (selector === '') {
-          this.$el.bind(eventName, method);
-        } else {
-          this.$el.delegate(selector, eventName, method);
-        }
-      }
-    },
-
-    // Clears all callbacks previously bound to the view with `delegateEvents`.
-    // You usually don't need to use this, but may wish to if you have multiple
-    // Backbone views attached to the same DOM element.
-    undelegateEvents: function() {
-      this.$el.unbind('.delegateEvents' + this.cid);
-    },
-
-    // Performs the initial configuration of a View with a set of options.
-    // Keys with special meaning *(model, collection, id, className)*, are
-    // attached directly to the view.
-    _configure: function(options) {
-      if (this.options) options = _.extend({}, this.options, options);
-      for (var i = 0, l = viewOptions.length; i < l; i++) {
-        var attr = viewOptions[i];
-        if (options[attr]) this[attr] = options[attr];
-      }
-      this.options = options;
-    },
-
-    // Ensure that the View has a DOM element to render into.
-    // If `this.el` is a string, pass it through `$()`, take the first
-    // matching element, and re-assign it to `el`. Otherwise, create
-    // an element from the `id`, `className` and `tagName` properties.
-    _ensureElement: function() {
-      if (!this.el) {
-        var attrs = getValue(this, 'attributes') || {};
-        if (this.id) attrs.id = this.id;
-        if (this.className) attrs['class'] = this.className;
-        this.setElement(this.make(this.tagName, attrs), false);
-      } else {
-        this.setElement(this.el, false);
-      }
-    }
-
-  });
-
-  // The self-propagating extend function that Backbone classes use.
-  var extend = function (protoProps, classProps) {
-    var child = inherits(this, protoProps, classProps);
-    child.extend = this.extend;
-    return child;
-  };
-
-  // Set up inheritance for the model, collection, and view.
-  Model.extend = Collection.extend = Router.extend = View.extend = extend;
-
-  // Backbone.sync
-  // -------------
-
-  // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
-  var methodMap = {
-    'create': 'POST',
-    'update': 'PUT',
-    'delete': 'DELETE',
-    'read':   'GET'
-  };
-
-  // Override this function to change the manner in which Backbone persists
-  // models to the server. You will be passed the type of request, and the
-  // model in question. By default, makes a RESTful Ajax request
-  // to the model's `url()`. Some possible customizations could be:
-  //
-  // * Use `setTimeout` to batch rapid-fire updates into a single request.
-  // * Send up the models as XML instead of JSON.
-  // * Persist models via WebSockets instead of Ajax.
-  //
-  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
-  // as `POST`, with a `_method` parameter containing the true HTTP method,
-  // as well as all requests with the body as `application/x-www-form-urlencoded`
-  // instead of `application/json` with the model in a param named `model`.
-  // Useful when interfacing with server-side languages like **PHP** that make
-  // it difficult to read the body of `PUT` requests.
-  Backbone.sync = function(method, model, options) {
-    var type = methodMap[method];
-
-    // Default options, unless specified.
-    options || (options = {});
-
-    // Default JSON-request options.
-    var params = {type: type, dataType: 'json'};
-
-    // Ensure that we have a URL.
-    if (!options.url) {
-      params.url = getValue(model, 'url') || urlError();
-    }
-
-    // Ensure that we have the appropriate request data.
-    if (!options.data && model && (method == 'create' || method == 'update')) {
-      params.contentType = 'application/json';
-      params.data = JSON.stringify(model.toJSON());
-    }
-
-    // For older servers, emulate JSON by encoding the request into an HTML-form.
-    if (Backbone.emulateJSON) {
-      params.contentType = 'application/x-www-form-urlencoded';
-      params.data = params.data ? {model: params.data} : {};
-    }
-
-    // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
-    // And an `X-HTTP-Method-Override` header.
-    if (Backbone.emulateHTTP) {
-      if (type === 'PUT' || type === 'DELETE') {
-        if (Backbone.emulateJSON) params.data._method = type;
-        params.type = 'POST';
-        params.beforeSend = function(xhr) {
-          xhr.setRequestHeader('X-HTTP-Method-Override', type);
-        };
-      }
-    }
-
-    // Don't process data on a non-GET request.
-    if (params.type !== 'GET' && !Backbone.emulateJSON) {
-      params.processData = false;
-    }
-
-    // Make the request, allowing the user to override any Ajax options.
-    return $.ajax(_.extend(params, options));
-  };
-
-  // Wrap an optional error callback with a fallback error event.
-  Backbone.wrapError = function(onError, originalModel, options) {
-    return function(model, resp) {
-      resp = model === originalModel ? resp : model;
-      if (onError) {
-        onError(originalModel, resp, options);
-      } else {
-        originalModel.trigger('error', originalModel, resp, options);
-      }
-    };
-  };
-
-  // Helpers
-  // -------
-
-  // Shared empty constructor function to aid in prototype-chain creation.
-  var ctor = function(){};
-
-  // Helper function to correctly set up the prototype chain, for subclasses.
-  // Similar to `goog.inherits`, but uses a hash of prototype properties and
-  // class properties to be extended.
-  var inherits = function(parent, protoProps, staticProps) {
-    var child;
-
-    // The constructor function for the new subclass is either defined by you
-    // (the "constructor" property in your `extend` definition), or defaulted
-    // by us to simply call the parent's constructor.
-    if (protoProps && protoProps.hasOwnProperty('constructor')) {
-      child = protoProps.constructor;
-    } else {
-      child = function(){ parent.apply(this, arguments); };
-    }
-
-    // Inherit class (static) properties from parent.
-    _.extend(child, parent);
-
-    // Set the prototype chain to inherit from `parent`, without calling
-    // `parent`'s constructor function.
-    ctor.prototype = parent.prototype;
-    child.prototype = new ctor();
-
-    // Add prototype properties (instance properties) to the subclass,
-    // if supplied.
-    if (protoProps) _.extend(child.prototype, protoProps);
-
-    // Add static properties to the constructor function, if supplied.
-    if (staticProps) _.extend(child, staticProps);
-
-    // Correctly set child's `prototype.constructor`.
-    child.prototype.constructor = child;
-
-    // Set a convenience property in case the parent's prototype is needed later.
-    child.__super__ = parent.prototype;
-
-    return child;
-  };
-
-  // Helper function to get a value from a Backbone object as a property
-  // or as a function.
-  var getValue = function(object, prop) {
-    if (!(object && object[prop])) return null;
-    return _.isFunction(object[prop]) ? object[prop]() : object[prop];
-  };
-
-  // Throw an error when a URL is needed, and none is supplied.
-  var urlError = function() {
-    throw new Error('A "url" property or function must be specified');
-  };
-
-  return Backbone;
-}));
-

+ 0 - 0
services/web/public/img/logo/banner-plain.png → services/web/public/brand/logo/banner-plain.png


+ 0 - 0
services/web/public/img/logo/banner.png → services/web/public/brand/logo/banner.png


+ 0 - 0
services/web/public/img/logo/icon_128.png → services/web/public/brand/logo/icon_128.png


+ 0 - 0
services/web/public/img/logo/lion-128.png → services/web/public/brand/logo/logo-128.png


+ 0 - 0
services/web/public/img/logo/lion-32.png → services/web/public/brand/logo/logo-32.png


+ 0 - 0
services/web/public/img/logo/lion-64.png → services/web/public/brand/logo/logo-64.png


+ 0 - 0
services/web/public/img/logo/logosmall.png → services/web/public/brand/logo/logosmall.png


+ 0 - 0
services/web/public/img/icons/16/code.png → services/web/public/brand/pictos/code.png


+ 0 - 0
services/web/public/img/icons/16/collaborators.png → services/web/public/brand/pictos/collaborators.png


+ 0 - 0
services/web/public/img/icons/16/history.png → services/web/public/brand/pictos/history.png


+ 0 - 0
services/web/public/img/icons/16/projects.png → services/web/public/brand/pictos/projects.png


+ 0 - 0
services/web/public/img/icons/16/settings.png → services/web/public/brand/pictos/settings.png


+ 0 - 0
services/web/public/img/icons/16/subscription.png → services/web/public/brand/pictos/subscription.png


+ 276 - 0
services/web/public/brand/plans.less

@@ -0,0 +1,276 @@
+.pricing-table {
+	font: 13px "PT Sans", "Helvetica Neue", Arial, sans-serif;
+	margin-bottom: 20px;
+	-webkit-border-radius: 5px;
+	   -moz-border-radius: 5px;
+	        border-radius: 5px;
+}
+
+.pricing-table ul {
+	margin: 0;
+	padding: 0;
+	list-style: none;
+}
+
+.pricing-header-row-1 {
+	text-align: center;
+	height: 40px;
+	padding: 3px 0 0;
+	-webkit-border-radius: 4px 4px 0 0;
+	   -moz-border-radius: 4px 4px 0 0;
+	        border-radius: 4px 4px 0 0;
+	-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
+	   -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
+	        box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
+}
+
+.pricing-header-row-2 {
+	text-align: center;
+	height: 50px;
+	margin-top: -1px;
+	padding: 10px 0 0;
+	border-bottom: none;	
+	-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
+	   -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
+	        box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
+}
+
+ul.big {
+	position: relative;
+	top: -10px;
+	.pricing-header-row-1 {
+		padding: 7px 0 7px;
+	}
+	.pricing-header-row-2 {
+		padding: 15px 0 5px;
+	}
+}
+
+.pricing-table {
+	h1.free {
+		font-size: 30px;
+	}
+}
+
+.pricing-row {
+	padding-top:20px;
+}
+
+.package-title h2 {
+	color: #f9f9f9;
+	margin: 0;
+	font-size: 24px;
+	line-height: 40px;
+	text-shadow: -1px -1px 0 rgba(0, 0, 0, .3), 1px 1px 0 rgba(255, 255, 255, .2);
+}
+
+.package-price h1 {
+	color: #f9f9f9;
+	margin: 0;
+	font-size: 44px;
+	line-height: 40px;
+	text-shadow: -1px -1px 0 rgba(0, 0, 0, .3), 1px 1px 0 rgba(255, 255, 255, .2);
+}
+
+.cents {
+	font-size: 16px;
+	position: relative;
+	top: -20px;
+}
+
+.pricing-content-row-odd {
+	font-size: 14px;
+	background-color: #f3f3f3;
+	padding: 10px 15px;
+	border-left: 1px solid #aaaaaa;
+	border-right: 1px solid #aaaaaa;
+}
+
+.pricing-content-row-even {
+	font-size: 14px;
+	background-color: #fcfcfc;
+	padding: 10px 15px;
+	border-left: 1px solid #aaaaaa;
+	border-right: 1px solid #aaaaaa;
+}
+
+.acc-inner {
+	display: block;
+	padding: 10px 15px;
+	border-left: 1px solid #aaaaaa;
+	border-right: 1px solid #aaaaaa;
+	-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .2), inset 0 -1px 1px rgba(0, 0, 0, .2);
+	   -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .2), inset 0 -1px 1px rgba(0, 0, 0, .2);
+	        box-shadow: inset 0 1px 1px rgba(0, 0, 0, .2), inset 0 -1px 1px rgba(0, 0, 0, .2);
+}
+
+.pricing-footer {
+	text-align: center;
+	height: 50px;
+	padding: 10px 0 0;	
+	-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
+	   -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
+	        box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
+	-webkit-border-radius: 0 0 4px 4px;
+	   -moz-border-radius: 0 0 4px 4px;
+	        border-radius: 0 0 4px 4px;
+}
+
+.animate, .animate .pricing-content-row-odd, .pricing-content-row-even, .acc-inner {
+	-webkit-transition: all .3s ease-in-out;
+	   -moz-transition: all .3s ease-in-out;
+	     -o-transition: all .3s ease-in-out;
+	    -ms-transition: all .3s ease-in-out;
+	        transition: all .3s ease-in-out;
+}
+
+.animate.change-bg ul:hover .pricing-footer {
+	-webkit-box-shadow: inset 0 0 0 rgba(255, 255, 255, 0);
+	   -moz-box-shadow: inset 0 0 0 rgba(255, 255, 255, 0);
+	        box-shadow: inset 0 0 0 rgba(255, 255, 255, 0);
+}
+
+.animate.go-up {
+	-webkit-transform: translate(0px, 0px);
+	   -moz-transform: translate(0px, 0px);
+	     -o-transform: translate(0px, 0px);
+	    -ms-transform: translate(0px, 0px);
+	        transform: translate(0px, 0px);
+}
+
+.animate.go-up:hover {
+	-webkit-transform: translate(0,-10px);
+	   -moz-transform: translate(0,-10px);
+	     -o-transform: translate(0,-10px);
+	    -ms-transform: translate(0,-10px);
+	        transform: translate(0,-10px);
+}
+
+.animate.add-shadow:hover {
+	-webkit-box-shadow: 0 0 15px rgba(0, 0, 255, .6);
+	   -moz-box-shadow: 0 0 15px rgba(0, 0, 255, .6);
+	        box-shadow: 0 0 15px rgba(0, 0, 255, .6);
+}
+
+.btn {
+	font: 13px "PT Sans", "Helvetica Neue", Arial, sans-serif;
+}
+
+.btn {
+	font-size: 18px;
+	line-height: 18px;
+	padding: 10px 20px;
+	text-shadow: none;
+	*zoom: 1;
+	-webkit-box-shadow: inset 0 0 1px rgba(255, 255, 255, .5), 0 0 1px rgba(0, 0, 0, .8);
+	   -moz-box-shadow: inset 0 0 1px rgba(255, 255, 255, .5), 0 0 1px rgba(0, 0, 0, .8);
+	        box-shadow: inset 0 0 1px rgba(255, 255, 255, .5), 0 0 1px rgba(0, 0, 0, .8);
+}
+
+.acc {
+	cursor: pointer;
+}
+
+.no-bold {
+	font-weight: normal;
+}
+
+/* STYLES
+   -----
+   1. BLack				6. Gray				11. Orange				16. Slategray
+   2. Blue				7. Green			12. Purple				17. Steelblue
+   3. Brown				8. Indigo			13. Red					18. Teal
+   4. Firebrick			9. Lime 			14. Silver				19. Violet
+   5. Gold				10. Olive			15. Skyblue				20. Yellow
+*/
+
+
+/* --- 17. Steelblue --- */
+.pricing-steelblue .pricing-header-row-1, .pricing-steelblue .pricing-header-row-2 {
+	background-color: #4581b3;
+	background-image: -moz-linear-gradient(top, #549dd9, #4581b3);
+	background-image: -ms-linear-gradient(top, #549dd9, #4581b3);
+	background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#549dd9), to(#4581b3));
+	background-image: -webkit-linear-gradient(top, #549dd9, #4581b3);
+	background-image: -o-linear-gradient(top, #549dd9, #4581b3);
+	background-image: linear-gradient(top, #549dd9, #4581b3);
+	background-repeat: repeat-x;
+	filter: progid:dximagetransform.microsoft.gradient(startcolorstr='#549dd9', endcolorstr='#4581b3', GradientType=0);
+	filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+	border: 1px solid #2b4f6e;
+}
+
+.pricing-steelblue .pricing-header-row-2 {
+	border-bottom: none;
+}
+
+.pricing-steelblue .pricing-footer {
+	background-color: #4581b3;
+	background-image: -moz-linear-gradient(top, #4581b3, #325c80);
+	background-image: -ms-linear-gradient(top, #4581b3, #325c80);
+	background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#4581b3), to(#325c80));
+	background-image: -webkit-linear-gradient(top, #4581b3, #325c80);
+	background-image: -o-linear-gradient(top, #4581b3, #325c80);
+	background-image: linear-gradient(top, #4581b3, #325c80);
+	background-repeat: repeat-x;
+	filter: progid:dximagetransform.microsoft.gradient(startcolorstr='#4581b3', endcolorstr='#325c80', GradientType=0);
+	filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+	border: 1px solid #2b4f6e;
+}
+
+.pricing-steelblue .animate.change-bg ul:hover .pricing-content-row-odd, .pricing-steelblue .animate.change-bg ul:hover .pricing-content-row-even{
+	color: #fff;
+	background: #4581b3;
+	border-left: 1px solid #2b4f6e;
+	border-right: 1px solid #2b4f6e;
+}
+
+.pricing-steelblue .animate.change-bg ul:hover .acc-inner {
+	color: #fff;
+	background: #4078a6;
+	border-left: 1px solid #2b4f6e;
+	border-right: 1px solid #2b4f6e;
+	-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .2), inset 0 -1px 2px rgba(0, 0, 0, .2), inset 0 -1px 0 rgba(255, 255, 255, .4);
+	   -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .2), inset 0 -1px 2px rgba(0, 0, 0, .2), inset 0 -1px 0 rgba(255, 255, 255, .4);
+	        box-shadow: inset 0 1px 1px rgba(0, 0, 0, .2), inset 0 -1px 2px rgba(0, 0, 0, .2), inset 0 -1px 0 rgba(255, 255, 255, .4);
+}
+
+.pricing-steelblue .animate.change-bg ul:hover .pricing-footer {
+	border-top-color: #4581b3;
+	-webkit-box-shadow: inset 0 0 0 #4581b3;
+	   -moz-box-shadow: inset 0 0 0 #4581b3;
+	        box-shadow: inset 0 0 0 #4581b3;
+}
+
+.pricing-steelblue .btn-pricing {
+	color: #d3dce5;
+	background-color: #4581b3;
+	*background-color: #325c80;
+	background-image: -moz-linear-gradient(top, #4581b3, #325c80);
+	background-image: -ms-linear-gradient(top, #4581b3, #325c80);
+	background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#4581b3), to(#325c80));
+	background-image: -webkit-linear-gradient(top, #4581b3, #325c80);
+	background-image: -o-linear-gradient(top, #4581b3, #325c80);
+	background-image: linear-gradient(top, #4581b3, #325c80);
+	background-repeat: repeat-x;
+	filter: progid:dximagetransform.microsoft.gradient(startcolorstr='#4581b3', endcolorstr='#325c80', GradientType=0);
+	filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+	border-color: #4581b3 #4581b3 #325c80;
+	border-color: rgba(0, 0, 0, .1) rgba(0, 0, 0, .1) rgba(0, 0, 0, .25);
+}
+
+.pricing-steelblue .btn-pricing:hover,
+.pricing-steelblue .btn-pricing:active,
+.pricing-steelblue .btn-pricing.active,
+.pricing-steelblue .btn-pricing.disabled,
+.pricing-steelblue .btn-pricing[disabled] {
+	color: #d3dce5;
+	background-color: #325c80;
+	*background-color: #325c80;
+}
+
+.pricing-steelblue .btn-pricing:active,
+.pricing-steelblue .btn-pricing.active {
+	color: #d3dce5;
+	background-color: #325c80 \9;
+}

+ 0 - 1
services/web/public/coffee/history/HistoryManager.coffee

@@ -7,7 +7,6 @@ define [
 ], (VersionListView, VersionList, HistoryView, AccountManager) ->
 	HistoryManager = class
 		templates:
-			historyLink: $("#historySideBarLinkTemplate").html()
 			historyPanel: $("#historyPanelTemplate").html()
 		
 		constructor: (ide, options) ->

+ 0 - 17
services/web/public/coffee/ide/MainAreaManager.coffee

@@ -1,10 +1,8 @@
 define () ->
 	class MainAreaManager
 		constructor: (@ide, @el) ->
-			@$folderArea = $('#folderArea')
 			@$iframe = $('#imageArea')
 			@$loading = $('#loading')
-			@$disconnect = $('#disconnect')
 			@$currentArea = $('#loading')
 			@areas = {}
 
@@ -29,9 +27,6 @@ define () ->
 			$('#imageArea iframe').attr 'src', src
 
 		change : (type, complete)->
-			if(@$currentArea == @$disconnect)
-				return
-
 			if @areas[type]?
 				@$currentArea.hide() if @$currentArea?
 				@areas[type].show 0, =>
@@ -42,12 +37,6 @@ define () ->
 			else
 				# Deprecated system
 				switch type
-					when 'folder'
-						if(@$folderArea.attr('id')!=@$currentArea.attr('id'))
-							@$currentArea.hide()
-							@$folderArea.show()
-							@$currentArea = @$folderArea
-						break
 					when 'iframe'
 						if(@$iframe.attr('id')!=@$currentArea.attr('id'))
 							@$iframe.show()
@@ -60,10 +49,4 @@ define () ->
 							@$loading.show()
 							@$currentArea = @$loading
 						break
-					when 'disconnect'
-						if(@$disconnect.attr('id')!=@$currentArea.attr('id'))
-							@$currentArea.hide()
-							@$disconnect.show()
-							@$currentArea = @$disconnect
-						break
 

+ 1 - 2
services/web/public/coffee/project-members/ProjectMembersManager.coffee

@@ -10,7 +10,6 @@ define [
 
 	class ProjectMembersManager
 		templates:
-			usersLink: $("#usersSideBarLinkTemplate").html()
 			userPanel: $("#userPanelTemplate").html()
 
 		constructor: (@ide, options) ->
@@ -220,7 +219,7 @@ define [
 
 					url = "https://www.facebook.com/dialog/feed?link=#{encodeURIComponent(@url("fb"))}&" +
 						  "app_id=148710621956179&" +
-						  "picture=https://www.sharelatex.com/img/logo/lion-128.png&" +
+						  "picture=https://www.sharelatex.com/brand/logo/logo-128.png&" +
 						  "name=#{@ide.project.get("name")}&" +
 						  "caption=My LaTeX project (#{@ide.project.get("name")}) is available online on ShareLaTeX&" +
 						  "redirect_uri=http://www.sharelatex.com&" +

+ 0 - 1
services/web/public/coffee/settings/SettingsManager.coffee

@@ -4,7 +4,6 @@ define [
 ], (Modal, DropboxSettingsManager) ->
 	class SettingsManager
 		templates:
-			settingsLink: $("#settingsSideBarLinkTemplate").html()
 			settingsPanel: $("#settingsPanelTemplate").html()
 
 		constructor: (@ide, options) ->

BIN
services/web/public/img/about/coffeescript_logo.png


BIN
services/web/public/img/about/nodejs_logo.jpg


BIN
services/web/public/img/about/redis_logo.png


BIN
services/web/public/img/about/scribtex_logo.jpeg


BIN
services/web/public/img/add.png


BIN
services/web/public/img/aqua.jpg


BIN
services/web/public/img/arrow.png


BIN
services/web/public/img/arrow1.png


BIN
services/web/public/img/backward.png


BIN
services/web/public/img/book.png


BIN
services/web/public/img/clock.png


BIN
services/web/public/img/create-folder.png


BIN
services/web/public/img/documentation.png


BIN
services/web/public/img/favicon.png


BIN
services/web/public/img/folder-large.png


BIN
services/web/public/img/folder-open.png


BIN
services/web/public/img/forward.png


BIN
services/web/public/img/galaxy.jpg


BIN
services/web/public/img/icons/24/code.png


BIN
services/web/public/img/icons/24/collaborators.png


BIN
services/web/public/img/icons/24/history.png


BIN
services/web/public/img/icons/24/settings.png


+ 3 - 0
services/web/public/img/iconshock/README.txt

@@ -0,0 +1,3 @@
+Icons by IconShock (www.iconshock.com) via findicons.com (http://findicons.com/icon/498089/height?id=526085#)
+
+Licensed under the Creative Commons Attribution (by) license

+ 0 - 0
services/web/public/img/fit-to-height.png → services/web/public/img/iconshock/fit-to-height.png


+ 0 - 0
services/web/public/img/fit-to-width.png → services/web/public/img/iconshock/fit-to-width.png


+ 0 - 0
services/web/public/img/save.png → services/web/public/img/iconshock/save.png


+ 0 - 0
services/web/public/img/zoom-in.png → services/web/public/img/iconshock/zoom-in.png


+ 0 - 0
services/web/public/img/zoom-out.png → services/web/public/img/iconshock/zoom-out.png


BIN
services/web/public/img/keyboard.png


BIN
services/web/public/img/list/45.png


BIN
services/web/public/img/list/48.png


BIN
services/web/public/img/list/50.png


BIN
services/web/public/img/list/56.png


BIN
services/web/public/img/log.png


BIN
services/web/public/img/logo/logo.gif


BIN
services/web/public/img/logo/main.acorn


BIN
services/web/public/img/nide.png


+ 0 - 0
services/web/public/img/doc.png → services/web/public/img/nide/doc.png


+ 0 - 0
services/web/public/img/down-arrow.png → services/web/public/img/nide/down-arrow.png


+ 0 - 0
services/web/public/img/folder.png → services/web/public/img/nide/folder.png


+ 0 - 0
services/web/public/img/right-arrow.png → services/web/public/img/nide/right-arrow.png


BIN
services/web/public/img/project1.png


BIN
services/web/public/img/remove.png


BIN
services/web/public/img/sad.png


BIN
services/web/public/img/screen_CS3.png


BIN
services/web/public/img/search.png


BIN
services/web/public/img/settings.png


BIN
services/web/public/img/share/facebook_button.png


BIN
services/web/public/img/share/mail.png


BIN
services/web/public/img/share/twitter_button.png


+ 0 - 0
services/web/public/img/arrow-up.png → services/web/public/img/silk/arrow-up.png


+ 0 - 0
services/web/public/img/created.png → services/web/public/img/silk/created.png


+ 0 - 0
services/web/public/img/deleted.png → services/web/public/img/silk/deleted.png


+ 0 - 0
services/web/public/img/moved.png → services/web/public/img/silk/moved.png


+ 22 - 0
services/web/public/img/silk/readme.txt

@@ -0,0 +1,22 @@
+Silk icon set 1.3
+
+_________________________________________
+Mark James
+http://www.famfamfam.com/lab/icons/silk/
+_________________________________________
+
+This work is licensed under a
+Creative Commons Attribution 2.5 License.
+[ http://creativecommons.org/licenses/by/2.5/ ]
+
+This means you may use it for any purpose,
+and make any changes you like.
+All I ask is that you include a link back
+to this page in your credits.
+
+Are you using this icon set? Send me an email
+(including a link or picture if available) to
+mjames@gmail.com
+
+Any other questions about this icon set please
+contact mjames@gmail.com

+ 0 - 0
services/web/public/img/logo/facebook-32.png → services/web/public/img/social/facebook-32.png


+ 0 - 0
services/web/public/img/logo/link-32.png → services/web/public/img/social/link-32.png


+ 0 - 0
services/web/public/img/logo/mail-32.png → services/web/public/img/social/mail-32.png


+ 0 - 0
services/web/public/img/logo/twitter-32.png → services/web/public/img/social/twitter-32.png


BIN
services/web/public/img/textures/concrete_wall.png


BIN
services/web/public/img/textures/polaroid.png


BIN
services/web/public/img/txture.png


BIN
services/web/public/img/user.png


BIN
services/web/public/img/users/3.png


BIN
services/web/public/img/users/numbers.png


BIN
services/web/public/img/users/numbers/1.png


BIN
services/web/public/img/users/numbers/2.png


BIN
services/web/public/img/users/user1.png


BIN
services/web/public/img/users/user2.png


BIN
services/web/public/img/washi.png


+ 0 - 1248
services/web/public/js/libs/fileuploader.js

@@ -1,1248 +0,0 @@
-/**
- * http://github.com/valums/file-uploader
- * 
- * Multiple file upload component with progress-bar, drag-and-drop. 
- * © 2010 Andrew Valums ( andrew(at)valums.com ) 
- * 
- * Licensed under GNU GPL 2 or later and GNU LGPL 2 or later, see license.txt.
- */    
-
-//
-// Helper functions
-//
-
-var qq = qq || {};
-
-/**
- * Adds all missing properties from second obj to first obj
- */ 
-qq.extend = function(first, second){
-    for (var prop in second){
-        first[prop] = second[prop];
-    }
-};  
-
-/**
- * Searches for a given element in the array, returns -1 if it is not present.
- * @param {Number} [from] The index at which to begin the search
- */
-qq.indexOf = function(arr, elt, from){
-    if (arr.indexOf) return arr.indexOf(elt, from);
-    
-    from = from || 0;
-    var len = arr.length;    
-    
-    if (from < 0) from += len;  
-
-    for (; from < len; from++){  
-        if (from in arr && arr[from] === elt){  
-            return from;
-        }
-    }  
-    return -1;  
-}; 
-    
-qq.getUniqueId = (function(){
-    var id = 0;
-    return function(){ return id++; };
-})();
-
-//
-// Events
-
-qq.attach = function(element, type, fn){
-    if (element.addEventListener){
-        element.addEventListener(type, fn, false);
-    } else if (element.attachEvent){
-        element.attachEvent('on' + type, fn);
-    }
-};
-qq.detach = function(element, type, fn){
-    if (element.removeEventListener){
-        element.removeEventListener(type, fn, false);
-    } else if (element.attachEvent){
-        element.detachEvent('on' + type, fn);
-    }
-};
-
-qq.preventDefault = function(e){
-    if (e.preventDefault){
-        e.preventDefault();
-    } else{
-        e.returnValue = false;
-    }
-};
-
-//
-// Node manipulations
-
-/**
- * Insert node a before node b.
- */
-qq.insertBefore = function(a, b){
-    b.parentNode.insertBefore(a, b);
-};
-qq.remove = function(element){
-    element.parentNode.removeChild(element);
-};
-
-qq.contains = function(parent, descendant){       
-    // compareposition returns false in this case
-    if (parent == descendant) return true;
-    
-    if (parent.contains){
-        return parent.contains(descendant);
-    } else {
-        return !!(descendant.compareDocumentPosition(parent) & 8);
-    }
-};
-
-/**
- * Creates and returns element from html string
- * Uses innerHTML to create an element
- */
-qq.toElement = (function(){
-    var div = document.createElement('div');
-    return function(html){
-        div.innerHTML = html;
-        var element = div.firstChild;
-        div.removeChild(element);
-        return element;
-    };
-})();
-
-//
-// Node properties and attributes
-
-/**
- * Sets styles for an element.
- * Fixes opacity in IE6-8.
- */
-qq.css = function(element, styles){
-    if (styles.opacity != null){
-        if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
-            styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
-        }
-    }
-    qq.extend(element.style, styles);
-};
-qq.hasClass = function(element, name){
-    var re = new RegExp('(^| )' + name + '( |$)');
-    return re.test(element.className);
-};
-qq.addClass = function(element, name){
-    if (!qq.hasClass(element, name)){
-        element.className += ' ' + name;
-    }
-};
-qq.removeClass = function(element, name){
-    var re = new RegExp('(^| )' + name + '( |$)');
-    element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
-};
-qq.setText = function(element, text){
-    element.innerText = text;
-    element.textContent = text;
-};
-
-//
-// Selecting elements
-
-qq.children = function(element){
-    var children = [],
-    child = element.firstChild;
-
-    while (child){
-        if (child.nodeType == 1){
-            children.push(child);
-        }
-        child = child.nextSibling;
-    }
-
-    return children;
-};
-
-qq.getByClass = function(element, className){
-    if (element.querySelectorAll){
-        return element.querySelectorAll('.' + className);
-    }
-
-    var result = [];
-    var candidates = element.getElementsByTagName("*");
-    var len = candidates.length;
-
-    for (var i = 0; i < len; i++){
-        if (qq.hasClass(candidates[i], className)){
-            result.push(candidates[i]);
-        }
-    }
-    return result;
-};
-
-/**
- * obj2url() takes a json-object as argument and generates
- * a querystring. pretty much like jQuery.param()
- * 
- * how to use:
- *
- *    `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
- *
- * will result in:
- *
- *    `http://any.url/upload?otherParam=value&a=b&c=d`
- *
- * @param  Object JSON-Object
- * @param  String current querystring-part
- * @return String encoded querystring
- */
-qq.obj2url = function(obj, temp, prefixDone){
-    var uristrings = [],
-        prefix = '&',
-        add = function(nextObj, i){
-            var nextTemp = temp 
-                ? (/\[\]$/.test(temp)) // prevent double-encoding
-                   ? temp
-                   : temp+'['+i+']'
-                : i;
-            if ((nextTemp != 'undefined') && (i != 'undefined')) {  
-                uristrings.push(
-                    (typeof nextObj === 'object') 
-                        ? qq.obj2url(nextObj, nextTemp, true)
-                        : (Object.prototype.toString.call(nextObj) === '[object Function]')
-                            ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
-                            : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)                                                          
-                );
-            }
-        }; 
-
-    if (!prefixDone && temp) {
-      prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
-      uristrings.push(temp);
-      uristrings.push(qq.obj2url(obj));
-    } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
-        // we wont use a for-in-loop on an array (performance)
-        for (var i = 0, len = obj.length; i < len; ++i){
-            add(obj[i], i);
-        }
-    } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
-        // for anything else but a scalar, we will use for-in-loop
-        for (var i in obj){
-            add(obj[i], i);
-        }
-    } else {
-        uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
-    }
-
-    return uristrings.join(prefix)
-                     .replace(/^&/, '')
-                     .replace(/%20/g, '+'); 
-};
-
-//
-//
-// Uploader Classes
-//
-//
-
-var qq = qq || {};
-    
-/**
- * Creates upload button, validates upload, but doesn't create file list or dd. 
- */
-qq.FileUploaderBasic = function(o){
-    this._options = {
-        // set to true to see the server response
-        debug: false,
-        action: '/server/upload',
-        params: {},
-        button: null,
-        multiple: true,
-        maxConnections: 3,
-        // validation        
-        allowedExtensions: [],               
-        sizeLimit: 0,   
-        minSizeLimit: 0,                             
-        // events
-        // return false to cancel submit
-        onSubmit: function(id, fileName){},
-        onProgress: function(id, fileName, loaded, total){},
-        onComplete: function(id, fileName, responseJSON){},
-        onCancel: function(id, fileName){},
-        // messages                
-        messages: {
-            typeError: "{file} has invalid extension. Only {extensions} are allowed.",
-            sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
-            minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
-            emptyError: "{file} is empty, please select files again without it.",
-            onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."            
-        },
-        showMessage: function(message){
-            alert(message);
-        }               
-    };
-    qq.extend(this._options, o);
-        
-    // number of files being uploaded
-    this._filesInProgress = 0;
-    this._handler = this._createUploadHandler(); 
-    
-    if (this._options.button){ 
-        this._button = this._createUploadButton(this._options.button);
-    }
-                        
-    this._preventLeaveInProgress();         
-};
-   
-qq.FileUploaderBasic.prototype = {
-    setParams: function(params){
-        this._options.params = params;
-    },
-    getInProgress: function(){
-        return this._filesInProgress;         
-    },
-    _createUploadButton: function(element){
-        var self = this;
-        
-        return new qq.UploadButton({
-            element: element,
-            multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
-            onChange: function(input){
-                self._onInputChange(input);
-            }        
-        });           
-    },    
-    _createUploadHandler: function(){
-        var self = this,
-            handlerClass;        
-        
-        if(qq.UploadHandlerXhr.isSupported()){           
-            handlerClass = 'UploadHandlerXhr';                        
-        } else {
-            handlerClass = 'UploadHandlerForm';
-        }
-
-        var handler = new qq[handlerClass]({
-            debug: this._options.debug,
-            action: this._options.action,         
-            maxConnections: this._options.maxConnections,   
-            onProgress: function(id, fileName, loaded, total){                
-                self._onProgress(id, fileName, loaded, total);
-                self._options.onProgress(id, fileName, loaded, total);                    
-            },            
-            onComplete: function(id, fileName, result){
-                self._onComplete(id, fileName, result);
-                self._options.onComplete(id, fileName, result);
-            },
-            onCancel: function(id, fileName){
-                self._onCancel(id, fileName);
-                self._options.onCancel(id, fileName);
-            }
-        });
-
-        return handler;
-    },    
-    _preventLeaveInProgress: function(){
-        var self = this;
-        
-        qq.attach(window, 'beforeunload', function(e){
-            if (!self._filesInProgress){return;}
-            
-            var e = e || window.event;
-            // for ie, ff
-            e.returnValue = self._options.messages.onLeave;
-            // for webkit
-            return self._options.messages.onLeave;             
-        });        
-    },    
-    _onSubmit: function(id, fileName){
-        this._filesInProgress++;  
-    },
-    _onProgress: function(id, fileName, loaded, total){        
-    },
-    _onComplete: function(id, fileName, result){
-        this._filesInProgress--;                 
-        if (result.error){
-            this._options.showMessage(result.error);
-        }             
-    },
-    _onCancel: function(id, fileName){
-        this._filesInProgress--;        
-    },
-    _onInputChange: function(input){
-        if (this._handler instanceof qq.UploadHandlerXhr){                
-            this._uploadFileList(input.files);                   
-        } else {             
-            if (this._validateFile(input)){                
-                this._uploadFile(input);                                    
-            }                      
-        }               
-        this._button.reset();   
-    },  
-    _uploadFileList: function(files){
-        for (var i=0; i<files.length; i++){
-            if ( !this._validateFile(files[i])){
-                return;
-            }            
-        }
-        
-        for (var i=0; i<files.length; i++){
-            this._uploadFile(files[i]);        
-        }        
-    },       
-    _uploadFile: function(fileContainer){      
-        var id = this._handler.add(fileContainer);
-        var fileName = this._handler.getName(id);
-        
-        if (this._options.onSubmit(id, fileName) !== false){
-            this._onSubmit(id, fileName);
-            this._handler.upload(id, this._options.params);
-        }
-    },      
-    _validateFile: function(file){
-        var name, size;
-        
-        if (file.value){
-            // it is a file input            
-            // get input value and remove path to normalize
-            name = file.value.replace(/.*(\/|\\)/, "");
-        } else {
-            // fix missing properties in Safari
-            name = file.fileName != null ? file.fileName : file.name;
-            size = file.fileSize != null ? file.fileSize : file.size;
-        }
-                    
-        if (! this._isAllowedExtension(name)){            
-            this._error('typeError', name);
-            return false;
-            
-        } else if (size === 0){            
-            this._error('emptyError', name);
-            return false;
-                                                     
-        } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){            
-            this._error('sizeError', name);
-            return false;
-                        
-        } else if (size && size < this._options.minSizeLimit){
-            this._error('minSizeError', name);
-            return false;            
-        }
-        
-        return true;                
-    },
-    _error: function(code, fileName){
-        var message = this._options.messages[code];        
-        function r(name, replacement){ message = message.replace(name, replacement); }
-        
-        r('{file}', this._formatFileName(fileName));        
-        r('{extensions}', this._options.allowedExtensions.join(', '));
-        r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
-        r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
-        
-        this._options.showMessage(message);                
-    },
-    _formatFileName: function(name){
-        if (name.length > 33){
-            name = name.slice(0, 19) + '...' + name.slice(-13);    
-        }
-        return name;
-    },
-    _isAllowedExtension: function(fileName){
-        var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
-        var allowed = this._options.allowedExtensions;
-        
-        if (!allowed.length){return true;}        
-        
-        for (var i=0; i<allowed.length; i++){
-            if (allowed[i].toLowerCase() == ext){ return true;}    
-        }
-        
-        return false;
-    },    
-    _formatSize: function(bytes){
-        var i = -1;                                    
-        do {
-            bytes = bytes / 1024;
-            i++;  
-        } while (bytes > 99);
-        
-        return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];          
-    }
-};
-    
-       
-/**
- * Class that creates upload widget with drag-and-drop and file list
- * @inherits qq.FileUploaderBasic
- */
-qq.FileUploader = function(o){
-    // call parent constructor
-    qq.FileUploaderBasic.apply(this, arguments);
-    
-    // additional options    
-    qq.extend(this._options, {
-        element: null,
-        // if set, will be used instead of qq-upload-list in template
-        listElement: null,
-                
-        template: '<div class="qq-uploader">' + 
-                '<div class="qq-upload-drop-area"><span>Drop files here to upload</span></div>' +
-                '<div class="btn btn-primary btn-large">Select a file</div> <b>or drag here</b>' +
-                '&nbsp <img style="padding-top:0px" src="/img/arrow.png" />' +
-                '<ul class="qq-upload-list"></ul>' + 
-             '</div>',
-
-        // template for one item in file list
-        fileTemplate: '<li>' +
-                '<span class="qq-upload-file"></span>' +
-                '<span class="qq-upload-spinner"></span>' +
-                '<span class="qq-upload-size"></span>' +
-                '<a class="qq-upload-cancel" href="#">Cancel</a>' +
-                '<span class="qq-upload-failed-text">Failed</span>' +
-            '</li>',        
-        
-        classes: {
-            // used to get elements from templates
-            button: 'btn',
-            drop: 'qq-upload-drop-area',
-            dropActive: 'qq-upload-drop-area-active',
-            list: 'qq-upload-list',
-                        
-            file: 'qq-upload-file',
-            spinner: 'qq-upload-spinner',
-            size: 'qq-upload-size',
-            cancel: 'qq-upload-cancel',
-
-            // added to list item when upload completes
-            // used in css to hide progress spinner
-            success: 'qq-upload-success',
-            fail: 'qq-upload-fail'
-        }
-    });
-    // overwrite options with user supplied    
-    qq.extend(this._options, o);       
-
-    this._element = this._options.element;
-    this._element.innerHTML = this._options.template;        
-    this._listElement = this._options.listElement || this._find(this._element, 'list');
-    
-    this._classes = this._options.classes;
-        
-    this._button = this._createUploadButton(this._find(this._element, 'button'));        
-    
-    this._bindCancelEvent();
-    this._setupDragDrop();
-};
-
-// inherit from Basic Uploader
-qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
-
-qq.extend(qq.FileUploader.prototype, {
-    /**
-     * Gets one of the elements listed in this._options.classes
-     **/
-    _find: function(parent, type){                                
-        var element = qq.getByClass(parent, this._options.classes[type])[0];        
-        if (!element){
-            throw new Error('element not found ' + type);
-        }
-        
-        return element;
-    },
-    _setupDragDrop: function(){
-        var self = this,
-            dropArea = this._find(this._element, 'drop');                        
-
-        var dz = new qq.UploadDropZone({
-            element: dropArea,
-            onEnter: function(e){
-                qq.addClass(dropArea, self._classes.dropActive);
-                e.stopPropagation();
-            },
-            onLeave: function(e){
-                e.stopPropagation();
-            },
-            onLeaveNotDescendants: function(e){
-                qq.removeClass(dropArea, self._classes.dropActive);  
-            },
-            onDrop: function(e){
-                dropArea.style.display = 'none';
-                qq.removeClass(dropArea, self._classes.dropActive);
-                self._uploadFileList(e.dataTransfer.files);    
-            }
-        });
-                
-        dropArea.style.display = 'none';
-
-        qq.attach(document, 'dragenter', function(e){     
-            if (!dz._isValidFileDrag(e)) return; 
-            
-            dropArea.style.display = 'block';            
-        });                 
-        qq.attach(document, 'dragleave', function(e){
-            if (!dz._isValidFileDrag(e)) return;            
-            
-            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
-            // only fire when leaving document out
-            if ( ! relatedTarget || relatedTarget.nodeName == "HTML"){               
-                dropArea.style.display = 'none';                                            
-            }
-        });                
-    },
-    _onSubmit: function(id, fileName){
-        qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
-        this._addToList(id, fileName);  
-    },
-    _onProgress: function(id, fileName, loaded, total){
-        qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
-
-        var item = this._getItemByFileId(id);
-        var size = this._find(item, 'size');
-        size.style.display = 'inline';
-        
-        var text; 
-        if (loaded != total){
-            text = Math.round(loaded / total * 100) + '% from ' + this._formatSize(total);
-        } else {                                   
-            text = this._formatSize(total);
-        }          
-        
-        qq.setText(size, text);         
-    },
-    _onComplete: function(id, fileName, result){
-        qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
-
-        // mark completed
-        var item = this._getItemByFileId(id);                
-        qq.remove(this._find(item, 'cancel'));
-        qq.remove(this._find(item, 'spinner'));
-        
-        if (result.success){
-            qq.addClass(item, this._classes.success);    
-        } else {
-            qq.addClass(item, this._classes.fail);
-        }         
-    },
-    _addToList: function(id, fileName){
-        var item = qq.toElement(this._options.fileTemplate);                
-        item.qqFileId = id;
-
-        var fileElement = this._find(item, 'file');        
-        qq.setText(fileElement, this._formatFileName(fileName));
-        this._find(item, 'size').style.display = 'none';        
-
-        this._listElement.appendChild(item);
-    },
-    _getItemByFileId: function(id){
-        var item = this._listElement.firstChild;        
-        
-        // there can't be txt nodes in dynamically created list
-        // and we can  use nextSibling
-        while (item){            
-            if (item.qqFileId == id) return item;            
-            item = item.nextSibling;
-        }          
-    },
-    /**
-     * delegate click event for cancel link 
-     **/
-    _bindCancelEvent: function(){
-        var self = this,
-            list = this._listElement;            
-        
-        qq.attach(list, 'click', function(e){            
-            e = e || window.event;
-            var target = e.target || e.srcElement;
-            
-            if (qq.hasClass(target, self._classes.cancel)){                
-                qq.preventDefault(e);
-               
-                var item = target.parentNode;
-                self._handler.cancel(item.qqFileId);
-                qq.remove(item);
-            }
-        });
-    }    
-});
-    
-qq.UploadDropZone = function(o){
-    this._options = {
-        element: null,  
-        onEnter: function(e){},
-        onLeave: function(e){},  
-        // is not fired when leaving element by hovering descendants   
-        onLeaveNotDescendants: function(e){},   
-        onDrop: function(e){}                       
-    };
-    qq.extend(this._options, o); 
-    
-    this._element = this._options.element;
-    
-    this._disableDropOutside();
-    this._attachEvents();   
-};
-
-qq.UploadDropZone.prototype = {
-    _disableDropOutside: function(e){
-        // run only once for all instances
-        if (!qq.UploadDropZone.dropOutsideDisabled ){
-
-            qq.attach(document, 'dragover', function(e){
-                if (e.dataTransfer){
-                    e.dataTransfer.dropEffect = 'none';
-                    e.preventDefault(); 
-                }           
-            });
-            
-            qq.UploadDropZone.dropOutsideDisabled = true; 
-        }        
-    },
-    _attachEvents: function(){
-        var self = this;              
-                  
-        qq.attach(self._element, 'dragover', function(e){
-            if (!self._isValidFileDrag(e)) return;
-            
-            var effect = e.dataTransfer.effectAllowed;
-            if (effect == 'move' || effect == 'linkMove'){
-                e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)    
-            } else {                    
-                e.dataTransfer.dropEffect = 'copy'; // for Chrome
-            }
-                                                     
-            e.stopPropagation();
-            e.preventDefault();                                                                    
-        });
-        
-        qq.attach(self._element, 'dragenter', function(e){
-            if (!self._isValidFileDrag(e)) return;
-                        
-            self._options.onEnter(e);
-        });
-        
-        qq.attach(self._element, 'dragleave', function(e){
-            if (!self._isValidFileDrag(e)) return;
-            
-            self._options.onLeave(e);
-            
-            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);                      
-            // do not fire when moving a mouse over a descendant
-            if (qq.contains(this, relatedTarget)) return;
-                        
-            self._options.onLeaveNotDescendants(e); 
-        });
-                
-        qq.attach(self._element, 'drop', function(e){
-            if (!self._isValidFileDrag(e)) return;
-            
-            e.preventDefault();
-            self._options.onDrop(e);
-        });          
-    },
-    _isValidFileDrag: function(e){
-        var dt = e.dataTransfer,
-            // do not check dt.types.contains in webkit, because it crashes safari 4            
-            isWebkit = navigator.userAgent.indexOf("AppleWebKit") > -1;                        
-
-        // dt.effectAllowed is none in Safari 5
-        // dt.types.contains check is for firefox            
-        return dt && dt.effectAllowed != 'none' && 
-            (dt.files || (!isWebkit && dt.types.contains && dt.types.contains('Files')));
-        
-    }        
-}; 
-
-qq.UploadButton = function(o){
-    this._options = {
-        element: null,  
-        // if set to true adds multiple attribute to file input      
-        multiple: false,
-        // name attribute of file input
-        name: 'file',
-        onChange: function(input){},
-        hoverClass: 'qq-upload-button-hover',
-        focusClass: 'qq-upload-button-focus'                       
-    };
-    
-    qq.extend(this._options, o);
-        
-    this._element = this._options.element;
-    
-    // make button suitable container for input
-    qq.css(this._element, {
-        position: 'relative',
-        overflow: 'hidden',
-        // Make sure browse button is in the right side
-        // in Internet Explorer
-        direction: 'ltr'
-    });   
-    
-    this._input = this._createInput();
-};
-
-qq.UploadButton.prototype = {
-    /* returns file input element */    
-    getInput: function(){
-        return this._input;
-    },
-    /* cleans/recreates the file input */
-    reset: function(){
-        if (this._input.parentNode){
-            qq.remove(this._input);    
-        }                
-        
-        qq.removeClass(this._element, this._options.focusClass);
-        this._input = this._createInput();
-    },    
-    _createInput: function(){                
-        var input = document.createElement("input");
-        
-        if (this._options.multiple){
-            input.setAttribute("multiple", "multiple");
-        }
-                
-        input.setAttribute("type", "file");
-        input.setAttribute("name", this._options.name);
-        
-        qq.css(input, {
-            position: 'absolute',
-            // in Opera only 'browse' button
-            // is clickable and it is located at
-            // the right side of the input
-            right: 0,
-            top: 0,
-            fontFamily: 'Arial',
-            // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
-            fontSize: '118px',
-            margin: 0,
-            padding: 0,
-            cursor: 'pointer',
-            opacity: 0
-        });
-        
-        this._element.appendChild(input);
-
-        var self = this;
-        qq.attach(input, 'change', function(){
-            self._options.onChange(input);
-        });
-                
-        qq.attach(input, 'mouseover', function(){
-            qq.addClass(self._element, self._options.hoverClass);
-        });
-        qq.attach(input, 'mouseout', function(){
-            qq.removeClass(self._element, self._options.hoverClass);
-        });
-        qq.attach(input, 'focus', function(){
-            qq.addClass(self._element, self._options.focusClass);
-        });
-        qq.attach(input, 'blur', function(){
-            qq.removeClass(self._element, self._options.focusClass);
-        });
-
-        // IE and Opera, unfortunately have 2 tab stops on file input
-        // which is unacceptable in our case, disable keyboard access
-        if (window.attachEvent){
-            // it is IE or Opera
-            input.setAttribute('tabIndex', "-1");
-        }
-
-        return input;            
-    }        
-};
-
-/**
- * Class for uploading files, uploading itself is handled by child classes
- */
-qq.UploadHandlerAbstract = function(o){
-    this._options = {
-        debug: false,
-        action: '/upload.php',
-        // maximum number of concurrent uploads        
-        maxConnections: 999,
-        onProgress: function(id, fileName, loaded, total){},
-        onComplete: function(id, fileName, response){},
-        onCancel: function(id, fileName){}
-    };
-    qq.extend(this._options, o);    
-    
-    this._queue = [];
-    // params for files in queue
-    this._params = [];
-};
-qq.UploadHandlerAbstract.prototype = {
-    log: function(str){
-        if (this._options.debug && window.console) console.log('[uploader] ' + str);        
-    },
-    /**
-     * Adds file or file input to the queue
-     * @returns id
-     **/    
-    add: function(file){},
-    /**
-     * Sends the file identified by id and additional query params to the server
-     */
-    upload: function(id, params){
-        var len = this._queue.push(id);
-
-        var copy = {};        
-        qq.extend(copy, params);
-        this._params[id] = copy;        
-                
-        // if too many active uploads, wait...
-        if (len <= this._options.maxConnections){               
-            this._upload(id, this._params[id]);
-        }
-    },
-    /**
-     * Cancels file upload by id
-     */
-    cancel: function(id){
-        this._cancel(id);
-        this._dequeue(id);
-    },
-    /**
-     * Cancells all uploads
-     */
-    cancelAll: function(){
-        for (var i=0; i<this._queue.length; i++){
-            this._cancel(this._queue[i]);
-        }
-        this._queue = [];
-    },
-    /**
-     * Returns name of the file identified by id
-     */
-    getName: function(id){},
-    /**
-     * Returns size of the file identified by id
-     */          
-    getSize: function(id){},
-    /**
-     * Returns id of files being uploaded or
-     * waiting for their turn
-     */
-    getQueue: function(){
-        return this._queue;
-    },
-    /**
-     * Actual upload method
-     */
-    _upload: function(id){},
-    /**
-     * Actual cancel method
-     */
-    _cancel: function(id){},     
-    /**
-     * Removes element from queue, starts upload of next
-     */
-    _dequeue: function(id){
-        var i = qq.indexOf(this._queue, id);
-        this._queue.splice(i, 1);
-                
-        var max = this._options.maxConnections;
-        
-        if (this._queue.length >= max && i < max){
-            var nextId = this._queue[max-1];
-            this._upload(nextId, this._params[nextId]);
-        }
-    }        
-};
-
-/**
- * Class for uploading files using form and iframe
- * @inherits qq.UploadHandlerAbstract
- */
-qq.UploadHandlerForm = function(o){
-    qq.UploadHandlerAbstract.apply(this, arguments);
-       
-    this._inputs = {};
-};
-// @inherits qq.UploadHandlerAbstract
-qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
-
-qq.extend(qq.UploadHandlerForm.prototype, {
-    add: function(fileInput){
-        fileInput.setAttribute('name', 'qqfile');
-        var id = 'qq-upload-handler-iframe' + qq.getUniqueId();       
-        
-        this._inputs[id] = fileInput;
-        
-        // remove file input from DOM
-        if (fileInput.parentNode){
-            qq.remove(fileInput);
-        }
-                
-        return id;
-    },
-    getName: function(id){
-        // get input value and remove path to normalize
-        return this._inputs[id].value.replace(/.*(\/|\\)/, "");
-    },    
-    _cancel: function(id){
-        this._options.onCancel(id, this.getName(id));
-        
-        delete this._inputs[id];        
-
-        var iframe = document.getElementById(id);
-        if (iframe){
-            // to cancel request set src to something else
-            // we use src="javascript:false;" because it doesn't
-            // trigger ie6 prompt on https
-            iframe.setAttribute('src', 'javascript:false;');
-
-            qq.remove(iframe);
-        }
-    },     
-    _upload: function(id, params){                        
-        var input = this._inputs[id];
-        
-        if (!input){
-            throw new Error('file with passed id was not added, or already uploaded or cancelled');
-        }                
-
-        var fileName = this.getName(id);
-                
-        var iframe = this._createIframe(id);
-        var form = this._createForm(iframe, params);
-        form.appendChild(input);
-
-        var self = this;
-        this._attachLoadEvent(iframe, function(){                                 
-            self.log('iframe loaded');
-            
-            var response = self._getIframeContentJSON(iframe);
-
-            self._options.onComplete(id, fileName, response);
-            self._dequeue(id);
-            
-            delete self._inputs[id];
-            // timeout added to fix busy state in FF3.6
-            setTimeout(function(){
-                qq.remove(iframe);
-            }, 1);
-        });
-
-        form.submit();        
-        qq.remove(form);        
-        
-        return id;
-    }, 
-    _attachLoadEvent: function(iframe, callback){
-        qq.attach(iframe, 'load', function(){
-            // when we remove iframe from dom
-            // the request stops, but in IE load
-            // event fires
-            if (!iframe.parentNode){
-                return;
-            }
-
-            // fixing Opera 10.53
-            if (iframe.contentDocument &&
-                iframe.contentDocument.body &&
-                iframe.contentDocument.body.innerHTML == "false"){
-                // In Opera event is fired second time
-                // when body.innerHTML changed from false
-                // to server response approx. after 1 sec
-                // when we upload file with iframe
-                return;
-            }
-
-            callback();
-        });
-    },
-    /**
-     * Returns json object received by iframe from server.
-     */
-    _getIframeContentJSON: function(iframe){
-        // iframe.contentWindow.document - for IE<7
-        var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
-            response;
-        
-        this.log("converting iframe's innerHTML to JSON");
-        this.log("innerHTML = " + doc.body.innerHTML);
-                        
-        try {
-            response = eval("(" + doc.body.innerHTML + ")");
-        } catch(err){
-            response = {};
-        }        
-
-        return response;
-    },
-    /**
-     * Creates iframe with unique name
-     */
-    _createIframe: function(id){
-        // We can't use following code as the name attribute
-        // won't be properly registered in IE6, and new window
-        // on form submit will open
-        // var iframe = document.createElement('iframe');
-        // iframe.setAttribute('name', id);
-
-        var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
-        // src="javascript:false;" removes ie6 prompt on https
-
-        iframe.setAttribute('id', id);
-
-        iframe.style.display = 'none';
-        document.body.appendChild(iframe);
-
-        return iframe;
-    },
-    /**
-     * Creates form, that will be submitted to iframe
-     */
-    _createForm: function(iframe, params){
-        // We can't use the following code in IE6
-        // var form = document.createElement('form');
-        // form.setAttribute('method', 'post');
-        // form.setAttribute('enctype', 'multipart/form-data');
-        // Because in this case file won't be attached to request
-        var form = qq.toElement('<form method="post" enctype="multipart/form-data"></form>');
-
-        var queryString = qq.obj2url(params, this._options.action);
-
-        form.setAttribute('action', queryString);
-        form.setAttribute('target', iframe.name);
-        form.style.display = 'none';
-        document.body.appendChild(form);
-
-        return form;
-    }
-});
-
-/**
- * Class for uploading files using xhr
- * @inherits qq.UploadHandlerAbstract
- */
-qq.UploadHandlerXhr = function(o){
-    qq.UploadHandlerAbstract.apply(this, arguments);
-
-    this._files = [];
-    this._xhrs = [];
-    
-    // current loaded size in bytes for each file 
-    this._loaded = [];
-};
-
-// static method
-qq.UploadHandlerXhr.isSupported = function(){
-    var input = document.createElement('input');
-    input.type = 'file';        
-    
-    return (
-        'multiple' in input &&
-        typeof File != "undefined" &&
-        typeof (new XMLHttpRequest()).upload != "undefined" );       
-};
-
-// @inherits qq.UploadHandlerAbstract
-qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
-
-qq.extend(qq.UploadHandlerXhr.prototype, {
-    /**
-     * Adds file to the queue
-     * Returns id to use with upload, cancel
-     **/    
-    add: function(file){
-        if (!(file instanceof File)){
-            throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
-        }
-                
-        return this._files.push(file) - 1;        
-    },
-    getName: function(id){        
-        var file = this._files[id];
-        // fix missing name in Safari 4
-        return file.fileName != null ? file.fileName : file.name;       
-    },
-    getSize: function(id){
-        var file = this._files[id];
-        return file.fileSize != null ? file.fileSize : file.size;
-    },    
-    /**
-     * Returns uploaded bytes for file identified by id 
-     */    
-    getLoaded: function(id){
-        return this._loaded[id] || 0; 
-    },
-    /**
-     * Sends the file identified by id and additional query params to the server
-     * @param {Object} params name-value string pairs
-     */    
-    _upload: function(id, params){
-        var file = this._files[id],
-            name = this.getName(id),
-            size = this.getSize(id);
-                
-        this._loaded[id] = 0;
-                                
-        var xhr = this._xhrs[id] = new XMLHttpRequest();
-        var self = this;
-                                        
-        xhr.upload.onprogress = function(e){
-            if (e.lengthComputable){
-                self._loaded[id] = e.loaded;
-                self._options.onProgress(id, name, e.loaded, e.total);
-            }
-        };
-
-        xhr.onreadystatechange = function(){            
-            if (xhr.readyState == 4){
-                self._onComplete(id, xhr);                    
-            }
-        };
-
-        // build query string
-        params = params || {};
-        params['qqfile'] = name;
-        var queryString = qq.obj2url(params, this._options.action);
-
-        xhr.open("POST", queryString, true);
-        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
-        xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
-        xhr.setRequestHeader("Content-Type", "application/octet-stream");
-        xhr.send(file);
-    },
-    _onComplete: function(id, xhr){
-        // the request was aborted/cancelled
-        if (!this._files[id]) return;
-        
-        var name = this.getName(id);
-        var size = this.getSize(id);
-        
-        this._options.onProgress(id, name, size, size);
-                
-        if (xhr.status == 200){
-            this.log("xhr - server response received");
-            this.log("responseText = " + xhr.responseText);
-                        
-            var response;
-                    
-            try {
-                response = eval("(" + xhr.responseText + ")");
-            } catch(err){
-                response = {};
-            }
-            
-            this._options.onComplete(id, name, response);
-                        
-        } else {                   
-            this._options.onComplete(id, name, {});
-        }
-                
-        this._files[id] = null;
-        this._xhrs[id] = null;    
-        this._dequeue(id);                    
-    },
-    _cancel: function(id){
-        this._options.onCancel(id, this.getName(id));
-        
-        this._files[id] = null;
-        
-        if (this._xhrs[id]){
-            this._xhrs[id].abort();
-            this._xhrs[id] = null;                                   
-        }
-    }
-});

Некоторые файлы не были показаны из-за большого количества измененных файлов