diff --git a/.bowerrc b/.bowerrc
new file mode 100644
index 000000000..e05a87695
--- /dev/null
+++ b/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "components"
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..c62ef0a4a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+ - "0.10.13"
+script: grunt test
diff --git a/CHANGES.rst b/CHANGES.rst
index afcc144fa..3ecdb1bb3 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,8 +1,8 @@
Changelog
=========
-0.5 (Unreleased)
-----------------
+0.5.0 (2013-07-30)
+------------------
- #09 Remove dependency on AMD/require.js [jcbrand]
- #22 Fixed compare operator in strophe.muc [sonata82]
@@ -11,9 +11,10 @@ Changelog
- #25 Using span with css instead of img [matheus-morfi]
- #26 Only the first minute digit shown in chatbox. [jcbrand]
- #28 Add Brazilian Portuguese translations [matheus-morfi]
+- Use Bower to manage 3rd party dependencies. [jcbrand]
-0.4 (2013-06-03)
-----------------
+0.4.0 (2013-06-03)
+------------------
- CSS tweaks: fixed overflowing text in status message and chatrooms list. [jcbrand]
- Bugfix: Couldn't join chatroom when clicking from a list of rooms. [jcbrand]
@@ -24,8 +25,8 @@ Changelog
- Reconnect automatically when the connection drops. [jcbrand]
- Add support for internationalization. [jcbrand]
-0.3 (2013-05-21)
-----------------
+0.3.0 (2013-05-21)
+------------------
- Add vCard support [jcbrand]
- Remember custom status messages upon reload. [jcbrand]
@@ -40,15 +41,15 @@ Changelog
- Multi-user chatrooms are now configurable. [jcbrand]
-0.2 (2013-03-28)
-----------------
+0.2.0 (2013-03-28)
+------------------
- Performance enhancements and general script cleanup [ichim-david]
- Add "Connecting to chat..." info [alecghica]
- Various smaller improvements and bugfixes [jcbrand]
-0.1 (2012-06-12)
-----------------
+0.1.0 (2012-06-12)
+------------------
- Created [jcbrand]
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 1f3829b5c..f6ebe90f9 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -2,7 +2,7 @@
Contributing to Converse.js
===========================
-Thanks for contributing to Converse.js_!
+Thanks for contributing to Converse.js_.
Please follow the usual github workflow. Create your own local fork of this repository,
make your changes and then submit a pull request.
@@ -19,12 +19,27 @@ for testing.
Take a look at ``tests.html`` and ``spec/MainSpec.js`` to see how
the tests are implemented.
-Check that the tests run
-------------------------
+If you are unsure how to write tests, please `contact me`_ and I'll be happy to
+help.
-Check that the Jasmine BDD tests complete sucessfully. Open tests.html in your
+Check that the tests pass
+-------------------------
+
+Check that the Jasmine tests complete sucessfully. Open tests.html in your
browser, and the tests will run automatically.
You can see the current test output online, here: http://conversejs.org/tests.html
+On the command line you can run ``grunt test`` (if you have before run ``npm
+install``).
+
+Check your code for errors or bad habits by running JSHint
+----------------------------------------------------------
+
+If you haven't yet done so, run ``npm install`` to install all development
+dependencies.
+
+Then run ``grunt jshint`` and check the output.
+
.. _Converse.js: http://conversejs.org
+.. _`contact me`: http://opkode.com/contact.html
diff --git a/Gruntfile.js b/Gruntfile.js
index 65d8d2ac0..d9ba99f17 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,20 +1,115 @@
module.exports = function(grunt) {
+ var cfg = require('./package.json');
grunt.initConfig({
jshint: {
- options: {
- trailing: true
+ options: {
+ trailing: true
+ },
+ target: {
+ src : [
+ 'converse.js',
+ 'mock.js',
+ 'main.js',
+ 'tests_main.js',
+ 'spec/*.js'
+ ]
+ }
},
- target: {
- src : [
- 'converse.js',
- 'mock.js',
- 'main.js',
- 'tests_main.js',
- 'spec/*.js'
- ]
- }
+ cssmin: {
+ options: {
+ banner: "/*"+
+ "* Converse.js (Web-based XMPP instant messaging client) \n"+
+ "* http://conversejs.org \n"+
+ "* Copyright (c) 2012, Jan-Carel Brand \n"+
+ "* Dual licensed under the MIT and GPL Licenses \n"+
+ "*/"
+ },
+ minify: {
+ dest: 'converse-'+cfg.version+'.min.css',
+ src: ['converse.css']
+ }
+ },
+ requirejs: {
+ compile: {
+ options: {
+ baseUrl: ".",
+ name: "main",
+ out: "converse-"+cfg.version+".min.js",
+ paths: {
+ "require": "components/requirejs/require",
+ "jquery": "components/jquery/jquery",
+ "jed": "components/jed/jed",
+ "locales": "locale/locales",
+ "af": "locale/af/LC_MESSAGES/af",
+ "en": "locale/en/LC_MESSAGES/en",
+ "de": "locale/de/LC_MESSAGES/de",
+ "es": "locale/es/LC_MESSAGES/es",
+ "it": "locale/it/LC_MESSAGES/it",
+ "pt_BR": "locale/pt_BR/LC_MESSAGES/pt_BR",
+ "sjcl": "components/sjcl/sjcl",
+ "tinysort": "components/tinysort/src/jquery.tinysort",
+ "underscore": "components/underscore/underscore",
+ "backbone": "components/backbone/backbone",
+ "localstorage": "components/backbone.localStorage/backbone.localStorage",
+ "strophe": "components/strophe/strophe",
+ "strophe.muc": "components/strophe.muc/index",
+ "strophe.roster": "components/strophe.roster/index",
+ "strophe.vcard": "components/strophe.vcard/index",
+ "strophe.disco": "components/strophe.disco/index"
+ },
+ done: function(done, output) {
+ var duplicates = require('rjs-build-analysis').duplicates(output);
+ if (duplicates.length > 0) {
+ grunt.log.subhead('Duplicates found in requirejs build:');
+ grunt.log.warn(duplicates);
+ done(new Error('r.js built duplicate modules, please check the excludes option.'));
+ }
+ done();
+ }
+ }
+ }
}
});
+ grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-jshint');
- grunt.registerTask('default', ['jshint']);
+ grunt.loadNpmTasks('grunt-contrib-requirejs');
+
+ grunt.registerTask('test', 'Run Tests', function () {
+ var done = this.async();
+ var child_process = require('child_process');
+ var exec = child_process.exec;
+ exec('./node_modules/.bin/phantomjs '+
+ 'node_modules/jasmine-reporters/test/phantomjs-testrunner.js '+
+ __dirname+'/tests.html',
+ function (err, stdout, stderr) {
+ if (err) {
+ grunt.log.write('Tests failed with error code '+err.code);
+ grunt.log.write(stderr);
+ }
+ grunt.log.write(stdout);
+ done();
+ });
+ });
+
+ grunt.registerTask('fetch', 'Set up the development environment', function () {
+ var done = this.async();
+ var child_process = require('child_process');
+ var exec = child_process.exec;
+ exec('bower update && cd ./components/strophe && make normal',
+ function (err, stdout, stderr) {
+ if (err) {
+ grunt.log.write('build failed with error code '+err.code);
+ grunt.log.write(stderr);
+ }
+ grunt.log.write(stdout);
+ done();
+ });
+ });
+
+ // TODO: update CHANGES.txt with release date
+ grunt.registerTask('release', 'Create a new release', ['requirejs', 'cssmin']);
+
+ grunt.registerTask('check', 'Perform all checks (e.g. before releasing)', function () {
+ grunt.task.run('jshint', 'test');
+ });
};
diff --git a/Libraries/backbone.js b/Libraries/backbone.js
deleted file mode 100644
index 3512d42fb..000000000
--- a/Libraries/backbone.js
+++ /dev/null
@@ -1,1571 +0,0 @@
-// Backbone.js 1.0.0
-
-// (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc.
-// Backbone may be freely distributed under the MIT license.
-// For all details and documentation:
-// http://backbonejs.org
-
-(function(){
-
- // Initial Setup
- // -------------
-
- // Save a reference to the global object (`window` in the browser, `exports`
- // on the server).
- var root = this;
-
- // 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 local references to array methods we'll want to use later.
- var array = [];
- var push = array.push;
- var slice = array.slice;
- var splice = array.splice;
-
- // The top-level namespace. All public Backbone classes and modules will
- // be attached to this. Exported for both the browser and the server.
- var Backbone;
- if (typeof exports !== 'undefined') {
- Backbone = exports;
- } else {
- Backbone = root.Backbone = {};
- }
-
- // Current version of the library. Keep in sync with `package.json`.
- Backbone.VERSION = '1.0.0';
-
- // Require Underscore, if we're on the server, and it's not already present.
- var _ = root._;
- if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
-
- // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
- // the `$` variable.
- Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;
-
- // 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 this;
- };
-
- // 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
- // ---------------
-
- // 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 an event to a `callback` function. Passing `"all"` will bind
- // the callback to all events fired.
- on: function(name, callback, context) {
- if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
- this._events || (this._events = {});
- var events = this._events[name] || (this._events[name] = []);
- events.push({callback: callback, context: context, ctx: context || this});
- return this;
- },
-
- // Bind an event to only be triggered a single time. After the first time
- // the callback is invoked, it will be removed.
- once: function(name, callback, context) {
- if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
- var self = this;
- var once = _.once(function() {
- self.off(name, once);
- callback.apply(this, arguments);
- });
- once._callback = callback;
- return this.on(name, once, context);
- },
-
- // 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 `name` is null, removes all bound
- // callbacks for all events.
- off: function(name, callback, context) {
- var retain, ev, events, names, i, l, j, k;
- if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
- if (!name && !callback && !context) {
- this._events = {};
- return this;
- }
-
- names = name ? [name] : _.keys(this._events);
- for (i = 0, l = names.length; i < l; i++) {
- name = names[i];
- if (events = this._events[name]) {
- this._events[name] = retain = [];
- if (callback || context) {
- for (j = 0, k = events.length; j < k; j++) {
- ev = events[j];
- if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
- (context && context !== ev.context)) {
- retain.push(ev);
- }
- }
- }
- if (!retain.length) delete this._events[name];
- }
- }
-
- 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(name) {
- if (!this._events) return this;
- var args = slice.call(arguments, 1);
- if (!eventsApi(this, 'trigger', name, args)) return this;
- var events = this._events[name];
- var allEvents = this._events.all;
- if (events) triggerEvents(events, args);
- if (allEvents) triggerEvents(allEvents, arguments);
- return this;
- },
-
- // Tell this object to stop listening to either specific events ... or
- // to every object it's currently listening to.
- stopListening: function(obj, name, callback) {
- var listeners = this._listeners;
- if (!listeners) return this;
- var deleteListener = !name && !callback;
- if (typeof name === 'object') callback = this;
- if (obj) (listeners = {})[obj._listenerId] = obj;
- for (var id in listeners) {
- listeners[id].off(name, callback, this);
- if (deleteListener) delete this._listeners[id];
- }
- return this;
- }
-
- };
-
- // Regular expression used to split event strings.
- var eventSplitter = /\s+/;
-
- // Implement fancy features of the Events API such as multiple event
- // names `"change blur"` and jQuery-style event maps `{change: action}`
- // in terms of the existing API.
- var eventsApi = function(obj, action, name, rest) {
- if (!name) return true;
-
- // Handle event maps.
- if (typeof name === 'object') {
- for (var key in name) {
- obj[action].apply(obj, [key, name[key]].concat(rest));
- }
- return false;
- }
-
- // Handle space separated event names.
- if (eventSplitter.test(name)) {
- var names = name.split(eventSplitter);
- for (var i = 0, l = names.length; i < l; i++) {
- obj[action].apply(obj, [names[i]].concat(rest));
- }
- return false;
- }
-
- return true;
- };
-
- // A difficult-to-believe, but optimized internal dispatch function for
- // triggering events. Tries to keep the usual cases speedy (most internal
- // Backbone events have 3 arguments).
- var triggerEvents = function(events, args) {
- var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
- switch (args.length) {
- case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
- case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
- case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
- case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
- default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
- }
- };
-
- var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
-
- // Inversion-of-control versions of `on` and `once`. Tell *this* object to
- // listen to an event in another object ... keeping track of what it's
- // listening to.
- _.each(listenMethods, function(implementation, method) {
- Events[method] = function(obj, name, callback) {
- var listeners = this._listeners || (this._listeners = {});
- var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
- listeners[id] = obj;
- if (typeof name === 'object') callback = this;
- obj[implementation](name, callback, this);
- return this;
- };
- });
-
- // Aliases for backwards compatibility.
- Events.bind = Events.on;
- Events.unbind = Events.off;
-
- // Allow the `Backbone` object to serve as a global event bus, for folks who
- // want global "pubsub" in a convenient place.
- _.extend(Backbone, Events);
-
- // Backbone.Model
- // --------------
-
- // Backbone **Models** are the basic data object in the framework --
- // frequently representing a row in a table in a database on your server.
- // A discrete chunk of data and a bunch of useful, related methods for
- // performing computations and transformations on that data.
-
- // Create a new model with the specified attributes. A client id (`cid`)
- // is automatically generated and assigned for you.
- var Model = Backbone.Model = function(attributes, options) {
- var defaults;
- var attrs = attributes || {};
- options || (options = {});
- this.cid = _.uniqueId('c');
- this.attributes = {};
- _.extend(this, _.pick(options, modelOptions));
- if (options.parse) attrs = this.parse(attrs, options) || {};
- if (defaults = _.result(this, 'defaults')) {
- attrs = _.defaults({}, attrs, defaults);
- }
- this.set(attrs, options);
- this.changed = {};
- this.initialize.apply(this, arguments);
- };
-
- // A list of options to be attached directly to the model, if provided.
- var modelOptions = ['url', 'urlRoot', 'collection'];
-
- // Attach all inheritable methods to the Model prototype.
- _.extend(Model.prototype, Events, {
-
- // A hash of attributes whose current and previous value differ.
- changed: null,
-
- // The value returned during the last failed validation.
- validationError: 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);
- },
-
- // Proxy `Backbone.sync` by default -- but override this if you need
- // custom syncing semantics for *this* particular model.
- sync: function() {
- return Backbone.sync.apply(this, arguments);
- },
-
- // Get the value of an attribute.
- get: function(attr) {
- return this.attributes[attr];
- },
-
- // Get the HTML-escaped value of an attribute.
- escape: function(attr) {
- return _.escape(this.get(attr));
- },
-
- // 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"`. This is
- // the core primitive operation of a model, updating the data and notifying
- // anyone who needs to know about the change in state. The heart of the beast.
- set: function(key, val, options) {
- var attr, attrs, unset, changes, silent, changing, prev, current;
- if (key == null) return this;
-
- // Handle both `"key", value` and `{key: value}` -style arguments.
- if (typeof key === 'object') {
- attrs = key;
- options = val;
- } else {
- (attrs = {})[key] = val;
- }
-
- options || (options = {});
-
- // Run validation.
- if (!this._validate(attrs, options)) return false;
-
- // Extract attributes and options.
- unset = options.unset;
- silent = options.silent;
- changes = [];
- changing = this._changing;
- this._changing = true;
-
- if (!changing) {
- this._previousAttributes = _.clone(this.attributes);
- this.changed = {};
- }
- current = this.attributes, prev = this._previousAttributes;
-
- // Check for changes of `id`.
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
-
- // For each `set` attribute, update or delete the current value.
- for (attr in attrs) {
- val = attrs[attr];
- if (!_.isEqual(current[attr], val)) changes.push(attr);
- if (!_.isEqual(prev[attr], val)) {
- this.changed[attr] = val;
- } else {
- delete this.changed[attr];
- }
- unset ? delete current[attr] : current[attr] = val;
- }
-
- // Trigger all relevant attribute changes.
- if (!silent) {
- if (changes.length) this._pending = true;
- for (var i = 0, l = changes.length; i < l; i++) {
- this.trigger('change:' + changes[i], this, current[changes[i]], options);
- }
- }
-
- // You might be wondering why there's a `while` loop here. Changes can
- // be recursively nested within `"change"` events.
- if (changing) return this;
- if (!silent) {
- while (this._pending) {
- this._pending = false;
- this.trigger('change', this, options);
- }
- }
- this._pending = false;
- this._changing = false;
- return this;
- },
-
- // Remove an attribute from the model, firing `"change"`. `unset` is a noop
- // if the attribute doesn't exist.
- unset: function(attr, options) {
- return this.set(attr, void 0, _.extend({}, options, {unset: true}));
- },
-
- // Clear all attributes on the model, firing `"change"`.
- clear: function(options) {
- var attrs = {};
- for (var key in this.attributes) attrs[key] = void 0;
- return this.set(attrs, _.extend({}, options, {unset: true}));
- },
-
- // 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 (attr == null) 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;
- var old = this._changing ? this._previousAttributes : this.attributes;
- 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 (attr == null || !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);
- },
-
- // Fetch the model from the server. If the server's representation of the
- // model differs from its current attributes, they will be overridden,
- // triggering a `"change"` event.
- fetch: function(options) {
- options = options ? _.clone(options) : {};
- if (options.parse === void 0) options.parse = true;
- var model = this;
- var success = options.success;
- options.success = function(resp) {
- if (!model.set(model.parse(resp, options), options)) return false;
- if (success) success(model, resp, options);
- model.trigger('sync', model, resp, options);
- };
- wrapError(this, options);
- return this.sync('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, val, options) {
- var attrs, method, xhr, attributes = this.attributes;
-
- // Handle both `"key", value` and `{key: value}` -style arguments.
- if (key == null || typeof key === 'object') {
- attrs = key;
- options = val;
- } else {
- (attrs = {})[key] = val;
- }
-
- // If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`.
- if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false;
-
- options = _.extend({validate: true}, options);
-
- // Do not persist invalid models.
- if (!this._validate(attrs, options)) return false;
-
- // Set temporary attributes if `{wait: true}`.
- if (attrs && options.wait) {
- this.attributes = _.extend({}, attributes, attrs);
- }
-
- // After a successful server-side save, the client is (optionally)
- // updated with the server-side state.
- if (options.parse === void 0) options.parse = true;
- var model = this;
- var success = options.success;
- options.success = function(resp) {
- // Ensure attributes are restored during synchronous saves.
- model.attributes = attributes;
- var serverAttrs = model.parse(resp, options);
- if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
- if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
- return false;
- }
- if (success) success(model, resp, options);
- model.trigger('sync', model, resp, options);
- };
- wrapError(this, options);
-
- method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
- if (method === 'patch') options.attrs = attrs;
- xhr = this.sync(method, this, options);
-
- // Restore attributes.
- if (attrs && options.wait) this.attributes = attributes;
-
- 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 destroy = function() {
- model.trigger('destroy', model, model.collection, options);
- };
-
- options.success = function(resp) {
- if (options.wait || model.isNew()) destroy();
- if (success) success(model, resp, options);
- if (!model.isNew()) model.trigger('sync', model, resp, options);
- };
-
- if (this.isNew()) {
- options.success();
- return false;
- }
- wrapError(this, options);
-
- var xhr = this.sync('delete', this, options);
- if (!options.wait) destroy();
- 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 = _.result(this, 'urlRoot') || _.result(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, options) {
- 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;
- },
-
- // Check if the model is currently in a valid state.
- isValid: function(options) {
- return this._validate({}, _.extend(options || {}, { validate: true }));
- },
-
- // Run validation against the next complete set of model attributes,
- // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
- _validate: function(attrs, options) {
- if (!options.validate || !this.validate) return true;
- attrs = _.extend({}, this.attributes, attrs);
- var error = this.validationError = this.validate(attrs, options) || null;
- if (!error) return true;
- this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error}));
- return false;
- }
-
- });
-
- // Underscore methods that we want to implement on the Model.
- var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
-
- // Mix in each Underscore method as a proxy to `Model#attributes`.
- _.each(modelMethods, function(method) {
- Model.prototype[method] = function() {
- var args = slice.call(arguments);
- args.unshift(this.attributes);
- return _[method].apply(_, args);
- };
- });
-
- // Backbone.Collection
- // -------------------
-
- // If models tend to represent a single row of data, a Backbone Collection is
- // more analagous to a table full of data ... or a small slice or page of that
- // table, or a collection of rows that belong together for a particular reason
- // -- all of the messages in this particular folder, all of the documents
- // belonging to this particular author, and so on. Collections maintain
- // indexes of their models, both in order, and for lookup by `id`.
-
- // Create a new **Collection**, perhaps to contain a specific type of `model`.
- // 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.url) this.url = options.url;
- if (options.model) this.model = options.model;
- if (options.comparator !== void 0) this.comparator = options.comparator;
- this._reset();
- this.initialize.apply(this, arguments);
- if (models) this.reset(models, _.extend({silent: true}, options));
- };
-
- // Default options for `Collection#set`.
- var setOptions = {add: true, remove: true, merge: true};
- var addOptions = {add: true, merge: false, remove: false};
-
- // 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); });
- },
-
- // Proxy `Backbone.sync` by default.
- sync: function() {
- return Backbone.sync.apply(this, arguments);
- },
-
- // Add a model, or list of models to the set.
- add: function(models, options) {
- return this.set(models, _.defaults(options || {}, addOptions));
- },
-
- // Remove a model, or a list of models from the set.
- remove: function(models, options) {
- models = _.isArray(models) ? models.slice() : [models];
- options || (options = {});
- var i, l, index, model;
- for (i = 0, l = models.length; i < l; i++) {
- model = this.get(models[i]);
- if (!model) continue;
- delete this._byId[model.id];
- delete this._byId[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;
- },
-
- // Update a collection by `set`-ing a new list of models, adding new ones,
- // removing models that are no longer present, and merging models that
- // already exist in the collection, as necessary. Similar to **Model#set**,
- // the core operation for updating the data contained by the collection.
- set: function(models, options) {
- options = _.defaults(options || {}, setOptions);
- if (options.parse) models = this.parse(models, options);
- if (!_.isArray(models)) models = models ? [models] : [];
- var i, l, model, attrs, existing, sort;
- var at = options.at;
- var sortable = this.comparator && (at == null) && options.sort !== false;
- var sortAttr = _.isString(this.comparator) ? this.comparator : null;
- var toAdd = [], toRemove = [], modelMap = {};
-
- // Turn bare objects into model references, and prevent invalid models
- // from being added.
- for (i = 0, l = models.length; i < l; i++) {
- if (!(model = this._prepareModel(models[i], options))) continue;
-
- // If a duplicate is found, prevent it from being added and
- // optionally merge it into the existing model.
- if (existing = this.get(model)) {
- if (options.remove) modelMap[existing.cid] = true;
- if (options.merge) {
- existing.set(model.attributes, options);
- if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
- }
-
- // This is a new model, push it to the `toAdd` list.
- } else if (options.add) {
- toAdd.push(model);
-
- // Listen to added models' events, and index models for lookup by
- // `id` and by `cid`.
- model.on('all', this._onModelEvent, this);
- this._byId[model.cid] = model;
- if (model.id != null) this._byId[model.id] = model;
- }
- }
-
- // Remove nonexistent models if appropriate.
- if (options.remove) {
- for (i = 0, l = this.length; i < l; ++i) {
- if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
- }
- if (toRemove.length) this.remove(toRemove, options);
- }
-
- // See if sorting is needed, update `length` and splice in new models.
- if (toAdd.length) {
- if (sortable) sort = true;
- this.length += toAdd.length;
- if (at != null) {
- splice.apply(this.models, [at, 0].concat(toAdd));
- } else {
- push.apply(this.models, toAdd);
- }
- }
-
- // Silently sort the collection if appropriate.
- if (sort) this.sort({silent: true});
-
- if (options.silent) return this;
-
- // Trigger `add` events.
- for (i = 0, l = toAdd.length; i < l; i++) {
- (model = toAdd[i]).trigger('add', model, this, options);
- }
-
- // Trigger `sort` if the collection was sorted.
- if (sort) this.trigger('sort', this, options);
- return this;
- },
-
- // 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 granular `add` or `remove` events. Fires `reset` when finished.
- // Useful for bulk operations and optimizations.
- reset: function(models, options) {
- options || (options = {});
- for (var i = 0, l = this.models.length; i < l; i++) {
- this._removeReference(this.models[i]);
- }
- options.previousModels = this.models;
- this._reset();
- this.add(models, _.extend({silent: true}, options));
- if (!options.silent) this.trigger('reset', this, options);
- return this;
- },
-
- // Add a model to the end of the collection.
- push: function(model, options) {
- model = this._prepareModel(model, options);
- this.add(model, _.extend({at: this.length}, 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;
- },
-
- // Slice out a sub-array of models from the collection.
- slice: function(begin, end) {
- return this.models.slice(begin, end);
- },
-
- // Get a model from the set by id.
- get: function(obj) {
- if (obj == null) return void 0;
- return this._byId[obj.id != null ? obj.id : obj.cid || obj];
- },
-
- // 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, first) {
- if (_.isEmpty(attrs)) return first ? void 0 : [];
- return this[first ? 'find' : 'filter'](function(model) {
- for (var key in attrs) {
- if (attrs[key] !== model.get(key)) return false;
- }
- return true;
- });
- },
-
- // Return the first model with matching attributes. Useful for simple cases
- // of `find`.
- findWhere: function(attrs) {
- return this.where(attrs, 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) {
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
- options || (options = {});
-
- // Run sort based on type of `comparator`.
- if (_.isString(this.comparator) || this.comparator.length === 1) {
- this.models = this.sortBy(this.comparator, this);
- } else {
- this.models.sort(_.bind(this.comparator, this));
- }
-
- if (!options.silent) this.trigger('sort', this, options);
- return this;
- },
-
- // Figure out the smallest index at which a model should be inserted so as
- // to maintain order.
- sortedIndex: function(model, value, context) {
- value || (value = this.comparator);
- var iterator = _.isFunction(value) ? value : function(model) {
- return model.get(value);
- };
- return _.sortedIndex(this.models, model, iterator, context);
- },
-
- // Pluck an attribute from each model in the collection.
- pluck: function(attr) {
- return _.invoke(this.models, 'get', attr);
- },
-
- // Fetch the default set of models for this collection, resetting the
- // collection when they arrive. If `reset: true` is passed, the response
- // data will be passed through the `reset` method instead of `set`.
- fetch: function(options) {
- options = options ? _.clone(options) : {};
- if (options.parse === void 0) options.parse = true;
- var success = options.success;
- var collection = this;
- options.success = function(resp) {
- var method = options.reset ? 'reset' : 'set';
- collection[method](resp, options);
- if (success) success(collection, resp, options);
- collection.trigger('sync', collection, resp, options);
- };
- wrapError(this, options);
- return this.sync('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) {
- options = options ? _.clone(options) : {};
- if (!(model = this._prepareModel(model, options))) return false;
- if (!options.wait) this.add(model, options);
- var collection = this;
- var success = options.success;
- options.success = function(resp) {
- if (options.wait) collection.add(model, options);
- if (success) success(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, options) {
- return resp;
- },
-
- // Create a new collection with an identical list of models as this one.
- clone: function() {
- return new this.constructor(this.models);
- },
-
- // Private method to reset all internal state. Called when the collection
- // is first initialized or reset.
- _reset: function() {
- this.length = 0;
- this.models = [];
- this._byId = {};
- },
-
- // Prepare a hash of attributes (or other model) to be added to this
- // collection.
- _prepareModel: function(attrs, options) {
- if (attrs instanceof Model) {
- if (!attrs.collection) attrs.collection = this;
- return attrs;
- }
- options || (options = {});
- options.collection = this;
- var model = new this.model(attrs, options);
- if (!model._validate(attrs, options)) {
- this.trigger('invalid', this, attrs, options);
- return false;
- }
- return model;
- },
-
- // Internal method to sever 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)];
- if (model.id != null) this._byId[model.id] = model;
- }
- this.trigger.apply(this, arguments);
- }
-
- });
-
- // Underscore methods that we want to implement on the Collection.
- // 90% of the core usefulness of Backbone Collections is actually implemented
- // right here:
- var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
- 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
- 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
- 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
- 'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf',
- 'isEmpty', 'chain'];
-
- // Mix in each Underscore method as a proxy to `Collection#models`.
- _.each(methods, function(method) {
- Collection.prototype[method] = function() {
- var args = slice.call(arguments);
- args.unshift(this.models);
- return _[method].apply(_, args);
- };
- });
-
- // Underscore methods that take a property name as an argument.
- var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
-
- // Use attributes instead of properties.
- _.each(attributeMethods, function(method) {
- Collection.prototype[method] = function(value, context) {
- var iterator = _.isFunction(value) ? value : function(model) {
- return model.get(value);
- };
- return _[method](this.models, iterator, context);
- };
- });
-
- // Backbone.View
- // -------------
-
- // Backbone Views are almost more convention than they are actual code. A View
- // is simply a JavaScript object that represents a logical chunk of UI in the
- // DOM. This might be a single item, an entire list, a sidebar or panel, or
- // even the surrounding frame which wraps your whole app. Defining a chunk of
- // UI as a **View** allows you to define your DOM events declaratively, without
- // having to worry about render order ... and makes it easy for the view to
- // react to specific changes in the state of your models.
-
- // 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', 'events'];
-
- // 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 by taking the element out of the DOM, and removing any
- // applicable Backbone.Events listeners.
- remove: function() {
- this.$el.remove();
- this.stopListening();
- return this;
- },
-
- // 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 Backbone.$ ? element : Backbone.$(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 = _.result(this, 'events')))) return this;
- this.undelegateEvents();
- for (var key in events) {
- var method = events[key];
- if (!_.isFunction(method)) method = this[events[key]];
- if (!method) continue;
-
- var match = key.match(delegateEventSplitter);
- var eventName = match[1], selector = match[2];
- method = _.bind(method, this);
- eventName += '.delegateEvents' + this.cid;
- if (selector === '') {
- this.$el.on(eventName, method);
- } else {
- this.$el.on(eventName, selector, method);
- }
- }
- return this;
- },
-
- // 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.off('.delegateEvents' + this.cid);
- return this;
- },
-
- // Performs the initial configuration of a View with a set of options.
- // Keys with special meaning *(e.g. model, collection, id, className)* are
- // attached directly to the view. See `viewOptions` for an exhaustive
- // list.
- _configure: function(options) {
- if (this.options) options = _.extend({}, _.result(this, 'options'), options);
- _.extend(this, _.pick(options, viewOptions));
- 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 = _.extend({}, _.result(this, 'attributes'));
- if (this.id) attrs.id = _.result(this, 'id');
- if (this.className) attrs['class'] = _.result(this, 'className');
- var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
- this.setElement($el, false);
- } else {
- this.setElement(_.result(this, 'el'), false);
- }
- }
-
- });
-
- // Backbone.sync
- // -------------
-
- // 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.
- _.defaults(options || (options = {}), {
- emulateHTTP: Backbone.emulateHTTP,
- emulateJSON: Backbone.emulateJSON
- });
-
- // Default JSON-request options.
- var params = {type: type, dataType: 'json'};
-
- // Ensure that we have a URL.
- if (!options.url) {
- params.url = _.result(model, 'url') || urlError();
- }
-
- // Ensure that we have the appropriate request data.
- if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
- params.contentType = 'application/json';
- params.data = JSON.stringify(options.attrs || model.toJSON(options));
- }
-
- // For older servers, emulate JSON by encoding the request into an HTML-form.
- if (options.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 (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
- params.type = 'POST';
- if (options.emulateJSON) params.data._method = type;
- var beforeSend = options.beforeSend;
- options.beforeSend = function(xhr) {
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
- if (beforeSend) return beforeSend.apply(this, arguments);
- };
- }
-
- // Don't process data on a non-GET request.
- if (params.type !== 'GET' && !options.emulateJSON) {
- params.processData = false;
- }
-
- // If we're sending a `PATCH` request, and we're in an old Internet Explorer
- // that still has ActiveX enabled by default, override jQuery to use that
- // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
- if (params.type === 'PATCH' && window.ActiveXObject &&
- !(window.external && window.external.msActiveXFilteringEnabled)) {
- params.xhr = function() {
- return new ActiveXObject("Microsoft.XMLHTTP");
- };
- }
-
- // Make the request, allowing the user to override any Ajax options.
- var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
- model.trigger('request', model, xhr, options);
- return xhr;
- };
-
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
- var methodMap = {
- 'create': 'POST',
- 'update': 'PUT',
- 'patch': 'PATCH',
- 'delete': 'DELETE',
- 'read': 'GET'
- };
-
- // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
- // Override this if you'd like to use a different library.
- Backbone.ajax = function() {
- return Backbone.$.ajax.apply(Backbone.$, 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 optionalParam = /\((.*?)\)/g;
- 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) {
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
- if (_.isFunction(name)) {
- callback = name;
- name = '';
- }
- if (!callback) callback = this[name];
- var router = this;
- Backbone.history.route(route, function(fragment) {
- var args = router._extractParameters(route, fragment);
- callback && callback.apply(router, args);
- router.trigger.apply(router, ['route:' + name].concat(args));
- router.trigger('route', name, args);
- Backbone.history.trigger('route', router, name, args);
- });
- return this;
- },
-
- // Simple proxy to `Backbone.history` to save a fragment into the history.
- navigate: function(fragment, options) {
- Backbone.history.navigate(fragment, options);
- return this;
- },
-
- // 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;
- this.routes = _.result(this, 'routes');
- var route, routes = _.keys(this.routes);
- while ((route = routes.pop()) != null) {
- this.route(route, this.routes[route]);
- }
- },
-
- // Convert a route string into a regular expression, suitable for matching
- // against the current location hash.
- _routeToRegExp: function(route) {
- route = route.replace(escapeRegExp, '\\$&')
- .replace(optionalParam, '(?:$1)?')
- .replace(namedParam, function(match, optional){
- return optional ? match : '([^\/]+)';
- })
- .replace(splatParam, '(.*?)');
- return new RegExp('^' + route + '$');
- },
-
- // Given a route, and a URL fragment that it matches, return the array of
- // extracted decoded parameters. Empty or unmatched parameters will be
- // treated as `null` to normalize cross-browser behavior.
- _extractParameters: function(route, fragment) {
- var params = route.exec(fragment).slice(1);
- return _.map(params, function(param) {
- return param ? decodeURIComponent(param) : null;
- });
- }
-
- });
-
- // Backbone.History
- // ----------------
-
- // Handles cross-browser history management, based on either
- // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
- // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
- // and URL fragments. If the browser supports neither (old IE, natch),
- // falls back to polling.
- var History = Backbone.History = function() {
- this.handlers = [];
- _.bindAll(this, 'checkUrl');
-
- // Ensure that `History` can be used outside of the browser.
- if (typeof window !== 'undefined') {
- this.location = window.location;
- this.history = window.history;
- }
- };
-
- // Cached regex for stripping a leading hash/slash and trailing space.
- var routeStripper = /^[#\/]|\s+$/g;
-
- // Cached regex for stripping leading and trailing slashes.
- var rootStripper = /^\/+|\/+$/g;
-
- // Cached regex for detecting MSIE.
- var isExplorer = /msie [\w.]+/;
-
- // Cached regex for removing a trailing slash.
- var trailingSlash = /\/$/;
-
- // 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(window) {
- var match = (window || this).location.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 || !this._wantsHashChange || forcePushState) {
- fragment = this.location.pathname;
- var root = this.root.replace(trailingSlash, '');
- if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
- } else {
- fragment = this.getHash();
- }
- }
- 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.root = this.options.root;
- this._wantsHashChange = this.options.hashChange !== false;
- this._wantsPushState = !!this.options.pushState;
- this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
- var fragment = this.getFragment();
- var docMode = document.documentMode;
- var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
-
- // Normalize root to always include a leading and trailing slash.
- this.root = ('/' + this.root + '/').replace(rootStripper, '/');
-
- if (oldIE && this._wantsHashChange) {
- this.iframe = Backbone.$('').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) {
- Backbone.$(window).on('popstate', this.checkUrl);
- } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
- Backbone.$(window).on('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 = this.location;
- var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.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);
- this.location.replace(this.root + this.location.search + '#' + 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, '');
- this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
- }
-
- 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() {
- Backbone.$(window).off('popstate', this.checkUrl).off('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};
- fragment = this.getFragment(fragment || '');
- if (this.fragment === fragment) return;
- this.fragment = fragment;
- var url = this.root + fragment;
-
- // If pushState is available, we use it to set the fragment as a real URL.
- if (this._hasPushState) {
- this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
-
- // If hash changes haven't been explicitly disabled, update the hash
- // fragment to store history.
- } else if (this._wantsHashChange) {
- this._updateHash(this.location, fragment, options.replace);
- if (this.iframe && (fragment !== 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, fragment, options.replace);
- }
-
- // If you've told us that you explicitly don't want fallback hashchange-
- // based history, then `navigate` becomes a page refresh.
- } else {
- return this.location.assign(url);
- }
- 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) {
- var href = location.href.replace(/(javascript:|#).*$/, '');
- location.replace(href + '#' + fragment);
- } else {
- // Some browsers require that `hash` contains a leading #.
- location.hash = '#' + fragment;
- }
- }
-
- });
-
- // Create the default Backbone.history.
- Backbone.history = new History;
-
- // Helpers
- // -------
-
- // 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 extend = function(protoProps, staticProps) {
- var parent = this;
- 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 && _.has(protoProps, 'constructor')) {
- child = protoProps.constructor;
- } else {
- child = function(){ return parent.apply(this, arguments); };
- }
-
- // Add static properties to the constructor function, if supplied.
- _.extend(child, parent, staticProps);
-
- // Set the prototype chain to inherit from `parent`, without calling
- // `parent`'s constructor function.
- var Surrogate = function(){ this.constructor = child; };
- Surrogate.prototype = parent.prototype;
- child.prototype = new Surrogate;
-
- // Add prototype properties (instance properties) to the subclass,
- // if supplied.
- if (protoProps) _.extend(child.prototype, protoProps);
-
- // Set a convenience property in case the parent's prototype is needed
- // later.
- child.__super__ = parent.prototype;
-
- return child;
- };
-
- // Set up inheritance for the model, collection, router, view and history.
- Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
-
- // 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');
- };
-
- // Wrap an optional error callback with a fallback error event.
- var wrapError = function (model, options) {
- var error = options.error;
- options.error = function(resp) {
- if (error) error(model, resp, options);
- model.trigger('error', model, resp, options);
- };
- };
-
-}).call(this);
diff --git a/Libraries/backbone.localStorage.js b/Libraries/backbone.localStorage.js
deleted file mode 100644
index 7378fd719..000000000
--- a/Libraries/backbone.localStorage.js
+++ /dev/null
@@ -1,192 +0,0 @@
-/**
- * Backbone localStorage Adapter
- * Version 1.1.0
- *
- * https://github.com/jeromegn/Backbone.localStorage
- */
-(function (root, factory) {
- if (typeof define === "function" && define.amd) {
- // AMD. Register as an anonymous module.
- define(["underscore","backbone"], function(_, Backbone) {
- // Use global variables if the locals is undefined.
- return factory(_ || root._, Backbone || root.Backbone);
- });
- } else {
- // RequireJS isn't being used. Assume underscore and backbone is loaded in
+
+Then, at the bottom of your page, after the closing *
* element, put the
+following inline Javascript code:
+
+::
+
+ require(['converse'], function (converse) {
+ converse.initialize({
+ auto_list_rooms: false,
+ auto_subscribe: false,
+ bosh_service_url: 'https://bind.opkode.im', // Please use this connection manager only for testing purposes
+ hide_muc_server: false,
+ i18n: locales.en, // Refer to ./locale/locales.js to see which locales are supported
+ prebind: false,
+ show_controlbox_by_default: true,
+ xhr_user_search: false
+ });
+ });
+
+The *index.html* file inside the Converse.js folder serves as a nice usable
+example of this.
+
+These minified files provide the same demo-like functionality as is available
+on the `conversejs.org`_ website. Useful for testing or demoing, but not very
+practical.
+
+You'll most likely want to implement some kind of single-signon solution for
+your website, where users authenticate once in your website and then stay
+logged into their XMPP session upon page reload.
+
+For more info on this, read: `Pre-binding and Single Session Support`_.
+
+You might also want to have more fine-grained control of what gets included in
+the minified Javascript file. Read `Configuration`_ and `Minification`_ for more info on how to do
+that.
+
+
============
Introduction
============
@@ -36,6 +90,7 @@ code.
The `What you will need`_ section has more information on all these
requirements.
+
==================
What you will need
==================
@@ -151,85 +206,109 @@ In the callback function, you call *converse.onConnected* together with the
connection object.
-=========================================
-Quickstart (to get a demo up and running)
-=========================================
-
-When you download a specific release of *Converse.js* there will be two minified files inside the zip file.
-
-* converse.min.js
-* converse.min.css
-
-You can include these two files inside the *
* element of your website via the *script* and *link*
-tags:
-
-::
-
-
-
-
-Then, at the bottom of your page, after the closing ** element, put the
-following inline Javascript code:
-
-::
-
- require(['converse'], function (converse) {
- converse.initialize({
- auto_list_rooms: false,
- auto_subscribe: false,
- bosh_service_url: 'https://bind.opkode.im', // Please use this connection manager only for testing purposes
- hide_muc_server: false,
- i18n: locales.en, // Refer to ./locale/locales.js to see which locales are supported
- prebind: false,
- show_controlbox_by_default: true,
- xhr_user_search: false
- });
- });
-
-The *index.html* file inside the Converse.js folder serves as a nice usable
-example of this.
-
-These minified files provide the same demo-like functionality as is available
-on the `conversejs.org`_ website. Useful for testing or demoing, but not very
-practical.
-
-You'll most likely want to implement some kind of single-signon solution for
-your website, where users authenticate once in your website and then stay
-logged into their XMPP session upon page reload.
-
-For more info on this, read: `Pre-binding and Single Session Support`_.
-
-You might also want to have more fine-grained control of what gets included in
-the minified Javascript file. Read `Configuration`_ and `Minification`_ for more info on how to do
-that.
-
===========
Development
===========
-With AMD and require.js (recommended)
--------------------------------------
+Install Node.js and development dependencies
+============================================
-Converse.js uses `require.js`_ to track and load dependencies.
+We use development tools (`Grunt `_ and `Bower `_)
+which depend on Node.js and npm (the Node package manager).
+
+If you don't have Node.js installed, you can download and install the latest
+version `here `_.
+
+Once you have Node.js installed, run the following command in the Converse.js
+directory:
+
+::
+
+ npm install
+
+Install 3rd party dependencies
+==============================
+
+Now that we have Grunt and Bower, you can install and configure Converse's
+3rd party dependencies with the following command:
+
+::
+
+ grunt fetch
+
+With AMD and require.js (recommended)
+=====================================
+
+Converse.js uses `require.js`_ to asynchronously load dependencies.
If you want to develop or customize converse.js, you'll want to load the
non-minified javascript files.
-Add the following two lines to the ** section of your webpage.
+Add the following two lines to the ** section of your webpage:
::
-
+
+require.js will then let the main.js file be parsed (because of the *data-main*
+attribute on the *script* tag), which will in turn cause converse.js to be
+parsed.
Without AMD and require.js
---------------------------
+==========================
Converse.js can also be used without require.js. If you for some reason prefer
-to use it this way, please refer to *non_amd.html* for an example of how and in
-what order all the Javascript files that converse.js depends on need to be
-loaded.
+to use it this way, please refer to
+`non_amd.html `_
+for an example of how and in what order all the Javascript files that converse.js
+depends on need to be loaded.
+
+
+Before submitting a pull request
+================================
+
+Add tests for your bugfix or feature
+------------------------------------
+
+Add a test for any bug fixed or feature added. We use Jasmine
+for testing.
+
+Take a look at ``tests.html`` and ``spec/MainSpec.js`` to see how
+the tests are implemented.
+
+If you are unsure how to write tests, please
+`contact me `_ and I'll be happy to help.
+
+Check that the tests pass
+-------------------------
+
+Check that the Jasmine tests complete sucessfully. Open
+`tests.html `_
+in your browser, and the tests will run automatically.
+
+On the command line you can run:
+
+::
+
+ grunt test
+
+Check your code for errors or bad habits by running JSHint
+----------------------------------------------------------
+
+`JSHint `_ will do a static analysis of your code and hightlight potential errors
+and/or bad habits.
+
+::
+
+ grunt jshint
+
+
+You can run both the tests and jshint in one go by calling:
+
+::
+
+ grunt check
=============
@@ -290,7 +369,7 @@ bosh_service_url
Connections to an XMPP server depend on a BOSH connection manager which acts as
a middle man between HTTP and XMPP.
-See `here`_ for more information.
+See `here `_ for more information.
fullname
--------
@@ -505,7 +584,6 @@ those hoops you had to jump through.
.. _`conversejs.org`: http://conversejs.org
.. _`require.js`: http://requirejs.org
.. _`read more about require.js's optimizer here`: http://requirejs.org/docs/optimization.html
-.. _`here`: http://metajack.im/2008/09/08/which-bosh-server-do-you-need/l
.. _`HTTP`: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
.. _`XMPP`: https://en.wikipedia.org/wiki/Xmpp
.. _`Converse.js homepage`: http://conversejs.org
diff --git a/docs/html/genindex.html b/docs/html/genindex.html
index 3d0567c12..29767b7b8 100644
--- a/docs/html/genindex.html
+++ b/docs/html/genindex.html
@@ -9,7 +9,7 @@
- Index — Converse.js 0.3 documentation
+ Index — Converse.js 0.5 documentation
@@ -17,7 +17,7 @@
-
+
Even though you can connect to public XMPP servers on the conversejs.org
-website, Converse.js is not really meant to be a “Software-as-a-service” (SaaS)
-webchat.
-
Instead, its goal is to provide the means for website owners to add a tightly
-integrated instant messaging service to their own sites.
-
As a website owner, you are expected to host Converse.js yourself, and to do some legwork to
-properly configure and integrate it into your site.
-
The benefit in doing this, is that your users have a much more streamlined and integrated
-webchat experience and that you have control over the data. The latter being a
-requirement for many sites dealing with sensitive information.
-
You’ll need to set up your own XMPP server and in order to have
-Session Support (i.e. single-signon functionality whereby users are authenticated once and stay
-logged in to XMPP upon page reload) you will also have to add some server-side
-code.
-
The What you will need section has more information on all these
-requirements.
Converse.js implements XMPP as its messaging protocol, and therefore needs
-to connect to an XMPP/Jabber server (Jabber is really just a synonym for XMPP).
-
You can connect to public XMPP servers like jabber.org but if you want to
-have Session Support you’ll have to set up your own XMPP server.
-
You can find a list of public XMPP servers/providers on xmpp.net and a list of
-servers that you can set up yourself on xmpp.org.
Your website and Converse.js use HTTP as protocol to communicate with
-the webserver. HTTP connections are stateless and usually shortlived.
-
XMPP on the other hand, is the protocol that enables instant messaging, and
-its connections are stateful and usually longer.
-
To enable a web application like Converse.js to communicate with an XMPP
-server, we need a proxy in the middle that can act as a bridge between the two
-protocols.
-
This is the job of a connection manager. A connection manager can be either a
-standalone application or part of an XMPP server. ejabberd for example,
-includes a connection manager (but you have to enable it).
-
The demo on the Converse.js homepage uses a a connection manager located at https://bind.opkode.im.
-This connection manager is for testing purposes only, please don’t use it in
-production.
The domain of the Converse.js demo is conversejs.org, but the domain of the connection manager is opkode.im.
-HTTP requests are made by Converse.js to the connection manager via XmlHttpRequests (XHR).
-Until recently, it was not possible to make such requests to a different domain
-than the one currently being served (to prevent XSS attacks).
-
Luckily there is now a standard called CORS (Cross-origin resource sharing), which enables exactly that.
-Modern browsers support CORS, but there are problems with Internet Explorer <
-10.
-
IE 8 and 9 partially support CORS via a proprietary implementation called
-XDomainRequest. There is a Strophe.js plugin which you can use to enable
-support for XDomainRequest when it is present.
-
In IE < 8, there is no support for CORS.
-
If you need to support these browsers, you can add a front-end proxy in
-Apache/Nginx which serves the connection manager under the same domain as your
-website. This will remove the need for any cross-domain XHR support.
It’s possible to enable single-site login, whereby users already
-authenticated in your website will also automatically be logged in on the chat server,
-but this will require custom code on your server.
If you want to enable single session support, make sure to pass prebind: true
-when you call converse.initialize (see ./index.html).
-
-
When you authenticate to the XMPP server on your backend, you’ll receive two
-tokens, RID (request ID) and SID (session ID).
-
These tokens then need to be passed back to the javascript running in your
-browser, where you will need them attach to the existing session.
-
You can embed the RID and SID tokens in your HTML markup or you can do an
-XMLHttpRequest call to you server and ask it to return them for you.
-
Below is one example of how this could work. An Ajax call is made to the
-relative URL /prebind and it expects to receive JSON data back.
-
$.getJSON('/prebind', function (data) {
- var connection = new Strophe.Connection(converse.bosh_service_url);
- connection.attach(data.jid, data.sid, data.rid, function (status) {
- if ((status === Strophe.Status.ATTACHED) || (status === Strophe.Status.CONNECTED)) {
- converse.onConnected(connection)
- }
- });
- }
-);
-
-
Here’s what’s happening:
-
The JSON data contains the user’s JID (jabber ID), RID and SID. The URL to the
-BOSH connection manager is already set as a configuration setting on the
-converse object (see ./main.js), so we can reuse it from there.
-
A new Strophe.Connection object is instantiated and then attach is called with
-the user’s JID, the necessary tokens and a callback function.
-
In the callback function, you call converse.onConnected together with the
-connection object.
When you download a specific release of Converse.js there will be two minified files inside the zip file.
converse.min.js
@@ -248,22 +155,186 @@ logged into their XMPP session upon page reload.
the minified Javascript file. Read Configuration and Minification for more info on how to do
that.
Even though you can connect to public XMPP servers on the conversejs.org
+website, Converse.js is not really meant to be a “Software-as-a-service” (SaaS)
+webchat.
+
Instead, its goal is to provide the means for website owners to add a tightly
+integrated instant messaging service to their own sites.
+
As a website owner, you are expected to host Converse.js yourself, and to do some legwork to
+properly configure and integrate it into your site.
+
The benefit in doing this, is that your users have a much more streamlined and integrated
+webchat experience and that you have control over the data. The latter being a
+requirement for many sites dealing with sensitive information.
+
You’ll need to set up your own XMPP server and in order to have
+Session Support (i.e. single-signon functionality whereby users are authenticated once and stay
+logged in to XMPP upon page reload) you will also have to add some server-side
+code.
+
The What you will need section has more information on all these
+requirements.
Converse.js implements XMPP as its messaging protocol, and therefore needs
+to connect to an XMPP/Jabber server (Jabber is really just a synonym for XMPP).
+
You can connect to public XMPP servers like jabber.org but if you want to
+have Session Support you’ll have to set up your own XMPP server.
+
You can find a list of public XMPP servers/providers on xmpp.net and a list of
+servers that you can set up yourself on xmpp.org.
Your website and Converse.js use HTTP as protocol to communicate with
+the webserver. HTTP connections are stateless and usually shortlived.
+
XMPP on the other hand, is the protocol that enables instant messaging, and
+its connections are stateful and usually longer.
+
To enable a web application like Converse.js to communicate with an XMPP
+server, we need a proxy in the middle that can act as a bridge between the two
+protocols.
+
This is the job of a connection manager. A connection manager can be either a
+standalone application or part of an XMPP server. ejabberd for example,
+includes a connection manager (but you have to enable it).
+
The demo on the Converse.js homepage uses a a connection manager located at https://bind.opkode.im.
+This connection manager is for testing purposes only, please don’t use it in
+production.
The domain of the Converse.js demo is conversejs.org, but the domain of the connection manager is opkode.im.
+HTTP requests are made by Converse.js to the connection manager via XmlHttpRequests (XHR).
+Until recently, it was not possible to make such requests to a different domain
+than the one currently being served (to prevent XSS attacks).
+
Luckily there is now a standard called CORS (Cross-origin resource sharing), which enables exactly that.
+Modern browsers support CORS, but there are problems with Internet Explorer <
+10.
+
IE 8 and 9 partially support CORS via a proprietary implementation called
+XDomainRequest. There is a Strophe.js plugin which you can use to enable
+support for XDomainRequest when it is present.
+
In IE < 8, there is no support for CORS.
+
If you need to support these browsers, you can add a front-end proxy in
+Apache/Nginx which serves the connection manager under the same domain as your
+website. This will remove the need for any cross-domain XHR support.
It’s possible to enable single-site login, whereby users already
+authenticated in your website will also automatically be logged in on the chat server,
+but this will require custom code on your server.
If you want to enable single session support, make sure to pass prebind: true
+when you call converse.initialize (see ./index.html).
+
+
When you authenticate to the XMPP server on your backend, you’ll receive two
+tokens, RID (request ID) and SID (session ID).
+
These tokens then need to be passed back to the javascript running in your
+browser, where you will need them attach to the existing session.
+
You can embed the RID and SID tokens in your HTML markup or you can do an
+XMLHttpRequest call to you server and ask it to return them for you.
+
Below is one example of how this could work. An Ajax call is made to the
+relative URL /prebind and it expects to receive JSON data back.
+
$.getJSON('/prebind', function (data) {
+ var connection = new Strophe.Connection(converse.bosh_service_url);
+ connection.attach(data.jid, data.sid, data.rid, function (status) {
+ if ((status === Strophe.Status.ATTACHED) || (status === Strophe.Status.CONNECTED)) {
+ converse.onConnected(connection)
+ }
+ });
+ }
+);
+
+
Here’s what’s happening:
+
The JSON data contains the user’s JID (jabber ID), RID and SID. The URL to the
+BOSH connection manager is already set as a configuration setting on the
+converse object (see ./main.js), so we can reuse it from there.
+
A new Strophe.Connection object is instantiated and then attach is called with
+the user’s JID, the necessary tokens and a callback function.
+
In the callback function, you call converse.onConnected together with the
+connection object.
require.js will then let the main.js file be parsed (because of the data-main
+attribute on the script tag), which will in turn cause converse.js to be
+parsed.
Converse.js can also be used without require.js. If you for some reason prefer
-to use it this way, please refer to non_amd.html for an example of how and in
-what order all the Javascript files that converse.js depends on need to be
-loaded.
+to use it this way, please refer to
+non_amd.html
+for an example of how and in what order all the Javascript files that converse.js
+depends on need to be loaded.
+
The included minified JS and CSS files can be used for demoing or testing, but
you’ll want to configure Converse.js to suit your needs before you deploy it
on your website.
@@ -277,14 +348,14 @@ all the available configuration settings.
JS file so that it will include the new settings. Please refer to the
Minification section for more info on how to do this.
Hide the server input field of the form inside the Room panel of the
controlbox. Useful if you want to restrict users to a specific XMPP server of
your choosing.
Use this option when you want to attach to an existing XMPP connection that was
already authenticated (usually on the backend before page load).
@@ -338,7 +409,7 @@ have to write a Javascript snippet to attach to the set up connection:
RID (Request ID), which you use when you attach to the connection.
We use require.js to keep track of Converse.js and its dependencies and to
to bundle them together in a single minified file fit for deployment to a
production site.
diff --git a/docs/html/searchindex.js b/docs/html/searchindex.js
index cc9191133..7e48a40f9 100644
--- a/docs/html/searchindex.js
+++ b/docs/html/searchindex.js
@@ -1 +1 @@
-Search.setIndex({objects:{},terms:{all:0,code:0,partial:0,queri:0,webchat:0,follow:0,middl:0,depend:0,sensit:0,sorri:0,specif:0,those:0,under:0,string:0,fals:0,mechan:0,jack:0,veri:0,list:0,pleas:0,prevent:0,past:0,second:0,pass:0,download:0,further:0,fullnam:0,click:0,even:0,index:0,what:0,hide:0,section:0,current:0,"new":0,net:0,"public":0,widget:0,gener:0,here:0,bodi:0,valu:0,box:0,convert:0,convers:0,mysit:0,reason:0,implement:0,via:0,extra:0,solut:0,prefer:0,ask:0,href:0,org:0,auto_list_room:0,instal:0,from:0,zip:0,commun:0,doubl:0,two:0,websit:0,stylesheet:0,call:0,recommend:0,type:0,until:0,tightli:0,more:0,yahoo:0,site:0,must:0,room:0,work:0,xhr:0,can:0,lc_messag:0,purpos:0,root:0,fetch:0,control:0,quickstart:0,share:0,templat:0,tag:0,proprietari:0,explor:0,onlin:0,occup:0,end:0,goal:0,snippet:0,how:0,sid:0,instead:0,css:0,updat:0,npm:0,regener:0,product:0,resourc:0,after:0,usabl:0,befor:0,callback:0,underscor:0,data:0,demonstr:0,man:0,practic:0,bind:0,show_controlbox_by_default:0,element:0,inform:0,order:0,chatbox:0,xmpp:0,over:0,through:0,streamlin:0,write:0,jid:0,directli:0,fit:0,pend:0,hidden:0,therefor:0,might:0,them:0,anim:0,"return":0,thei:0,initi:0,front:0,now:0,introduct:0,name:0,edit:0,authent:0,token:0,ejabberd:0,each:0,side:0,mean:0,domain:0,individu:0,realli:0,legwork:0,connect:0,happen:0,extract:0,special:0,variabl:0,shown:0,space:0,jabber:0,content:0,rel:0,internet:0,plural:0,factori:0,po2json:0,proxi:0,insid:0,standard:0,standalon:0,ajax:0,put:0,succesfulli:0,afterward:0,could:0,keep:0,yui:0,first:0,origin:0,softwar:0,render:0,onc:0,hoop:0,lastnam:0,number:0,yourself:0,restrict:0,alreadi:0,done:0,owner:0,open:0,differ:0,script:0,top:0,messag:0,attach:0,attack:0,jed:0,luckili:0,option:0,tool:0,specifi:0,compressor:0,part:0,exactli:0,than:0,serv:0,jump:0,kind:0,bloat:0,provid:0,remov:0,jqueri:0,bridg:0,browser:0,pre:0,saa:0,modern:0,ani:0,packag:0,have:0,tabl:0,need:0,moffitt:0,django:0,bosh_service_url:0,prebind:0,min:0,latter:0,note:0,also:0,contact:0,build:0,which:0,singl:0,sure:0,though:0,track:0,object:0,most:0,deploi:0,homepag:0,"class":0,don:0,url:0,request:0,face:0,runtim:0,xdomainrequest:0,show:0,german:0,text:0,session:0,fine:0,find:0,onli:0,locat:0,just:0,configur:0,apach:0,should:0,folder:0,local:0,meant:0,get:0,opkod:0,cannot:0,requir:0,enabl:0,emb:0,method:0,reload:0,integr:0,contain:0,where:0,set:0,stroph:0,see:0,close:0,page:0,statu:0,state:0,reus:0,between:0,experi:0,hide_muc_serv:0,attribut:0,kei:0,screen:0,javascript:0,job:0,bosh:0,roster:0,cor:0,instant:0,shortliv:0,conversej:0,etc:0,grain:0,mani:0,login:0,com:0,load:0,instanti:0,pot:0,backend:0,creat:0,rebuild:0,json:0,much:0,besid:0,subscrib:0,msgmerg:0,great:0,minifi:0,togeth:0,i18n:0,present:0,multi:0,main:0,servic:0,plugin:0,defin:0,file:0,helper:0,demo:0,auto_subscrib:0,non:0,rid:0,develop:0,minim:0,receiv:0,media:0,make:0,minif:0,cross:0,same:0,webpag:0,html:0,chatroom:0,signon:0,http:0,webserv:0,optim:0,upon:0,hand:0,"50kb":0,user:0,xhr_user_search:0,recent:0,stateless:0,markup:0,without:0,exampl:0,command:0,wherebi:0,thi:0,choos:0,usual:0,plural_form:0,protocol:0,firstnam:0,languag:0,web:0,xmlhttprequest:0,had:0,add:0,other:0,non_amd:0,input:0,yuicompressor:0,match:0,take:0,applic:0,format:0,read:0,nginx:0,traffic:0,xss:0,like:0,success:0,server:0,benefit:0,necessari:0,either:0,manag:0,deal:0,nplural:0,some:0,back:0,librari:0,bottom:0,deploy:0,overcom:0,refer:0,run:0,host:0,panel:0,src:0,about:0,controlbox:0,unfortun:0,act:0,own:0,encod:0,automat:0,wrap:0,your:0,log:0,wai:0,transfer:0,support:0,custom:0,avail:0,includ:0,lot:0,suit:0,"var":0,"function":0,head:0,properli:0,form:0,blogpost:0,bundl:0,link:0,translat:0,synonym:0,line:0,inlin:0,"true":0,congratul:0,longer:0,info:0,made:0,locale_data:0,possibl:0,"default":0,below:0,toggl:0,otherwis:0,problem:0,expect:0,featur:0,onconnect:0,exist:0,chat:0,want:0,when:0,detail:0,gettext:0,field:0,valid:0,rememb:0,test:0,you:0,nice:0,node:0,intend:0,releas:0,stai:0,lang:0,requirej:0,getjson:0,time:0},objtypes:{},titles:["Introduction"],objnames:{},filenames:["index"]})
\ No newline at end of file
+Search.setIndex({objects:{},terms:{all:0,code:0,partial:0,queri:0,webchat:0,follow:0,middl:0,depend:0,sensit:0,sorri:0,specif:0,those:0,under:0,spec:0,string:0,fals:0,mechan:0,jack:0,veri:0,list:0,pleas:0,prevent:0,past:0,second:0,pass:0,download:0,further:0,fullnam:0,click:0,even:0,index:0,what:0,hide:0,section:0,current:0,version:0,"new":0,net:0,"public":0,widget:0,gener:0,here:0,bodi:0,let:0,valu:0,box:0,convert:0,convers:0,mysit:0,ajax:0,implement:0,via:0,extra:0,solut:0,prefer:0,put:0,href:0,org:0,auto_list_room:0,instal:0,from:0,zip:0,commun:0,doubl:0,two:0,websit:0,stylesheet:0,call:0,recommend:0,type:0,until:0,tightli:0,more:0,yahoo:0,must:0,room:0,work:0,xhr:0,can:0,lc_messag:0,purpos:0,root:0,fetch:0,control:0,quickstart:0,share:0,templat:0,tag:0,proprietari:0,explor:0,onlin:0,occup:0,end:0,goal:0,snippet:0,how:0,sid:0,roster:0,instead:0,css:0,updat:0,npm:0,regener:0,product:0,resourc:0,after:0,usabl:0,befor:0,underscor:0,data:0,demonstr:0,man:0,practic:0,bind:0,show_controlbox_by_default:0,element:0,caus:0,callback:0,parti:0,order:0,help:0,chatbox:0,xmpp:0,over:0,becaus:0,through:0,streamlin:0,write:0,jid:0,directli:0,fit:0,fix:0,"static":0,pend:0,hidden:0,therefor:0,might:0,them:0,anim:0,"return":0,thei:0,initi:0,front:0,now:0,introduct:0,name:0,edit:0,authent:0,token:0,ejabberd:0,each:0,side:0,mean:0,domain:0,individu:0,realli:0,legwork:0,connect:0,happen:0,extract:0,special:0,variabl:0,shown:0,"3rd":0,space:0,open:0,content:0,rel:0,internet:0,plural:0,factori:0,po2json:0,proxi:0,insid:0,standard:0,standalon:0,reason:0,ask:0,succesfulli:0,afterward:0,could:0,keep:0,yui:0,turn:0,first:0,origin:0,softwar:0,render:0,onc:0,hoop:0,lastnam:0,number:0,yourself:0,restrict:0,alreadi:0,done:0,owner:0,custom:0,jabber:0,differ:0,script:0,top:0,messag:0,attach:0,attack:0,jed:0,luckili:0,option:0,tool:0,specifi:0,compressor:0,part:0,pars:0,grunt:0,than:0,serv:0,jump:0,kind:0,bloat:0,provid:0,remov:0,exampl:0,bridg:0,browser:0,pre:0,"function":0,saa:0,modern:0,ani:0,packag:0,have:0,tabl:0,need:0,moffitt:0,django:0,bosh_service_url:0,prebind:0,min:0,latter:0,note:0,also:0,contact:0,take:0,which:0,singl:0,sure:0,though:0,unsur:0,object:0,most:0,deploi:0,homepag:0,"class":0,don:0,url:0,request:0,face:0,runtim:0,bower:0,latest:0,xdomainrequest:0,show:0,german:0,text:0,session:0,fine:0,find:0,onli:0,exactli:0,locat:0,just:0,configur:0,apach:0,should:0,folder:0,local:0,meant:0,get:0,opkod:0,cannot:0,requir:0,enabl:0,emb:0,mainspec:0,method:0,reload:0,bad:0,integr:0,contain:0,where:0,set:0,habit:0,stroph:0,see:0,close:0,page:0,statu:0,state:0,reus:0,between:0,experi:0,jasmin:0,hide_muc_serv:0,attribut:0,kei:0,screen:0,javascript:0,job:0,bosh:0,both:0,cor:0,instant:0,shortliv:0,conversej:0,etc:0,grain:0,mani:0,login:0,com:0,load:0,instanti:0,pot:0,non:0,backend:0,sucessfulli:0,rebuild:0,compon:0,json:0,much:0,besid:0,subscrib:0,msgmerg:0,great:0,minifi:0,togeth:0,i18n:0,present:0,multi:0,main:0,look:0,servic:0,plugin:0,defin:0,error:0,hightlight:0,chat:0,helper:0,demo:0,auto_subscrib:0,site:0,inform:0,rid:0,develop:0,minim:0,receiv:0,media:0,make:0,minif:0,cross:0,same:0,webpag:0,html:0,chatroom:0,complet:0,signon:0,http:0,webserv:0,optim:0,upon:0,hand:0,"50kb":0,user:0,xhr_user_search:0,recent:0,stateless:0,markup:0,without:0,command:0,wherebi:0,thi:0,choos:0,usual:0,plural_form:0,protocol:0,firstnam:0,jshint:0,languag:0,web:0,xmlhttprequest:0,had:0,onconnect:0,add:0,other:0,non_amd:0,input:0,yuicompressor:0,match:0,build:0,applic:0,format:0,read:0,amd:0,nginx:0,traffic:0,xss:0,like:0,success:0,server:0,benefit:0,necessari:0,either:0,manag:0,deal:0,nplural:0,some:0,back:0,librari:0,bottom:0,deploy:0,track:0,overcom:0,refer:0,run:0,host:0,panel:0,src:0,about:0,controlbox:0,unfortun:0,act:0,own:0,encod:0,automat:0,wrap:0,your:0,log:0,wai:0,transfer:0,support:0,submit:0,happi:0,avail:0,includ:0,lot:0,suit:0,"var":0,analysi:0,head:0,properli:0,form:0,blogpost:0,bundl:0,link:0,translat:0,synonym:0,line:0,inlin:0,"true":0,bug:0,congratul:0,requirej:0,info:0,pull:0,made:0,locale_data:0,possibl:0,"default":0,bugfix:0,asynchron:0,below:0,toggl:0,otherwis:0,problem:0,expect:0,featur:0,creat:0,exist:0,file:0,check:0,want:0,when:0,detail:0,gettext:0,field:0,valid:0,rememb:0,test:0,you:0,nice:0,node:0,intend:0,releas:0,stai:0,lang:0,longer:0,directori:0,getjson:0,potenti:0,time:0},objtypes:{},titles:["Quickstart (to get a demo up and running)"],objnames:{},filenames:["index"]})
\ No newline at end of file
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 02061f9d4..fe3c6f53e 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -48,9 +48,9 @@ copyright = u'2013, JC Brand'
# built documents.
#
# The short X.Y version.
-version = '0.3'
+version = '0.5'
# The full version, including alpha/beta/rc tags.
-release = '0.3'
+release = '0.5'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 92913cf05..1fd9b1c81 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -10,6 +10,60 @@
:depth: 3
:local:
+
+=========================================
+Quickstart (to get a demo up and running)
+=========================================
+
+When you download a specific release of *Converse.js* there will be two minified files inside the zip file.
+
+* converse.min.js
+* converse.min.css
+
+You can include these two files inside the ** element of your website via the *script* and *link*
+tags:
+
+::
+
+
+
+
+Then, at the bottom of your page, after the closing ** element, put the
+following inline Javascript code:
+
+::
+
+ require(['converse'], function (converse) {
+ converse.initialize({
+ auto_list_rooms: false,
+ auto_subscribe: false,
+ bosh_service_url: 'https://bind.opkode.im', // Please use this connection manager only for testing purposes
+ hide_muc_server: false,
+ i18n: locales.en, // Refer to ./locale/locales.js to see which locales are supported
+ prebind: false,
+ show_controlbox_by_default: true,
+ xhr_user_search: false
+ });
+ });
+
+The *index.html* file inside the Converse.js folder serves as a nice usable
+example of this.
+
+These minified files provide the same demo-like functionality as is available
+on the `conversejs.org`_ website. Useful for testing or demoing, but not very
+practical.
+
+You'll most likely want to implement some kind of single-signon solution for
+your website, where users authenticate once in your website and then stay
+logged into their XMPP session upon page reload.
+
+For more info on this, read: `Pre-binding and Single Session Support`_.
+
+You might also want to have more fine-grained control of what gets included in
+the minified Javascript file. Read `Configuration`_ and `Minification`_ for more info on how to do
+that.
+
+
============
Introduction
============
@@ -36,6 +90,7 @@ code.
The `What you will need`_ section has more information on all these
requirements.
+
==================
What you will need
==================
@@ -151,85 +206,109 @@ In the callback function, you call *converse.onConnected* together with the
connection object.
-=========================================
-Quickstart (to get a demo up and running)
-=========================================
-
-When you download a specific release of *Converse.js* there will be two minified files inside the zip file.
-
-* converse.min.js
-* converse.min.css
-
-You can include these two files inside the ** element of your website via the *script* and *link*
-tags:
-
-::
-
-
-
-
-Then, at the bottom of your page, after the closing ** element, put the
-following inline Javascript code:
-
-::
-
- require(['converse'], function (converse) {
- converse.initialize({
- auto_list_rooms: false,
- auto_subscribe: false,
- bosh_service_url: 'https://bind.opkode.im', // Please use this connection manager only for testing purposes
- hide_muc_server: false,
- i18n: locales.en, // Refer to ./locale/locales.js to see which locales are supported
- prebind: false,
- show_controlbox_by_default: true,
- xhr_user_search: false
- });
- });
-
-The *index.html* file inside the Converse.js folder serves as a nice usable
-example of this.
-
-These minified files provide the same demo-like functionality as is available
-on the `conversejs.org`_ website. Useful for testing or demoing, but not very
-practical.
-
-You'll most likely want to implement some kind of single-signon solution for
-your website, where users authenticate once in your website and then stay
-logged into their XMPP session upon page reload.
-
-For more info on this, read: `Pre-binding and Single Session Support`_.
-
-You might also want to have more fine-grained control of what gets included in
-the minified Javascript file. Read `Configuration`_ and `Minification`_ for more info on how to do
-that.
-
===========
Development
===========
-With AMD and require.js (recommended)
--------------------------------------
+Install Node.js and development dependencies
+============================================
-Converse.js uses `require.js`_ to track and load dependencies.
+We use development tools (`Grunt `_ and `Bower `_)
+which depend on Node.js and npm (the Node package manager).
+
+If you don't have Node.js installed, you can download and install the latest
+version `here `_.
+
+Once you have Node.js installed, run the following command in the Converse.js
+directory:
+
+::
+
+ npm install
+
+Install 3rd party dependencies
+==============================
+
+Now that we have Grunt and Bower, you can install and configure Converse's
+3rd party dependencies with the following command:
+
+::
+
+ grunt fetch
+
+With AMD and require.js (recommended)
+=====================================
+
+Converse.js uses `require.js`_ to asynchronously load dependencies.
If you want to develop or customize converse.js, you'll want to load the
non-minified javascript files.
-Add the following two lines to the ** section of your webpage.
+Add the following two lines to the ** section of your webpage:
::
-
+
+require.js will then let the main.js file be parsed (because of the *data-main*
+attribute on the *script* tag), which will in turn cause converse.js to be
+parsed.
Without AMD and require.js
---------------------------
+==========================
Converse.js can also be used without require.js. If you for some reason prefer
-to use it this way, please refer to *non_amd.html* for an example of how and in
-what order all the Javascript files that converse.js depends on need to be
-loaded.
+to use it this way, please refer to
+`non_amd.html `_
+for an example of how and in what order all the Javascript files that converse.js
+depends on need to be loaded.
+
+
+Before submitting a pull request
+================================
+
+Add tests for your bugfix or feature
+------------------------------------
+
+Add a test for any bug fixed or feature added. We use Jasmine
+for testing.
+
+Take a look at ``tests.html`` and ``spec/MainSpec.js`` to see how
+the tests are implemented.
+
+If you are unsure how to write tests, please
+`contact me `_ and I'll be happy to help.
+
+Check that the tests pass
+-------------------------
+
+Check that the Jasmine tests complete sucessfully. Open
+`tests.html `_
+in your browser, and the tests will run automatically.
+
+On the command line you can run:
+
+::
+
+ grunt test
+
+Check your code for errors or bad habits by running JSHint
+----------------------------------------------------------
+
+`JSHint `_ will do a static analysis of your code and hightlight potential errors
+and/or bad habits.
+
+::
+
+ grunt jshint
+
+
+You can run both the tests and jshint in one go by calling:
+
+::
+
+ grunt check
=============
@@ -290,7 +369,7 @@ bosh_service_url
Connections to an XMPP server depend on a BOSH connection manager which acts as
a middle man between HTTP and XMPP.
-See `here`_ for more information.
+See `here `_ for more information.
fullname
--------
@@ -505,7 +584,6 @@ those hoops you had to jump through.
.. _`conversejs.org`: http://conversejs.org
.. _`require.js`: http://requirejs.org
.. _`read more about require.js's optimizer here`: http://requirejs.org/docs/optimization.html
-.. _`here`: http://metajack.im/2008/09/08/which-bosh-server-do-you-need/l
.. _`HTTP`: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
.. _`XMPP`: https://en.wikipedia.org/wiki/Xmpp
.. _`Converse.js homepage`: http://conversejs.org
diff --git a/index.html b/index.html
index 86b646f05..359107bc1 100644
--- a/index.html
+++ b/index.html
@@ -5,8 +5,8 @@
-
-
+
+
Converse.js
@@ -64,7 +64,7 @@
Custom status messages
Typing notifications
Third person messages (/me )
-
i18n aware
+
Translated into multiple languages (af, de, es, it, pt_BR)
Screencasts
diff --git a/javascripts/main.js b/javascripts/main.js
deleted file mode 100644
index d8135d37b..000000000
--- a/javascripts/main.js
+++ /dev/null
@@ -1 +0,0 @@
-console.log('This would be the main JS file.');
diff --git a/locale/af/LC_MESSAGES/af.js b/locale/af/LC_MESSAGES/af.js
index 0fcc8b9f1..2775f3a04 100644
--- a/locale/af/LC_MESSAGES/af.js
+++ b/locale/af/LC_MESSAGES/af.js
@@ -1,5 +1,5 @@
(function (root, factory) {
- var af = new Jed({
+ var translations = {
"domain": "converse",
"locale_data": {
"converse": {
@@ -450,16 +450,16 @@
]
}
}
- });
+ };
if (typeof define === 'function' && define.amd) {
define("af", ['jed'], function () {
- return factory(af);
+ return factory(new Jed(translations));
});
} else {
if (!window.locales) {
window.locales = {};
}
- window.locales.af = af;
+ window.locales.af = factory(new Jed(translations));
}
}(this, function (af) {
return af;
diff --git a/locale/de/LC_MESSAGES/de.js b/locale/de/LC_MESSAGES/de.js
index bfa924de1..0c3cafd3b 100644
--- a/locale/de/LC_MESSAGES/de.js
+++ b/locale/de/LC_MESSAGES/de.js
@@ -1,5 +1,5 @@
(function (root, factory) {
- var de = new Jed({
+ var translations = {
"domain": "converse",
"locale_data": {
"converse": {
@@ -450,16 +450,16 @@
]
}
}
- });
+ };
if (typeof define === 'function' && define.amd) {
define("de", ['jed'], function () {
- return factory(de);
+ return factory(new Jed(translations));
});
} else {
if (!window.locales) {
window.locales = {};
}
- window.locales.de = de;
+ window.locales.de = factory(new Jed(translations));
}
}(this, function (de) {
return de;
diff --git a/locale/en/LC_MESSAGES/en.js b/locale/en/LC_MESSAGES/en.js
index 5b4a4b6d6..e1452bb6f 100644
--- a/locale/en/LC_MESSAGES/en.js
+++ b/locale/en/LC_MESSAGES/en.js
@@ -1,5 +1,5 @@
(function (root, factory) {
- var en = new Jed({
+ var translations = {
"domain": "converse",
"locale_data": {
"converse": {
@@ -10,16 +10,16 @@
}
}
}
- });
+ };
if (typeof define === 'function' && define.amd) {
define("en", ['jed'], function () {
- return factory(en);
+ return factory(new Jed(translations));
});
} else {
if (!window.locales) {
window.locales = {};
}
- window.locales.en = en;
+ window.locales.en = factory(new Jed(translations));
}
}(this, function (en) {
return en;
diff --git a/locale/es/LC_MESSAGES/es.js b/locale/es/LC_MESSAGES/es.js
index 44f4920ff..8dfae7dae 100644
--- a/locale/es/LC_MESSAGES/es.js
+++ b/locale/es/LC_MESSAGES/es.js
@@ -1,5 +1,5 @@
(function (root, factory) {
- var es = new Jed({
+ var translations = {
"domain": "converse",
"locale_data": {
"converse": {
@@ -450,16 +450,16 @@
]
}
}
- });
+ };
if (typeof define === 'function' && define.amd) {
define("es", ['jed'], function () {
- return factory(es);
+ return factory(new Jed(translations));
});
} else {
if (!window.locales) {
window.locales = {};
}
- window.locales.es = es;
+ window.locales.es = factory(new Jed(translations));
}
}(this, function (es) {
return es;
diff --git a/locale/hu/LC_MESSAGES/converse.json b/locale/hu/LC_MESSAGES/converse.json
deleted file mode 100644
index ce387fc35..000000000
--- a/locale/hu/LC_MESSAGES/converse.json
+++ /dev/null
@@ -1,457 +0,0 @@
-{
- "converse": {
- "": {
- "Project-Id-Version": "Converse.js 0.4",
- "Report-Msgid-Bugs-To": "",
- "POT-Creation-Date": "2013-06-01 23:03+0200",
- "PO-Revision-Date": "2013-06-02 19:45+0200",
- "Last-Translator": "JC Brand ",
- "Language-Team": "Hungarian",
- "Language": "hu",
- "MIME-Version": "1.0",
- "Content-Type": "text/plain; charset=ASCII",
- "Content-Transfer-Encoding": "8bit",
- "Plural-Forms": "nplurals=2; plural=(n != 1);"
- },
- "Show this menu": [
- null,
- ""
- ],
- "Write in the third person": [
- null,
- ""
- ],
- "Remove messages": [
- null,
- ""
- ],
- "Personal message": [
- null,
- ""
- ],
- "Contacts": [
- null,
- ""
- ],
- "Online": [
- null,
- ""
- ],
- "Busy": [
- null,
- ""
- ],
- "Away": [
- null,
- ""
- ],
- "Offline": [
- null,
- ""
- ],
- "Click to add new chat contacts": [
- null,
- ""
- ],
- "Add a contact": [
- null,
- ""
- ],
- "Contact username": [
- null,
- ""
- ],
- "Add": [
- null,
- ""
- ],
- "Contact name": [
- null,
- ""
- ],
- "Search": [
- null,
- ""
- ],
- "No users found": [
- null,
- ""
- ],
- "Click to add as a chat contact": [
- null,
- ""
- ],
- "Click to open this room": [
- null,
- ""
- ],
- "Show more information on this room": [
- null,
- ""
- ],
- "Description:": [
- null,
- ""
- ],
- "Occupants:": [
- null,
- ""
- ],
- "Features:": [
- null,
- ""
- ],
- "Requires authentication": [
- null,
- ""
- ],
- "Hidden": [
- null,
- ""
- ],
- "Requires an invitation": [
- null,
- ""
- ],
- "Moderated": [
- null,
- ""
- ],
- "Non-anonymous": [
- null,
- ""
- ],
- "Open room": [
- null,
- ""
- ],
- "Permanent room": [
- null,
- ""
- ],
- "Public": [
- null,
- ""
- ],
- "Semi-anonymous": [
- null,
- ""
- ],
- "Temporary room": [
- null,
- ""
- ],
- "Unmoderated": [
- null,
- ""
- ],
- "Rooms": [
- null,
- ""
- ],
- "Room name": [
- null,
- ""
- ],
- "Nickname": [
- null,
- ""
- ],
- "Server": [
- null,
- ""
- ],
- "Join": [
- null,
- ""
- ],
- "Show rooms": [
- null,
- ""
- ],
- "No rooms on %1$s": [
- null,
- ""
- ],
- "Rooms on %1$s": [
- null,
- ""
- ],
- "Set chatroom topic": [
- null,
- ""
- ],
- "Kick user from chatroom": [
- null,
- ""
- ],
- "Ban user from chatroom": [
- null,
- ""
- ],
- "Message": [
- null,
- ""
- ],
- "Save": [
- null,
- ""
- ],
- "Cancel": [
- null,
- ""
- ],
- "An error occurred while trying to save the form.": [
- null,
- ""
- ],
- "This chatroom requires a password": [
- null,
- ""
- ],
- "Password: ": [
- null,
- ""
- ],
- "Submit": [
- null,
- ""
- ],
- "This room is not anonymous": [
- null,
- ""
- ],
- "This room now shows unavailable members": [
- null,
- ""
- ],
- "This room does not show unavailable members": [
- null,
- ""
- ],
- "Non-privacy-related room configuration has changed": [
- null,
- ""
- ],
- "Room logging is now enabled": [
- null,
- ""
- ],
- "Room logging is now disabled": [
- null,
- ""
- ],
- "This room is now non-anonymous": [
- null,
- ""
- ],
- "This room is now semi-anonymous": [
- null,
- ""
- ],
- "This room is now fully-anonymous": [
- null,
- ""
- ],
- "A new room has been created": [
- null,
- ""
- ],
- "Your nickname has been changed": [
- null,
- ""
- ],
- "%1$s has been banned": [
- null,
- ""
- ],
- "%1$s has been kicked out": [
- null,
- ""
- ],
- "%1$s has been removed because of an affiliation change": [
- null,
- ""
- ],
- "%1$s has been removed for not being a member": [
- null,
- ""
- ],
- "You have been banned from this room": [
- null,
- ""
- ],
- "You have been kicked from this room": [
- null,
- ""
- ],
- "You have been removed from this room because of an affiliation change": [
- null,
- ""
- ],
- "You have been removed from this room because the room has changed to members-only and you're not a member": [
- null,
- ""
- ],
- "You have been removed from this room because the MUC (Multi-user chat) service is being shut down.": [
- null,
- ""
- ],
- "You are not on the member list of this room": [
- null,
- ""
- ],
- "No nickname was specified": [
- null,
- ""
- ],
- "You are not allowed to create new rooms": [
- null,
- ""
- ],
- "Your nickname doesn't conform to this room's policies": [
- null,
- ""
- ],
- "Your nickname is already taken": [
- null,
- ""
- ],
- "This room does not (yet) exist": [
- null,
- ""
- ],
- "This room has reached it's maximum number of occupants": [
- null,
- ""
- ],
- "Topic set by %1$s to: %2$s": [
- null,
- ""
- ],
- "This user is a moderator": [
- null,
- ""
- ],
- "This user can send messages in this room": [
- null,
- ""
- ],
- "This user can NOT send messages in this room": [
- null,
- ""
- ],
- "Click to chat with this contact": [
- null,
- ""
- ],
- "Click to remove this contact": [
- null,
- ""
- ],
- "Contact requests": [
- null,
- ""
- ],
- "My contacts": [
- null,
- ""
- ],
- "Pending contacts": [
- null,
- ""
- ],
- "Custom status": [
- null,
- ""
- ],
- "Click to change your chat status": [
- null,
- ""
- ],
- "Click here to write a custom status message": [
- null,
- ""
- ],
- "online": [
- null,
- ""
- ],
- "busy": [
- null,
- ""
- ],
- "away for long": [
- null,
- ""
- ],
- "away": [
- null,
- ""
- ],
- "I am %1$s": [
- null,
- ""
- ],
- "Sign in": [
- null,
- ""
- ],
- "XMPP/Jabber Username:": [
- null,
- ""
- ],
- "Password:": [
- null,
- ""
- ],
- "Log In": [
- null,
- ""
- ],
- "BOSH Service URL:": [
- null,
- ""
- ],
- "Connected": [
- null,
- ""
- ],
- "Disconnected": [
- null,
- ""
- ],
- "Error": [
- null,
- ""
- ],
- "Connecting": [
- null,
- ""
- ],
- "Connection Failed": [
- null,
- ""
- ],
- "Authenticating": [
- null,
- ""
- ],
- "Authentication Failed": [
- null,
- ""
- ],
- "Disconnecting": [
- null,
- ""
- ],
- "Attached": [
- null,
- ""
- ],
- "Online Contacts": [
- null,
- ""
- ]
- }
-}
\ No newline at end of file
diff --git a/locale/hu/LC_MESSAGES/converse.po b/locale/hu/LC_MESSAGES/converse.po
deleted file mode 100644
index e61ce8581..000000000
--- a/locale/hu/LC_MESSAGES/converse.po
+++ /dev/null
@@ -1,481 +0,0 @@
-# Hungarian translations for Converse.js package.
-# Copyright (C) 2013 Jan-Carel Brand
-# This file is distributed under the same license as the Converse.js package.
-# JC Brand , 2013.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Converse.js 0.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-06-01 23:03+0200\n"
-"PO-Revision-Date: 2013-06-02 19:45+0200\n"
-"Last-Translator: JC Brand \n"
-"Language-Team: Hungarian\n"
-"Language: hu\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=ASCII\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#: converse.js:397 converse.js:1128
-msgid "Show this menu"
-msgstr ""
-
-#: converse.js:398 converse.js:1129
-msgid "Write in the third person"
-msgstr ""
-
-#: converse.js:399 converse.js:1133
-msgid "Remove messages"
-msgstr ""
-
-#: converse.js:539
-msgid "Personal message"
-msgstr ""
-
-#: converse.js:613
-msgid "Contacts"
-msgstr ""
-
-#: converse.js:618
-msgid "Online"
-msgstr ""
-
-#: converse.js:619
-msgid "Busy"
-msgstr ""
-
-#: converse.js:620
-msgid "Away"
-msgstr ""
-
-#: converse.js:621
-msgid "Offline"
-msgstr ""
-
-#: converse.js:628
-msgid "Click to add new chat contacts"
-msgstr ""
-
-#: converse.js:628
-msgid "Add a contact"
-msgstr ""
-
-#: converse.js:637
-msgid "Contact username"
-msgstr ""
-
-#: converse.js:638
-msgid "Add"
-msgstr ""
-
-#: converse.js:646
-msgid "Contact name"
-msgstr ""
-
-#: converse.js:647
-msgid "Search"
-msgstr ""
-
-#: converse.js:682
-msgid "No users found"
-msgstr ""
-
-#: converse.js:689
-msgid "Click to add as a chat contact"
-msgstr ""
-
-#: converse.js:753
-msgid "Click to open this room"
-msgstr ""
-
-#: converse.js:755
-msgid "Show more information on this room"
-msgstr ""
-
-#: converse.js:760
-msgid "Description:"
-msgstr ""
-
-#: converse.js:761
-msgid "Occupants:"
-msgstr ""
-
-#: converse.js:762
-msgid "Features:"
-msgstr ""
-
-#: converse.js:764
-msgid "Requires authentication"
-msgstr ""
-
-#: converse.js:767
-msgid "Hidden"
-msgstr ""
-
-#: converse.js:770
-msgid "Requires an invitation"
-msgstr ""
-
-#: converse.js:773
-msgid "Moderated"
-msgstr ""
-
-#: converse.js:776
-msgid "Non-anonymous"
-msgstr ""
-
-#: converse.js:779
-msgid "Open room"
-msgstr ""
-
-#: converse.js:782
-msgid "Permanent room"
-msgstr ""
-
-#: converse.js:785
-msgid "Public"
-msgstr ""
-
-#: converse.js:788
-msgid "Semi-anonymous"
-msgstr ""
-
-#: converse.js:791
-msgid "Temporary room"
-msgstr ""
-
-#: converse.js:794
-msgid "Unmoderated"
-msgstr ""
-
-#: converse.js:800
-msgid "Rooms"
-msgstr ""
-
-#: converse.js:804
-msgid "Room name"
-msgstr ""
-
-#: converse.js:805
-msgid "Nickname"
-msgstr ""
-
-#: converse.js:806
-msgid "Server"
-msgstr ""
-
-#: converse.js:807
-msgid "Join"
-msgstr ""
-
-#: converse.js:808
-msgid "Show rooms"
-msgstr ""
-
-#. For translators: %1$s is a variable and will be replaced with the XMPP server name
-#: converse.js:841
-msgid "No rooms on %1$s"
-msgstr ""
-
-#. For translators: %1$s is a variable and will be
-#. replaced with the XMPP server name
-#: converse.js:856
-msgid "Rooms on %1$s"
-msgstr ""
-
-#: converse.js:1130
-msgid "Set chatroom topic"
-msgstr ""
-
-#: converse.js:1131
-msgid "Kick user from chatroom"
-msgstr ""
-
-#: converse.js:1132
-msgid "Ban user from chatroom"
-msgstr ""
-
-#: converse.js:1159
-msgid "Message"
-msgstr ""
-
-#: converse.js:1273 converse.js:2318
-msgid "Save"
-msgstr ""
-
-#: converse.js:1274
-msgid "Cancel"
-msgstr ""
-
-#: converse.js:1321
-msgid "An error occurred while trying to save the form."
-msgstr ""
-
-#: converse.js:1367
-msgid "This chatroom requires a password"
-msgstr ""
-
-#: converse.js:1368
-msgid "Password: "
-msgstr ""
-
-#: converse.js:1369
-msgid "Submit"
-msgstr ""
-
-#: converse.js:1383
-msgid "This room is not anonymous"
-msgstr ""
-
-#: converse.js:1384
-msgid "This room now shows unavailable members"
-msgstr ""
-
-#: converse.js:1385
-msgid "This room does not show unavailable members"
-msgstr ""
-
-#: converse.js:1386
-msgid "Non-privacy-related room configuration has changed"
-msgstr ""
-
-#: converse.js:1387
-msgid "Room logging is now enabled"
-msgstr ""
-
-#: converse.js:1388
-msgid "Room logging is now disabled"
-msgstr ""
-
-#: converse.js:1389
-msgid "This room is now non-anonymous"
-msgstr ""
-
-#: converse.js:1390
-msgid "This room is now semi-anonymous"
-msgstr ""
-
-#: converse.js:1391
-msgid "This room is now fully-anonymous"
-msgstr ""
-
-#: converse.js:1392
-msgid "A new room has been created"
-msgstr ""
-
-#: converse.js:1393
-msgid "Your nickname has been changed"
-msgstr ""
-
-#. For translations: %1$s will be replaced with the user's nickname
-#. Don't translate "strong"
-#. Example: jcbrand has been banned
-#: converse.js:1400
-msgid "%1$s has been banned"
-msgstr ""
-
-#. For translations: %1$s will be replaced with the user's nickname
-#. Don't translate "strong"
-#. Example: jcbrand has been kicked out
-#: converse.js:1404
-msgid "%1$s has been kicked out"
-msgstr ""
-
-#. For translations: %1$s will be replaced with the user's nickname
-#. Don't translate "strong"
-#. Example: jcbrand has been removed because of an affiliasion change
-#: converse.js:1408
-msgid "%1$s has been removed because of an affiliation change"
-msgstr ""
-
-#. For translations: %1$s will be replaced with the user's nickname
-#. Don't translate "strong"
-#. Example: jcbrand has been removed for not being a member
-#: converse.js:1412
-msgid "%1$s has been removed for not being a member"
-msgstr ""
-
-#: converse.js:1416 converse.js:1478
-msgid "You have been banned from this room"
-msgstr ""
-
-#: converse.js:1417
-msgid "You have been kicked from this room"
-msgstr ""
-
-#: converse.js:1418
-msgid "You have been removed from this room because of an affiliation change"
-msgstr ""
-
-#: converse.js:1419
-msgid ""
-"You have been removed from this room because the room has changed to members-"
-"only and you're not a member"
-msgstr ""
-
-#: converse.js:1420
-msgid ""
-"You have been removed from this room because the MUC (Multi-user chat) "
-"service is being shut down."
-msgstr ""
-
-#: converse.js:1476
-msgid "You are not on the member list of this room"
-msgstr ""
-
-#: converse.js:1482
-msgid "No nickname was specified"
-msgstr ""
-
-#: converse.js:1486
-msgid "You are not allowed to create new rooms"
-msgstr ""
-
-#: converse.js:1488
-msgid "Your nickname doesn't conform to this room's policies"
-msgstr ""
-
-#: converse.js:1490
-msgid "Your nickname is already taken"
-msgstr ""
-
-#: converse.js:1492
-msgid "This room does not (yet) exist"
-msgstr ""
-
-#: converse.js:1494
-msgid "This room has reached it's maximum number of occupants"
-msgstr ""
-
-#. For translators: the %1$s and %2$s parts will get replaced by the user and topic text respectively
-#. Example: Topic set by JC Brand to: Hello World!
-#: converse.js:1571
-msgid "Topic set by %1$s to: %2$s"
-msgstr ""
-
-#: converse.js:1587
-msgid "This user is a moderator"
-msgstr ""
-
-#: converse.js:1590
-msgid "This user can send messages in this room"
-msgstr ""
-
-#: converse.js:1593
-msgid "This user can NOT send messages in this room"
-msgstr ""
-
-#: converse.js:1796
-msgid "Click to chat with this contact"
-msgstr ""
-
-#: converse.js:1797 converse.js:1801
-msgid "Click to remove this contact"
-msgstr ""
-
-#: converse.js:2163
-msgid "Contact requests"
-msgstr ""
-
-#: converse.js:2164
-msgid "My contacts"
-msgstr ""
-
-#: converse.js:2165
-msgid "Pending contacts"
-msgstr ""
-
-#: converse.js:2317
-msgid "Custom status"
-msgstr ""
-
-#: converse.js:2323
-msgid "Click to change your chat status"
-msgstr ""
-
-#: converse.js:2326
-msgid "Click here to write a custom status message"
-msgstr ""
-
-#: converse.js:2355 converse.js:2363
-msgid "online"
-msgstr ""
-
-#: converse.js:2357
-msgid "busy"
-msgstr ""
-
-#: converse.js:2359
-msgid "away for long"
-msgstr ""
-
-#: converse.js:2361
-msgid "away"
-msgstr ""
-
-#. For translators: the %1$s part gets replaced with the status
-#. Example, I am online
-#: converse.js:2375 converse.js:2409
-msgid "I am %1$s"
-msgstr ""
-
-#: converse.js:2480
-msgid "Sign in"
-msgstr ""
-
-#: converse.js:2483
-msgid "XMPP/Jabber Username:"
-msgstr ""
-
-#: converse.js:2485
-msgid "Password:"
-msgstr ""
-
-#: converse.js:2487
-msgid "Log In"
-msgstr ""
-
-#: converse.js:2491
-msgid "BOSH Service URL:"
-msgstr ""
-
-#: converse.js:2503
-msgid "Connected"
-msgstr ""
-
-#: converse.js:2507
-msgid "Disconnected"
-msgstr ""
-
-#: converse.js:2511
-msgid "Error"
-msgstr ""
-
-#: converse.js:2513
-msgid "Connecting"
-msgstr ""
-
-#: converse.js:2516
-msgid "Connection Failed"
-msgstr ""
-
-#: converse.js:2518
-msgid "Authenticating"
-msgstr ""
-
-#: converse.js:2521
-msgid "Authentication Failed"
-msgstr ""
-
-#: converse.js:2523
-msgid "Disconnecting"
-msgstr ""
-
-#: converse.js:2525
-msgid "Attached"
-msgstr ""
-
-#: converse.js:2656
-msgid "Online Contacts"
-msgstr ""
diff --git a/locale/hu/LC_MESSAGES/hu.js b/locale/hu/LC_MESSAGES/hu.js
deleted file mode 100644
index 381c9096a..000000000
--- a/locale/hu/LC_MESSAGES/hu.js
+++ /dev/null
@@ -1,459 +0,0 @@
-(function (root, factory) {
- define("hu", ['jed'], function () {
- var hu = new Jed({
- "domain": "converse",
- "locale_data": {
- "converse": {
- "": {
- "Content-Type": "text/plain; charset=ASCII",
- "Content-Transfer-Encoding": "8bit",
- "Plural-Forms": "nplurals=2; plural=(n != 1);"
- },
- "Show this menu": [
- null,
- ""
- ],
- "Write in the third person": [
- null,
- ""
- ],
- "Remove messages": [
- null,
- ""
- ],
- "Personal message": [
- null,
- ""
- ],
- "Contacts": [
- null,
- ""
- ],
- "Online": [
- null,
- ""
- ],
- "Busy": [
- null,
- ""
- ],
- "Away": [
- null,
- ""
- ],
- "Offline": [
- null,
- ""
- ],
- "Click to add new chat contacts": [
- null,
- ""
- ],
- "Add a contact": [
- null,
- ""
- ],
- "Contact username": [
- null,
- ""
- ],
- "Add": [
- null,
- ""
- ],
- "Contact name": [
- null,
- ""
- ],
- "Search": [
- null,
- ""
- ],
- "No users found": [
- null,
- ""
- ],
- "Click to add as a chat contact": [
- null,
- ""
- ],
- "Click to open this room": [
- null,
- ""
- ],
- "Show more information on this room": [
- null,
- ""
- ],
- "Description:": [
- null,
- ""
- ],
- "Occupants:": [
- null,
- ""
- ],
- "Features:": [
- null,
- ""
- ],
- "Requires authentication": [
- null,
- ""
- ],
- "Hidden": [
- null,
- ""
- ],
- "Requires an invitation": [
- null,
- ""
- ],
- "Moderated": [
- null,
- ""
- ],
- "Non-anonymous": [
- null,
- ""
- ],
- "Open room": [
- null,
- ""
- ],
- "Permanent room": [
- null,
- ""
- ],
- "Public": [
- null,
- ""
- ],
- "Semi-anonymous": [
- null,
- ""
- ],
- "Temporary room": [
- null,
- ""
- ],
- "Unmoderated": [
- null,
- ""
- ],
- "Rooms": [
- null,
- ""
- ],
- "Room name": [
- null,
- ""
- ],
- "Nickname": [
- null,
- ""
- ],
- "Server": [
- null,
- ""
- ],
- "Join": [
- null,
- ""
- ],
- "Show rooms": [
- null,
- ""
- ],
- "No rooms on %1$s": [
- null,
- ""
- ],
- "Rooms on %1$s": [
- null,
- ""
- ],
- "Set chatroom topic": [
- null,
- ""
- ],
- "Kick user from chatroom": [
- null,
- ""
- ],
- "Ban user from chatroom": [
- null,
- ""
- ],
- "Message": [
- null,
- ""
- ],
- "Save": [
- null,
- ""
- ],
- "Cancel": [
- null,
- ""
- ],
- "An error occurred while trying to save the form.": [
- null,
- ""
- ],
- "This chatroom requires a password": [
- null,
- ""
- ],
- "Password: ": [
- null,
- ""
- ],
- "Submit": [
- null,
- ""
- ],
- "This room is not anonymous": [
- null,
- ""
- ],
- "This room now shows unavailable members": [
- null,
- ""
- ],
- "This room does not show unavailable members": [
- null,
- ""
- ],
- "Non-privacy-related room configuration has changed": [
- null,
- ""
- ],
- "Room logging is now enabled": [
- null,
- ""
- ],
- "Room logging is now disabled": [
- null,
- ""
- ],
- "This room is now non-anonymous": [
- null,
- ""
- ],
- "This room is now semi-anonymous": [
- null,
- ""
- ],
- "This room is now fully-anonymous": [
- null,
- ""
- ],
- "A new room has been created": [
- null,
- ""
- ],
- "Your nickname has been changed": [
- null,
- ""
- ],
- "%1$s has been banned": [
- null,
- ""
- ],
- "%1$s has been kicked out": [
- null,
- ""
- ],
- "%1$s has been removed because of an affiliation change": [
- null,
- ""
- ],
- "%1$s has been removed for not being a member": [
- null,
- ""
- ],
- "You have been banned from this room": [
- null,
- ""
- ],
- "You have been kicked from this room": [
- null,
- ""
- ],
- "You have been removed from this room because of an affiliation change": [
- null,
- ""
- ],
- "You have been removed from this room because the room has changed to members-only and you're not a member": [
- null,
- ""
- ],
- "You have been removed from this room because the MUC (Multi-user chat) service is being shut down.": [
- null,
- ""
- ],
- "You are not on the member list of this room": [
- null,
- ""
- ],
- "No nickname was specified": [
- null,
- ""
- ],
- "You are not allowed to create new rooms": [
- null,
- ""
- ],
- "Your nickname doesn't conform to this room's policies": [
- null,
- ""
- ],
- "Your nickname is already taken": [
- null,
- ""
- ],
- "This room does not (yet) exist": [
- null,
- ""
- ],
- "This room has reached it's maximum number of occupants": [
- null,
- ""
- ],
- "Topic set by %1$s to: %2$s": [
- null,
- ""
- ],
- "This user is a moderator": [
- null,
- ""
- ],
- "This user can send messages in this room": [
- null,
- ""
- ],
- "This user can NOT send messages in this room": [
- null,
- ""
- ],
- "Click to chat with this contact": [
- null,
- ""
- ],
- "Click to remove this contact": [
- null,
- ""
- ],
- "Contact requests": [
- null,
- ""
- ],
- "My contacts": [
- null,
- ""
- ],
- "Pending contacts": [
- null,
- ""
- ],
- "Custom status": [
- null,
- ""
- ],
- "Click to change your chat status": [
- null,
- ""
- ],
- "Click here to write a custom status message": [
- null,
- ""
- ],
- "online": [
- null,
- ""
- ],
- "busy": [
- null,
- ""
- ],
- "away for long": [
- null,
- ""
- ],
- "away": [
- null,
- ""
- ],
- "I am %1$s": [
- null,
- ""
- ],
- "Sign in": [
- null,
- ""
- ],
- "XMPP/Jabber Username:": [
- null,
- ""
- ],
- "Password:": [
- null,
- ""
- ],
- "Log In": [
- null,
- ""
- ],
- "BOSH Service URL:": [
- null,
- ""
- ],
- "Connected": [
- null,
- ""
- ],
- "Disconnected": [
- null,
- ""
- ],
- "Error": [
- null,
- ""
- ],
- "Connecting": [
- null,
- ""
- ],
- "Connection Failed": [
- null,
- ""
- ],
- "Authenticating": [
- null,
- ""
- ],
- "Authentication Failed": [
- null,
- ""
- ],
- "Disconnecting": [
- null,
- ""
- ],
- "Attached": [
- null,
- ""
- ],
- "Online Contacts": [
- null,
- ""
- ]
- }
- }
- });
- return factory(hu);
- });
-}(this, function (hu) {
- return hu;
-}));
diff --git a/locale/it/LC_MESSAGES/it.js b/locale/it/LC_MESSAGES/it.js
index e3df7e8b0..0190d51fe 100644
--- a/locale/it/LC_MESSAGES/it.js
+++ b/locale/it/LC_MESSAGES/it.js
@@ -1,5 +1,5 @@
(function (root, factory) {
- var it = new Jed({
+ var translations = {
"domain": "converse",
"locale_data": {
"converse": {
@@ -461,16 +461,16 @@
]
}
}
- });
+ };
if (typeof define === 'function' && define.amd) {
define("it", ['jed'], function () {
- return factory(it);
+ return factory(new Jed(translations));
});
} else {
if (!window.locales) {
window.locales = {};
}
- window.locales.it = it;
+ window.locales.it = factory(new Jed(translations));
}
}(this, function (it) {
return it;
diff --git a/locale/locales.js b/locale/locales.js
index d49106ae4..23d3703d9 100644
--- a/locale/locales.js
+++ b/locale/locales.js
@@ -8,12 +8,11 @@
(function (root, factory) {
require.config({
paths: {
- "jed": "Libraries/jed",
+ "jed": "components/jed/jed",
"af": "locale/af/LC_MESSAGES/af",
"en": "locale/en/LC_MESSAGES/en",
"es": "locale/es/LC_MESSAGES/es",
"de": "locale/de/LC_MESSAGES/de",
- "hu": "locale/hu/LC_MESSAGES/hu",
"it": "locale/it/LC_MESSAGES/it",
"pt_BR": "locale/pt_BR/LC_MESSAGES/pt_BR"
}
@@ -25,16 +24,14 @@
'en',
'es',
'de',
- 'hu',
"it",
"pt_BR"
- ], function (jed, af, en, es, de, hu, it, pt_BR) {
+ ], function (jed, af, en, es, de, it, pt_BR) {
root.locales = {};
root.locales.af = af;
root.locales.en = en;
root.locales.es = es;
root.locales.de = de;
- root.locales.hu = hu;
root.locales.it = it;
root.locales.pt_BR = pt_BR;
});
diff --git a/locale/pt_BR/LC_MESSAGES/pt_BR.js b/locale/pt_BR/LC_MESSAGES/pt_BR.js
index d2aef4b45..ca8a002ff 100644
--- a/locale/pt_BR/LC_MESSAGES/pt_BR.js
+++ b/locale/pt_BR/LC_MESSAGES/pt_BR.js
@@ -1,5 +1,5 @@
(function (root, factory) {
- var pt_BR = new Jed({
+ var translations = {
"domain": "converse",
"locale_data": {
"converse": {
@@ -461,16 +461,16 @@
]
}
}
- });
+ };
if (typeof define === 'function' && define.amd) {
define("pt_BR", ['jed'], function () {
- return factory(pt_BR);
+ return factory(new Jed(translations));
});
} else {
if (!window.locales) {
window.locales = {};
}
- window.locales.pt_BR = pt_BR;
+ window.locales.pt_BR = factory(new Jed(translations));
}
}(this, function (i18n) {
return i18n;
diff --git a/main.js b/main.js
index e445679d5..e752e5962 100644
--- a/main.js
+++ b/main.js
@@ -1,3 +1,42 @@
-require(["jquery", "converse"], function($, converse) {
+require.config({
+ paths: {
+ "jquery": "components/jquery/jquery",
+ "locales": "locale/locales",
+ "sjcl": "components/sjcl/sjcl",
+ "tinysort": "components/tinysort/src/jquery.tinysort",
+ "underscore": "components/underscore/underscore",
+ "backbone": "components/backbone/backbone",
+ "localstorage": "components/backbone.localStorage/backbone.localStorage",
+ "strophe": "components/strophe/strophe",
+ "strophe.muc": "components/strophe.muc/index",
+ "strophe.roster": "components/strophe.roster/index",
+ "strophe.vcard": "components/strophe.vcard/index",
+ "strophe.disco": "components/strophe.disco/index"
+ },
+
+ // define module dependencies for modules not using define
+ shim: {
+ 'backbone': {
+ //These script dependencies should be loaded before loading
+ //backbone.js
+ deps: [
+ 'underscore',
+ 'jquery'
+ ],
+ //Once loaded, use the global 'Backbone' as the
+ //module value.
+ exports: 'Backbone'
+ },
+ 'tinysort': { deps: ['jquery'] },
+ 'strophe': { deps: ['jquery'] },
+ 'underscore': { exports: '_' },
+ 'strophe.muc': { deps: ['strophe', 'jquery'] },
+ 'strophe.roster': { deps: ['strophe'] },
+ 'strophe.vcard': { deps: ['strophe'] },
+ 'strophe.disco': { deps: ['strophe'] }
+ }
+});
+
+require(["components/requirejs/require", "jquery", "converse"], function(require, $, converse) {
window.converse = converse;
});
diff --git a/non_amd.html b/non_amd.html
index f1c9f5c36..24f096c11 100644
--- a/non_amd.html
+++ b/non_amd.html
@@ -8,17 +8,17 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
Converse.js
diff --git a/package.json b/package.json
index b3fd194cb..7786fd926 100755
--- a/package.json
+++ b/package.json
@@ -29,6 +29,11 @@
"devDependencies": {
"grunt-cli": "~0.1.9",
"grunt": "~0.4.1",
- "grunt-contrib-jshint": "~0.6.0"
+ "grunt-contrib-jshint": "~0.6.0",
+ "phantomjs": "~1.9.1-0",
+ "jasmine-reporters": "~0.2.1",
+ "bower": "~1.0.0",
+ "grunt-contrib-requirejs": "~0.4.1",
+ "grunt-contrib-cssmin": "~0.6.1"
}
}
diff --git a/test_minified.html b/test_minified.html
new file mode 100644
index 000000000..831e18e38
--- /dev/null
+++ b/test_minified.html
@@ -0,0 +1,115 @@
+
+
+
+ converse.js tests for minified files
+
+
+
+
+
+
+
+
+
+
+
+
+
+