+ The following examples demonstrate how it is possible to generate and save any type of data right in the browser using the W3C saveAs()FileSaver interface, without contacting any servers.
+
+
+
+
+
+
+
+
+
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/backbone-relational/backbone-relational.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/backbone-relational/backbone-relational.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,2076 @@
+/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab: */
+/**
+ * Backbone-relational.js 0.10.0
+ * (c) 2011-2014 Paul Uithol and contributors (https://github.com/PaulUithol/Backbone-relational/graphs/contributors)
+ *
+ * Backbone-relational may be freely distributed under the MIT license; see the accompanying LICENSE.txt.
+ * For details and documentation: https://github.com/PaulUithol/Backbone-relational.
+ * Depends on Backbone (and thus on Underscore as well): https://github.com/documentcloud/backbone.
+ *
+ * Example:
+ *
+ Zoo = Backbone.RelationalModel.extend({
+ relations: [ {
+ type: Backbone.HasMany,
+ key: 'animals',
+ relatedModel: 'Animal',
+ reverseRelation: {
+ key: 'livesIn',
+ includeInJSON: 'id'
+ // 'relatedModel' is automatically set to 'Zoo'; the 'relationType' to 'HasOne'.
+ }
+ } ],
+
+ toString: function() {
+ return this.get( 'name' );
+ }
+ });
+
+ Animal = Backbone.RelationalModel.extend({
+ toString: function() {
+ return this.get( 'species' );
+ }
+ });
+
+ // Creating the zoo will give it a collection with one animal in it: the monkey.
+ // The animal created after that has a relation `livesIn` that points to the zoo it's currently associated with.
+ // If you instantiate (or fetch) the zebra later, it will automatically be added.
+
+ var zoo = new Zoo({
+ name: 'Artis',
+ animals: [ { id: 'monkey-1', species: 'Chimp' }, 'lion-1', 'zebra-1' ]
+ });
+
+ var lion = new Animal( { id: 'lion-1', species: 'Lion' } ),
+ monkey = zoo.get( 'animals' ).first(),
+ sameZoo = lion.get( 'livesIn' );
+ */
+( function( root, factory ) {
+ // Set up Backbone-relational for the environment. Start with AMD.
+ if ( typeof define === 'function' && define.amd ) {
+ define( [ 'exports', 'backbone', 'underscore' ], factory );
+ }
+ // Next for Node.js or CommonJS.
+ else if ( typeof exports !== 'undefined' ) {
+ factory( exports, require( 'backbone' ), require( 'underscore' ) );
+ }
+ // Finally, as a browser global. Use `root` here as it references `window`.
+ else {
+ factory( root, root.Backbone, root._ );
+ }
+}( this, function( exports, Backbone, _ ) {
+ "use strict";
+
+ Backbone.Relational = {
+ showWarnings: true
+ };
+
+ /**
+ * Semaphore mixin; can be used as both binary and counting.
+ **/
+ Backbone.Semaphore = {
+ _permitsAvailable: null,
+ _permitsUsed: 0,
+
+ acquire: function() {
+ if ( this._permitsAvailable && this._permitsUsed >= this._permitsAvailable ) {
+ throw new Error( 'Max permits acquired' );
+ }
+ else {
+ this._permitsUsed++;
+ }
+ },
+
+ release: function() {
+ if ( this._permitsUsed === 0 ) {
+ throw new Error( 'All permits released' );
+ }
+ else {
+ this._permitsUsed--;
+ }
+ },
+
+ isLocked: function() {
+ return this._permitsUsed > 0;
+ },
+
+ setAvailablePermits: function( amount ) {
+ if ( this._permitsUsed > amount ) {
+ throw new Error( 'Available permits cannot be less than used permits' );
+ }
+ this._permitsAvailable = amount;
+ }
+ };
+
+ /**
+ * A BlockingQueue that accumulates items while blocked (via 'block'),
+ * and processes them when unblocked (via 'unblock').
+ * Process can also be called manually (via 'process').
+ */
+ Backbone.BlockingQueue = function() {
+ this._queue = [];
+ };
+ _.extend( Backbone.BlockingQueue.prototype, Backbone.Semaphore, {
+ _queue: null,
+
+ add: function( func ) {
+ if ( this.isBlocked() ) {
+ this._queue.push( func );
+ }
+ else {
+ func();
+ }
+ },
+
+ // Some of the queued events may trigger other blocking events. By
+ // copying the queue here it allows queued events to process closer to
+ // the natural order.
+ //
+ // queue events [ 'A', 'B', 'C' ]
+ // A handler of 'B' triggers 'D' and 'E'
+ // By copying `this._queue` this executes:
+ // [ 'A', 'B', 'D', 'E', 'C' ]
+ // The same order the would have executed if they didn't have to be
+ // delayed and queued.
+ process: function() {
+ var queue = this._queue;
+ this._queue = [];
+ while ( queue && queue.length ) {
+ queue.shift()();
+ }
+ },
+
+ block: function() {
+ this.acquire();
+ },
+
+ unblock: function() {
+ this.release();
+ if ( !this.isBlocked() ) {
+ this.process();
+ }
+ },
+
+ isBlocked: function() {
+ return this.isLocked();
+ }
+ });
+ /**
+ * Global event queue. Accumulates external events ('add:', 'remove:' and 'change:')
+ * until the top-level object is fully initialized (see 'Backbone.RelationalModel').
+ */
+ Backbone.Relational.eventQueue = new Backbone.BlockingQueue();
+
+ /**
+ * Backbone.Store keeps track of all created (and destruction of) Backbone.RelationalModel.
+ * Handles lookup for relations.
+ */
+ Backbone.Store = function() {
+ this._collections = [];
+ this._reverseRelations = [];
+ this._orphanRelations = [];
+ this._subModels = [];
+ this._modelScopes = [ exports ];
+ };
+ _.extend( Backbone.Store.prototype, Backbone.Events, {
+ /**
+ * Create a new `Relation`.
+ * @param {Backbone.RelationalModel} [model]
+ * @param {Object} relation
+ * @param {Object} [options]
+ */
+ initializeRelation: function( model, relation, options ) {
+ var type = !_.isString( relation.type ) ? relation.type : Backbone[ relation.type ] || this.getObjectByName( relation.type );
+ if ( type && type.prototype instanceof Backbone.Relation ) {
+ var rel = new type( model, relation, options ); // Also pushes the new Relation into `model._relations`
+ }
+ else {
+ Backbone.Relational.showWarnings && typeof console !== 'undefined' && console.warn( 'Relation=%o; missing or invalid relation type!', relation );
+ }
+ },
+
+ /**
+ * Add a scope for `getObjectByName` to look for model types by name.
+ * @param {Object} scope
+ */
+ addModelScope: function( scope ) {
+ this._modelScopes.push( scope );
+ },
+
+ /**
+ * Remove a scope.
+ * @param {Object} scope
+ */
+ removeModelScope: function( scope ) {
+ this._modelScopes = _.without( this._modelScopes, scope );
+ },
+
+ /**
+ * Add a set of subModelTypes to the store, that can be used to resolve the '_superModel'
+ * for a model later in 'setupSuperModel'.
+ *
+ * @param {Backbone.RelationalModel} subModelTypes
+ * @param {Backbone.RelationalModel} superModelType
+ */
+ addSubModels: function( subModelTypes, superModelType ) {
+ this._subModels.push({
+ 'superModelType': superModelType,
+ 'subModels': subModelTypes
+ });
+ },
+
+ /**
+ * Check if the given modelType is registered as another model's subModel. If so, add it to the super model's
+ * '_subModels', and set the modelType's '_superModel', '_subModelTypeName', and '_subModelTypeAttribute'.
+ *
+ * @param {Backbone.RelationalModel} modelType
+ */
+ setupSuperModel: function( modelType ) {
+ _.find( this._subModels, function( subModelDef ) {
+ return _.filter( subModelDef.subModels || [], function( subModelTypeName, typeValue ) {
+ var subModelType = this.getObjectByName( subModelTypeName );
+
+ if ( modelType === subModelType ) {
+ // Set 'modelType' as a child of the found superModel
+ subModelDef.superModelType._subModels[ typeValue ] = modelType;
+
+ // Set '_superModel', '_subModelTypeValue', and '_subModelTypeAttribute' on 'modelType'.
+ modelType._superModel = subModelDef.superModelType;
+ modelType._subModelTypeValue = typeValue;
+ modelType._subModelTypeAttribute = subModelDef.superModelType.prototype.subModelTypeAttribute;
+ return true;
+ }
+ }, this ).length;
+ }, this );
+ },
+
+ /**
+ * Add a reverse relation. Is added to the 'relations' property on model's prototype, and to
+ * existing instances of 'model' in the store as well.
+ * @param {Object} relation
+ * @param {Backbone.RelationalModel} relation.model
+ * @param {String} relation.type
+ * @param {String} relation.key
+ * @param {String|Object} relation.relatedModel
+ */
+ addReverseRelation: function( relation ) {
+ var exists = _.any( this._reverseRelations, function( rel ) {
+ return _.all( relation || [], function( val, key ) {
+ return val === rel[ key ];
+ });
+ });
+
+ if ( !exists && relation.model && relation.type ) {
+ this._reverseRelations.push( relation );
+ this._addRelation( relation.model, relation );
+ this.retroFitRelation( relation );
+ }
+ },
+
+ /**
+ * Deposit a `relation` for which the `relatedModel` can't be resolved at the moment.
+ *
+ * @param {Object} relation
+ */
+ addOrphanRelation: function( relation ) {
+ var exists = _.any( this._orphanRelations, function( rel ) {
+ return _.all( relation || [], function( val, key ) {
+ return val === rel[ key ];
+ });
+ });
+
+ if ( !exists && relation.model && relation.type ) {
+ this._orphanRelations.push( relation );
+ }
+ },
+
+ /**
+ * Try to initialize any `_orphanRelation`s
+ */
+ processOrphanRelations: function() {
+ // Make sure to operate on a copy since we're removing while iterating
+ _.each( this._orphanRelations.slice( 0 ), function( rel ) {
+ var relatedModel = Backbone.Relational.store.getObjectByName( rel.relatedModel );
+ if ( relatedModel ) {
+ this.initializeRelation( null, rel );
+ this._orphanRelations = _.without( this._orphanRelations, rel );
+ }
+ }, this );
+ },
+
+ /**
+ *
+ * @param {Backbone.RelationalModel.constructor} type
+ * @param {Object} relation
+ * @private
+ */
+ _addRelation: function( type, relation ) {
+ if ( !type.prototype.relations ) {
+ type.prototype.relations = [];
+ }
+ type.prototype.relations.push( relation );
+
+ _.each( type._subModels || [], function( subModel ) {
+ this._addRelation( subModel, relation );
+ }, this );
+ },
+
+ /**
+ * Add a 'relation' to all existing instances of 'relation.model' in the store
+ * @param {Object} relation
+ */
+ retroFitRelation: function( relation ) {
+ var coll = this.getCollection( relation.model, false );
+ coll && coll.each( function( model ) {
+ if ( !( model instanceof relation.model ) ) {
+ return;
+ }
+
+ var rel = new relation.type( model, relation );
+ }, this );
+ },
+
+ /**
+ * Find the Store's collection for a certain type of model.
+ * @param {Backbone.RelationalModel} type
+ * @param {Boolean} [create=true] Should a collection be created if none is found?
+ * @return {Backbone.Collection} A collection if found (or applicable for 'model'), or null
+ */
+ getCollection: function( type, create ) {
+ if ( type instanceof Backbone.RelationalModel ) {
+ type = type.constructor;
+ }
+
+ var rootModel = type;
+ while ( rootModel._superModel ) {
+ rootModel = rootModel._superModel;
+ }
+
+ var coll = _.find( this._collections, function( item ) {
+ return item.model === rootModel;
+ });
+
+ if ( !coll && create !== false ) {
+ coll = this._createCollection( rootModel );
+ }
+
+ return coll;
+ },
+
+ /**
+ * Find a model type on one of the modelScopes by name. Names are split on dots.
+ * @param {String} name
+ * @return {Object}
+ */
+ getObjectByName: function( name ) {
+ var parts = name.split( '.' ),
+ type = null;
+
+ _.find( this._modelScopes, function( scope ) {
+ type = _.reduce( parts || [], function( memo, val ) {
+ return memo ? memo[ val ] : undefined;
+ }, scope );
+
+ if ( type && type !== scope ) {
+ return true;
+ }
+ }, this );
+
+ return type;
+ },
+
+ _createCollection: function( type ) {
+ var coll;
+
+ // If 'type' is an instance, take its constructor
+ if ( type instanceof Backbone.RelationalModel ) {
+ type = type.constructor;
+ }
+
+ // Type should inherit from Backbone.RelationalModel.
+ if ( type.prototype instanceof Backbone.RelationalModel ) {
+ coll = new Backbone.Collection();
+ coll.model = type;
+
+ this._collections.push( coll );
+ }
+
+ return coll;
+ },
+
+ /**
+ * Find the attribute that is to be used as the `id` on a given object
+ * @param type
+ * @param {String|Number|Object|Backbone.RelationalModel} item
+ * @return {String|Number}
+ */
+ resolveIdForItem: function( type, item ) {
+ var id = _.isString( item ) || _.isNumber( item ) ? item : null;
+
+ if ( id === null ) {
+ if ( item instanceof Backbone.RelationalModel ) {
+ id = item.id;
+ }
+ else if ( _.isObject( item ) ) {
+ id = item[ type.prototype.idAttribute ];
+ }
+ }
+
+ // Make all falsy values `null` (except for 0, which could be an id.. see '/issues/179')
+ if ( !id && id !== 0 ) {
+ id = null;
+ }
+
+ return id;
+ },
+
+ /**
+ * Find a specific model of a certain `type` in the store
+ * @param type
+ * @param {String|Number|Object|Backbone.RelationalModel} item
+ */
+ find: function( type, item ) {
+ var id = this.resolveIdForItem( type, item ),
+ coll = this.getCollection( type );
+
+ // Because the found object could be of any of the type's superModel
+ // types, only return it if it's actually of the type asked for.
+ if ( coll ) {
+ var obj = coll.get( id );
+
+ if ( obj instanceof type ) {
+ return obj;
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Add a 'model' to its appropriate collection. Retain the original contents of 'model.collection'.
+ * @param {Backbone.RelationalModel} model
+ */
+ register: function( model ) {
+ var coll = this.getCollection( model );
+
+ if ( coll ) {
+ var modelColl = model.collection;
+ coll.add( model );
+ model.collection = modelColl;
+ }
+ },
+
+ /**
+ * Check if the given model may use the given `id`
+ * @param model
+ * @param [id]
+ */
+ checkId: function( model, id ) {
+ var coll = this.getCollection( model ),
+ duplicate = coll && coll.get( id );
+
+ if ( duplicate && model !== duplicate ) {
+ if ( Backbone.Relational.showWarnings && typeof console !== 'undefined' ) {
+ console.warn( 'Duplicate id! Old RelationalModel=%o, new RelationalModel=%o', duplicate, model );
+ }
+
+ throw new Error( "Cannot instantiate more than one Backbone.RelationalModel with the same id per type!" );
+ }
+ },
+
+ /**
+ * Explicitly update a model's id in its store collection
+ * @param {Backbone.RelationalModel} model
+ */
+ update: function( model ) {
+ var coll = this.getCollection( model );
+
+ // Register a model if it isn't yet (which happens if it was created without an id).
+ if ( !coll.contains( model ) ) {
+ this.register( model );
+ }
+
+ // This triggers updating the lookup indices kept in a collection
+ coll._onModelEvent( 'change:' + model.idAttribute, model, coll );
+
+ // Trigger an event on model so related models (having the model's new id in their keyContents) can add it.
+ model.trigger( 'relational:change:id', model, coll );
+ },
+
+ /**
+ * Unregister from the store: a specific model, a collection, or a model type.
+ * @param {Backbone.RelationalModel|Backbone.RelationalModel.constructor|Backbone.Collection} type
+ */
+ unregister: function( type ) {
+ var coll,
+ models;
+
+ if ( type instanceof Backbone.Model ) {
+ coll = this.getCollection( type );
+ models = [ type ];
+ }
+ else if ( type instanceof Backbone.Collection ) {
+ coll = this.getCollection( type.model );
+ models = _.clone( type.models );
+ }
+ else {
+ coll = this.getCollection( type );
+ models = _.clone( coll.models );
+ }
+
+ _.each( models, function( model ) {
+ this.stopListening( model );
+ _.invoke( model.getRelations(), 'stopListening' );
+ }, this );
+
+
+ // If we've unregistered an entire store collection, reset the collection (which is much faster).
+ // Otherwise, remove each model one by one.
+ if ( _.contains( this._collections, type ) ) {
+ coll.reset( [] );
+ }
+ else {
+ _.each( models, function( model ) {
+ if ( coll.get( model ) ) {
+ coll.remove( model );
+ }
+ else {
+ coll.trigger( 'relational:remove', model, coll );
+ }
+ }, this );
+ }
+ },
+
+ /**
+ * Reset the `store` to it's original state. The `reverseRelations` are kept though, since attempting to
+ * re-initialize these on models would lead to a large amount of warnings.
+ */
+ reset: function() {
+ this.stopListening();
+
+ // Unregister each collection to remove event listeners
+ _.each( this._collections, function( coll ) {
+ this.unregister( coll );
+ }, this );
+
+ this._collections = [];
+ this._subModels = [];
+ this._modelScopes = [ exports ];
+ }
+ });
+ Backbone.Relational.store = new Backbone.Store();
+
+ /**
+ * The main Relation class, from which 'HasOne' and 'HasMany' inherit. Internally, 'relational:' events
+ * are used to regulate addition and removal of models from relations.
+ *
+ * @param {Backbone.RelationalModel} [instance] Model that this relation is created for. If no model is supplied,
+ * Relation just tries to instantiate it's `reverseRelation` if specified, and bails out after that.
+ * @param {Object} options
+ * @param {string} options.key
+ * @param {Backbone.RelationalModel.constructor} options.relatedModel
+ * @param {Boolean|String} [options.includeInJSON=true] Serialize the given attribute for related model(s)' in toJSON, or just their ids.
+ * @param {Boolean} [options.createModels=true] Create objects from the contents of keys if the object is not found in Backbone.store.
+ * @param {Object} [options.reverseRelation] Specify a bi-directional relation. If provided, Relation will reciprocate
+ * the relation to the 'relatedModel'. Required and optional properties match 'options', except that it also needs
+ * {Backbone.Relation|String} type ('HasOne' or 'HasMany').
+ * @param {Object} opts
+ */
+ Backbone.Relation = function( instance, options, opts ) {
+ this.instance = instance;
+ // Make sure 'options' is sane, and fill with defaults from subclasses and this object's prototype
+ options = _.isObject( options ) ? options : {};
+ this.reverseRelation = _.defaults( options.reverseRelation || {}, this.options.reverseRelation );
+ this.options = _.defaults( options, this.options, Backbone.Relation.prototype.options );
+
+ this.reverseRelation.type = !_.isString( this.reverseRelation.type ) ? this.reverseRelation.type :
+ Backbone[ this.reverseRelation.type ] || Backbone.Relational.store.getObjectByName( this.reverseRelation.type );
+
+ this.key = this.options.key;
+ this.keySource = this.options.keySource || this.key;
+ this.keyDestination = this.options.keyDestination || this.keySource || this.key;
+
+ this.model = this.options.model || this.instance.constructor;
+
+ this.relatedModel = this.options.relatedModel;
+
+ // No 'relatedModel' is interpreted as self-referential
+ if ( _.isUndefined( this.relatedModel ) ) {
+ this.relatedModel = this.model;
+ }
+
+ // Otherwise, try to resolve the given value to an object
+ if ( _.isFunction( this.relatedModel ) && !( this.relatedModel.prototype instanceof Backbone.RelationalModel ) ) {
+ this.relatedModel = _.result( this, 'relatedModel' );
+ }
+ if ( _.isString( this.relatedModel ) ) {
+ this.relatedModel = Backbone.Relational.store.getObjectByName( this.relatedModel );
+ }
+
+
+ if ( !this.checkPreconditions() ) {
+ return;
+ }
+
+ // Add the reverse relation on 'relatedModel' to the store's reverseRelations
+ if ( !this.options.isAutoRelation && this.reverseRelation.type && this.reverseRelation.key ) {
+ Backbone.Relational.store.addReverseRelation( _.defaults( {
+ isAutoRelation: true,
+ model: this.relatedModel,
+ relatedModel: this.model,
+ reverseRelation: this.options // current relation is the 'reverseRelation' for its own reverseRelation
+ },
+ this.reverseRelation // Take further properties from this.reverseRelation (type, key, etc.)
+ ) );
+ }
+
+ if ( instance ) {
+ var contentKey = this.keySource;
+ if ( contentKey !== this.key && _.isObject( this.instance.get( this.key ) ) ) {
+ contentKey = this.key;
+ }
+
+ this.setKeyContents( this.instance.get( contentKey ) );
+ this.relatedCollection = Backbone.Relational.store.getCollection( this.relatedModel );
+
+ // Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
+ if ( this.keySource !== this.key ) {
+ delete this.instance.attributes[ this.keySource ];
+ }
+
+ // Add this Relation to instance._relations
+ this.instance._relations[ this.key ] = this;
+
+ this.initialize( opts );
+
+ if ( this.options.autoFetch ) {
+ this.instance.getAsync( this.key, _.isObject( this.options.autoFetch ) ? this.options.autoFetch : {} );
+ }
+
+ // When 'relatedModel' are created or destroyed, check if it affects this relation.
+ this.listenTo( this.instance, 'destroy', this.destroy )
+ .listenTo( this.relatedCollection, 'relational:add relational:change:id', this.tryAddRelated )
+ .listenTo( this.relatedCollection, 'relational:remove', this.removeRelated );
+ }
+ };
+ // Fix inheritance :\
+ Backbone.Relation.extend = Backbone.Model.extend;
+ // Set up all inheritable **Backbone.Relation** properties and methods.
+ _.extend( Backbone.Relation.prototype, Backbone.Events, Backbone.Semaphore, {
+ options: {
+ createModels: true,
+ includeInJSON: true,
+ isAutoRelation: false,
+ autoFetch: false,
+ parse: false
+ },
+
+ instance: null,
+ key: null,
+ keyContents: null,
+ relatedModel: null,
+ relatedCollection: null,
+ reverseRelation: null,
+ related: null,
+
+ /**
+ * Check several pre-conditions.
+ * @return {Boolean} True if pre-conditions are satisfied, false if they're not.
+ */
+ checkPreconditions: function() {
+ var i = this.instance,
+ k = this.key,
+ m = this.model,
+ rm = this.relatedModel,
+ warn = Backbone.Relational.showWarnings && typeof console !== 'undefined';
+
+ if ( !m || !k || !rm ) {
+ warn && console.warn( 'Relation=%o: missing model, key or relatedModel (%o, %o, %o).', this, m, k, rm );
+ return false;
+ }
+ // Check if the type in 'model' inherits from Backbone.RelationalModel
+ if ( !( m.prototype instanceof Backbone.RelationalModel ) ) {
+ warn && console.warn( 'Relation=%o: model does not inherit from Backbone.RelationalModel (%o).', this, i );
+ return false;
+ }
+ // Check if the type in 'relatedModel' inherits from Backbone.RelationalModel
+ if ( !( rm.prototype instanceof Backbone.RelationalModel ) ) {
+ warn && console.warn( 'Relation=%o: relatedModel does not inherit from Backbone.RelationalModel (%o).', this, rm );
+ return false;
+ }
+ // Check if this is not a HasMany, and the reverse relation is HasMany as well
+ if ( this instanceof Backbone.HasMany && this.reverseRelation.type === Backbone.HasMany ) {
+ warn && console.warn( 'Relation=%o: relation is a HasMany, and the reverseRelation is HasMany as well.', this );
+ return false;
+ }
+ // Check if we're not attempting to create a relationship on a `key` that's already used.
+ if ( i && _.keys( i._relations ).length ) {
+ var existing = _.find( i._relations, function( rel ) {
+ return rel.key === k;
+ }, this );
+
+ if ( existing ) {
+ warn && console.warn( 'Cannot create relation=%o on %o for model=%o: already taken by relation=%o.',
+ this, k, i, existing );
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Set the related model(s) for this relation
+ * @param {Backbone.Model|Backbone.Collection} related
+ */
+ setRelated: function( related ) {
+ this.related = related;
+ this.instance.attributes[ this.key ] = related;
+ },
+
+ /**
+ * Determine if a relation (on a different RelationalModel) is the reverse
+ * relation of the current one.
+ * @param {Backbone.Relation} relation
+ * @return {Boolean}
+ */
+ _isReverseRelation: function( relation ) {
+ return relation.instance instanceof this.relatedModel && this.reverseRelation.key === relation.key &&
+ this.key === relation.reverseRelation.key;
+ },
+
+ /**
+ * Get the reverse relations (pointing back to 'this.key' on 'this.instance') for the currently related model(s).
+ * @param {Backbone.RelationalModel} [model] Get the reverse relations for a specific model.
+ * If not specified, 'this.related' is used.
+ * @return {Backbone.Relation[]}
+ */
+ getReverseRelations: function( model ) {
+ var reverseRelations = [];
+ // Iterate over 'model', 'this.related.models' (if this.related is a Backbone.Collection), or wrap 'this.related' in an array.
+ var models = !_.isUndefined( model ) ? [ model ] : this.related && ( this.related.models || [ this.related ] ),
+ relations = null,
+ relation = null;
+
+ for( var i = 0; i < ( models || [] ).length; i++ ) {
+ relations = models[ i ].getRelations() || [];
+
+ for( var j = 0; j < relations.length; j++ ) {
+ relation = relations[ j ];
+
+ if ( this._isReverseRelation( relation ) ) {
+ reverseRelations.push( relation );
+ }
+ }
+ }
+
+ return reverseRelations;
+ },
+
+ /**
+ * When `this.instance` is destroyed, cleanup our relations.
+ * Get reverse relation, call removeRelated on each.
+ */
+ destroy: function() {
+ this.stopListening();
+
+ if ( this instanceof Backbone.HasOne ) {
+ this.setRelated( null );
+ }
+ else if ( this instanceof Backbone.HasMany ) {
+ this.setRelated( this._prepareCollection() );
+ }
+
+ _.each( this.getReverseRelations(), function( relation ) {
+ relation.removeRelated( this.instance );
+ }, this );
+ }
+ });
+
+ Backbone.HasOne = Backbone.Relation.extend({
+ options: {
+ reverseRelation: { type: 'HasMany' }
+ },
+
+ initialize: function( opts ) {
+ this.listenTo( this.instance, 'relational:change:' + this.key, this.onChange );
+
+ var related = this.findRelated( opts );
+ this.setRelated( related );
+
+ // Notify new 'related' object of the new relation.
+ _.each( this.getReverseRelations(), function( relation ) {
+ relation.addRelated( this.instance, opts );
+ }, this );
+ },
+
+ /**
+ * Find related Models.
+ * @param {Object} [options]
+ * @return {Backbone.Model}
+ */
+ findRelated: function( options ) {
+ var related = null;
+
+ options = _.defaults( { parse: this.options.parse }, options );
+
+ if ( this.keyContents instanceof this.relatedModel ) {
+ related = this.keyContents;
+ }
+ else if ( this.keyContents || this.keyContents === 0 ) { // since 0 can be a valid `id` as well
+ var opts = _.defaults( { create: this.options.createModels }, options );
+ related = this.relatedModel.findOrCreate( this.keyContents, opts );
+ }
+
+ // Nullify `keyId` if we have a related model; in case it was already part of the relation
+ if ( related ) {
+ this.keyId = null;
+ }
+
+ return related;
+ },
+
+ /**
+ * Normalize and reduce `keyContents` to an `id`, for easier comparison
+ * @param {String|Number|Backbone.Model} keyContents
+ */
+ setKeyContents: function( keyContents ) {
+ this.keyContents = keyContents;
+ this.keyId = Backbone.Relational.store.resolveIdForItem( this.relatedModel, this.keyContents );
+ },
+
+ /**
+ * Event handler for `change:`.
+ * If the key is changed, notify old & new reverse relations and initialize the new relation.
+ */
+ onChange: function( model, attr, options ) {
+ // Don't accept recursive calls to onChange (like onChange->findRelated->findOrCreate->initializeRelations->addRelated->onChange)
+ if ( this.isLocked() ) {
+ return;
+ }
+ this.acquire();
+ options = options ? _.clone( options ) : {};
+
+ // 'options.__related' is set by 'addRelated'/'removeRelated'. If it is set, the change
+ // is the result of a call from a relation. If it's not, the change is the result of
+ // a 'set' call on this.instance.
+ var changed = _.isUndefined( options.__related ),
+ oldRelated = changed ? this.related : options.__related;
+
+ if ( changed ) {
+ this.setKeyContents( attr );
+ var related = this.findRelated( options );
+ this.setRelated( related );
+ }
+
+ // Notify old 'related' object of the terminated relation
+ if ( oldRelated && this.related !== oldRelated ) {
+ _.each( this.getReverseRelations( oldRelated ), function( relation ) {
+ relation.removeRelated( this.instance, null, options );
+ }, this );
+ }
+
+ // Notify new 'related' object of the new relation. Note we do re-apply even if this.related is oldRelated;
+ // that can be necessary for bi-directional relations if 'this.instance' was created after 'this.related'.
+ // In that case, 'this.instance' will already know 'this.related', but the reverse might not exist yet.
+ _.each( this.getReverseRelations(), function( relation ) {
+ relation.addRelated( this.instance, options );
+ }, this );
+
+ // Fire the 'change:' event if 'related' was updated
+ if ( !options.silent && this.related !== oldRelated ) {
+ var dit = this;
+ this.changed = true;
+ Backbone.Relational.eventQueue.add( function() {
+ dit.instance.trigger( 'change:' + dit.key, dit.instance, dit.related, options, true );
+ dit.changed = false;
+ });
+ }
+ this.release();
+ },
+
+ /**
+ * If a new 'this.relatedModel' appears in the 'store', try to match it to the last set 'keyContents'
+ */
+ tryAddRelated: function( model, coll, options ) {
+ if ( ( this.keyId || this.keyId === 0 ) && model.id === this.keyId ) { // since 0 can be a valid `id` as well
+ this.addRelated( model, options );
+ this.keyId = null;
+ }
+ },
+
+ addRelated: function( model, options ) {
+ // Allow 'model' to set up its relations before proceeding.
+ // (which can result in a call to 'addRelated' from a relation of 'model')
+ var dit = this;
+ model.queue( function() {
+ if ( model !== dit.related ) {
+ var oldRelated = dit.related || null;
+ dit.setRelated( model );
+ dit.onChange( dit.instance, model, _.defaults( { __related: oldRelated }, options ) );
+ }
+ });
+ },
+
+ removeRelated: function( model, coll, options ) {
+ if ( !this.related ) {
+ return;
+ }
+
+ if ( model === this.related ) {
+ var oldRelated = this.related || null;
+ this.setRelated( null );
+ this.onChange( this.instance, model, _.defaults( { __related: oldRelated }, options ) );
+ }
+ }
+ });
+
+ Backbone.HasMany = Backbone.Relation.extend({
+ collectionType: null,
+
+ options: {
+ reverseRelation: { type: 'HasOne' },
+ collectionType: Backbone.Collection,
+ collectionKey: true,
+ collectionOptions: {}
+ },
+
+ initialize: function( opts ) {
+ this.listenTo( this.instance, 'relational:change:' + this.key, this.onChange );
+
+ // Handle a custom 'collectionType'
+ this.collectionType = this.options.collectionType;
+ if ( _.isFunction( this.collectionType ) && this.collectionType !== Backbone.Collection && !( this.collectionType.prototype instanceof Backbone.Collection ) ) {
+ this.collectionType = _.result( this, 'collectionType' );
+ }
+ if ( _.isString( this.collectionType ) ) {
+ this.collectionType = Backbone.Relational.store.getObjectByName( this.collectionType );
+ }
+ if ( this.collectionType !== Backbone.Collection && !( this.collectionType.prototype instanceof Backbone.Collection ) ) {
+ throw new Error( '`collectionType` must inherit from Backbone.Collection' );
+ }
+
+ var related = this.findRelated( opts );
+ this.setRelated( related );
+ },
+
+ /**
+ * Bind events and setup collectionKeys for a collection that is to be used as the backing store for a HasMany.
+ * If no 'collection' is supplied, a new collection will be created of the specified 'collectionType' option.
+ * @param {Backbone.Collection} [collection]
+ * @return {Backbone.Collection}
+ */
+ _prepareCollection: function( collection ) {
+ if ( this.related ) {
+ this.stopListening( this.related );
+ }
+
+ if ( !collection || !( collection instanceof Backbone.Collection ) ) {
+ var options = _.isFunction( this.options.collectionOptions ) ?
+ this.options.collectionOptions( this.instance ) : this.options.collectionOptions;
+
+ collection = new this.collectionType( null, options );
+ }
+
+ collection.model = this.relatedModel;
+
+ if ( this.options.collectionKey ) {
+ var key = this.options.collectionKey === true ? this.options.reverseRelation.key : this.options.collectionKey;
+
+ if ( collection[ key ] && collection[ key ] !== this.instance ) {
+ if ( Backbone.Relational.showWarnings && typeof console !== 'undefined' ) {
+ console.warn( 'Relation=%o; collectionKey=%s already exists on collection=%o', this, key, this.options.collectionKey );
+ }
+ }
+ else if ( key ) {
+ collection[ key ] = this.instance;
+ }
+ }
+
+ this.listenTo( collection, 'relational:add', this.handleAddition )
+ .listenTo( collection, 'relational:remove', this.handleRemoval )
+ .listenTo( collection, 'relational:reset', this.handleReset );
+
+ return collection;
+ },
+
+ /**
+ * Find related Models.
+ * @param {Object} [options]
+ * @return {Backbone.Collection}
+ */
+ findRelated: function( options ) {
+ var related = null;
+
+ options = _.defaults( { parse: this.options.parse }, options );
+
+ // Replace 'this.related' by 'this.keyContents' if it is a Backbone.Collection
+ if ( this.keyContents instanceof Backbone.Collection ) {
+ this._prepareCollection( this.keyContents );
+ related = this.keyContents;
+ }
+ // Otherwise, 'this.keyContents' should be an array of related object ids.
+ // Re-use the current 'this.related' if it is a Backbone.Collection; otherwise, create a new collection.
+ else {
+ var toAdd = [];
+
+ _.each( this.keyContents, function( attributes ) {
+ var model = null;
+
+ if ( attributes instanceof this.relatedModel ) {
+ model = attributes;
+ }
+ else {
+ // If `merge` is true, update models here, instead of during update.
+ model = ( _.isObject( attributes ) && options.parse && this.relatedModel.prototype.parse ) ?
+ this.relatedModel.prototype.parse( _.clone( attributes ), options ) : attributes;
+ }
+
+ model && toAdd.push( model );
+ }, this );
+
+ if ( this.related instanceof Backbone.Collection ) {
+ related = this.related;
+ }
+ else {
+ related = this._prepareCollection();
+ }
+
+ // By now, `parse` will already have been executed just above for models if specified.
+ // Disable to prevent additional calls.
+ related.set( toAdd, _.defaults( { parse: false }, options ) );
+ }
+
+ // Remove entries from `keyIds` that were already part of the relation (and are thus 'unchanged')
+ this.keyIds = _.difference( this.keyIds, _.pluck( related.models, 'id' ) );
+
+ return related;
+ },
+
+ /**
+ * Normalize and reduce `keyContents` to a list of `ids`, for easier comparison
+ * @param {String|Number|String[]|Number[]|Backbone.Collection} keyContents
+ */
+ setKeyContents: function( keyContents ) {
+ this.keyContents = keyContents instanceof Backbone.Collection ? keyContents : null;
+ this.keyIds = [];
+
+ if ( !this.keyContents && ( keyContents || keyContents === 0 ) ) { // since 0 can be a valid `id` as well
+ // Handle cases the an API/user supplies just an Object/id instead of an Array
+ this.keyContents = _.isArray( keyContents ) ? keyContents : [ keyContents ];
+
+ _.each( this.keyContents, function( item ) {
+ var itemId = Backbone.Relational.store.resolveIdForItem( this.relatedModel, item );
+ if ( itemId || itemId === 0 ) {
+ this.keyIds.push( itemId );
+ }
+ }, this );
+ }
+ },
+
+ /**
+ * Event handler for `change:`.
+ * If the contents of the key are changed, notify old & new reverse relations and initialize the new relation.
+ */
+ onChange: function( model, attr, options ) {
+ options = options ? _.clone( options ) : {};
+ this.setKeyContents( attr );
+ this.changed = false;
+
+ var related = this.findRelated( options );
+ this.setRelated( related );
+
+ if ( !options.silent ) {
+ var dit = this;
+ Backbone.Relational.eventQueue.add( function() {
+ // The `changed` flag can be set in `handleAddition` or `handleRemoval`
+ if ( dit.changed ) {
+ dit.instance.trigger( 'change:' + dit.key, dit.instance, dit.related, options, true );
+ dit.changed = false;
+ }
+ });
+ }
+ },
+
+ /**
+ * When a model is added to a 'HasMany', trigger 'add' on 'this.instance' and notify reverse relations.
+ * (should be 'HasOne', must set 'this.instance' as their related).
+ */
+ handleAddition: function( model, coll, options ) {
+ //console.debug('handleAddition called; args=%o', arguments);
+ options = options ? _.clone( options ) : {};
+ this.changed = true;
+
+ _.each( this.getReverseRelations( model ), function( relation ) {
+ relation.addRelated( this.instance, options );
+ }, this );
+
+ // Only trigger 'add' once the newly added model is initialized (so, has its relations set up)
+ var dit = this;
+ !options.silent && Backbone.Relational.eventQueue.add( function() {
+ dit.instance.trigger( 'add:' + dit.key, model, dit.related, options );
+ });
+ },
+
+ /**
+ * When a model is removed from a 'HasMany', trigger 'remove' on 'this.instance' and notify reverse relations.
+ * (should be 'HasOne', which should be nullified)
+ */
+ handleRemoval: function( model, coll, options ) {
+ //console.debug('handleRemoval called; args=%o', arguments);
+ options = options ? _.clone( options ) : {};
+ this.changed = true;
+
+ _.each( this.getReverseRelations( model ), function( relation ) {
+ relation.removeRelated( this.instance, null, options );
+ }, this );
+
+ var dit = this;
+ !options.silent && Backbone.Relational.eventQueue.add( function() {
+ dit.instance.trigger( 'remove:' + dit.key, model, dit.related, options );
+ });
+ },
+
+ handleReset: function( coll, options ) {
+ var dit = this;
+ options = options ? _.clone( options ) : {};
+ !options.silent && Backbone.Relational.eventQueue.add( function() {
+ dit.instance.trigger( 'reset:' + dit.key, dit.related, options );
+ });
+ },
+
+ tryAddRelated: function( model, coll, options ) {
+ var item = _.contains( this.keyIds, model.id );
+
+ if ( item ) {
+ this.addRelated( model, options );
+ this.keyIds = _.without( this.keyIds, model.id );
+ }
+ },
+
+ addRelated: function( model, options ) {
+ // Allow 'model' to set up its relations before proceeding.
+ // (which can result in a call to 'addRelated' from a relation of 'model')
+ var dit = this;
+ model.queue( function() {
+ if ( dit.related && !dit.related.get( model ) ) {
+ dit.related.add( model, _.defaults( { parse: false }, options ) );
+ }
+ });
+ },
+
+ removeRelated: function( model, coll, options ) {
+ if ( this.related.get( model ) ) {
+ this.related.remove( model, options );
+ }
+ }
+ });
+
+ /**
+ * A type of Backbone.Model that also maintains relations to other models and collections.
+ * New events when compared to the original:
+ * - 'add:' (model, related collection, options)
+ * - 'remove:' (model, related collection, options)
+ * - 'change:' (model, related model or collection, options)
+ */
+ Backbone.RelationalModel = Backbone.Model.extend({
+ relations: null, // Relation descriptions on the prototype
+ _relations: null, // Relation instances
+ _isInitialized: false,
+ _deferProcessing: false,
+ _queue: null,
+ _attributeChangeFired: false, // Keeps track of `change` event firing under some conditions (like nested `set`s)
+
+ subModelTypeAttribute: 'type',
+ subModelTypes: null,
+
+ constructor: function( attributes, options ) {
+ // Nasty hack, for cases like 'model.get( ).add( item )'.
+ // Defer 'processQueue', so that when 'Relation.createModels' is used we trigger 'HasMany'
+ // collection events only after the model is really fully set up.
+ // Example: event for "p.on( 'add:jobs' )" -> "p.get('jobs').add( { company: c.id, person: p.id } )".
+ if ( options && options.collection ) {
+ var dit = this,
+ collection = this.collection = options.collection;
+
+ // Prevent `collection` from cascading down to nested models; they shouldn't go into this `if` clause.
+ delete options.collection;
+
+ this._deferProcessing = true;
+
+ var processQueue = function( model ) {
+ if ( model === dit ) {
+ dit._deferProcessing = false;
+ dit.processQueue();
+ collection.off( 'relational:add', processQueue );
+ }
+ };
+ collection.on( 'relational:add', processQueue );
+
+ // So we do process the queue eventually, regardless of whether this model actually gets added to 'options.collection'.
+ _.defer( function() {
+ processQueue( dit );
+ });
+ }
+
+ Backbone.Relational.store.processOrphanRelations();
+ Backbone.Relational.store.listenTo( this, 'relational:unregister', Backbone.Relational.store.unregister );
+
+ this._queue = new Backbone.BlockingQueue();
+ this._queue.block();
+ Backbone.Relational.eventQueue.block();
+
+ try {
+ Backbone.Model.apply( this, arguments );
+ }
+ finally {
+ // Try to run the global queue holding external events
+ Backbone.Relational.eventQueue.unblock();
+ }
+ },
+
+ /**
+ * Override 'trigger' to queue 'change' and 'change:*' events
+ */
+ trigger: function( eventName ) {
+ if ( eventName.length > 5 && eventName.indexOf( 'change' ) === 0 ) {
+ var dit = this,
+ args = arguments;
+
+ if ( !Backbone.Relational.eventQueue.isBlocked() ) {
+ // If we're not in a more complicated nested scenario, fire the change event right away
+ Backbone.Model.prototype.trigger.apply( dit, args );
+ }
+ else {
+ Backbone.Relational.eventQueue.add( function() {
+ // Determine if the `change` event is still valid, now that all relations are populated
+ var changed = true;
+ if ( eventName === 'change' ) {
+ // `hasChanged` may have gotten reset by nested calls to `set`.
+ changed = dit.hasChanged() || dit._attributeChangeFired;
+ dit._attributeChangeFired = false;
+ }
+ else {
+ var attr = eventName.slice( 7 ),
+ rel = dit.getRelation( attr );
+
+ if ( rel ) {
+ // If `attr` is a relation, `change:attr` get triggered from `Relation.onChange`.
+ // These take precedence over `change:attr` events triggered by `Model.set`.
+ // The relation sets a fourth attribute to `true`. If this attribute is present,
+ // continue triggering this event; otherwise, it's from `Model.set` and should be stopped.
+ changed = ( args[ 4 ] === true );
+
+ // If this event was triggered by a relation, set the right value in `this.changed`
+ // (a Collection or Model instead of raw data).
+ if ( changed ) {
+ dit.changed[ attr ] = args[ 2 ];
+ }
+ // Otherwise, this event is from `Model.set`. If the relation doesn't report a change,
+ // remove attr from `dit.changed` so `hasChanged` doesn't take it into account.
+ else if ( !rel.changed ) {
+ delete dit.changed[ attr ];
+ }
+ }
+ else if ( changed ) {
+ dit._attributeChangeFired = true;
+ }
+ }
+
+ changed && Backbone.Model.prototype.trigger.apply( dit, args );
+ });
+ }
+ }
+ else if ( eventName === 'destroy' ) {
+ Backbone.Model.prototype.trigger.apply( this, arguments );
+ Backbone.Relational.store.unregister( this );
+ }
+ else {
+ Backbone.Model.prototype.trigger.apply( this, arguments );
+ }
+
+ return this;
+ },
+
+ /**
+ * Initialize Relations present in this.relations; determine the type (HasOne/HasMany), then creates a new instance.
+ * Invoked in the first call so 'set' (which is made from the Backbone.Model constructor).
+ */
+ initializeRelations: function( options ) {
+ this.acquire(); // Setting up relations often also involve calls to 'set', and we only want to enter this function once
+ this._relations = {};
+
+ _.each( this.relations || [], function( rel ) {
+ Backbone.Relational.store.initializeRelation( this, rel, options );
+ }, this );
+
+ this._isInitialized = true;
+ this.release();
+ this.processQueue();
+ },
+
+ /**
+ * When new values are set, notify this model's relations (also if options.silent is set).
+ * (called from `set`; Relation.setRelated locks this model before calling 'set' on it to prevent loops)
+ * @param {Object} [changedAttrs]
+ * @param {Object} [options]
+ */
+ updateRelations: function( changedAttrs, options ) {
+ if ( this._isInitialized && !this.isLocked() ) {
+ _.each( this._relations, function( rel ) {
+ if ( !changedAttrs || ( rel.keySource in changedAttrs || rel.key in changedAttrs ) ) {
+ // Fetch data in `rel.keySource` if data got set in there, or `rel.key` otherwise
+ var value = this.attributes[ rel.keySource ] || this.attributes[ rel.key ],
+ attr = changedAttrs && ( changedAttrs[ rel.keySource ] || changedAttrs[ rel.key ] );
+
+ // Update a relation if its value differs from this model's attributes, or it's been explicitly nullified.
+ // Which can also happen before the originally intended related model has been found (`val` is null).
+ if ( rel.related !== value || ( value === null && attr === null ) ) {
+ this.trigger( 'relational:change:' + rel.key, this, value, options || {} );
+ }
+ }
+
+ // Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
+ if ( rel.keySource !== rel.key ) {
+ delete this.attributes[ rel.keySource ];
+ }
+ }, this );
+ }
+ },
+
+ /**
+ * Either add to the queue (if we're not initialized yet), or execute right away.
+ */
+ queue: function( func ) {
+ this._queue.add( func );
+ },
+
+ /**
+ * Process _queue
+ */
+ processQueue: function() {
+ if ( this._isInitialized && !this._deferProcessing && this._queue.isBlocked() ) {
+ this._queue.unblock();
+ }
+ },
+
+ /**
+ * Get a specific relation.
+ * @param {string} attr The relation key to look for.
+ * @return {Backbone.Relation} An instance of 'Backbone.Relation', if a relation was found for 'attr', or null.
+ */
+ getRelation: function( attr ) {
+ return this._relations[ attr ];
+ },
+
+ /**
+ * Get all of the created relations.
+ * @return {Backbone.Relation[]}
+ */
+ getRelations: function() {
+ return _.values( this._relations );
+ },
+
+
+ /**
+ * Get a list of ids that will be fetched on a call to `getAsync`.
+ * @param {string|Backbone.Relation} attr The relation key to fetch models for.
+ * @param [refresh=false] Add ids for models that are already in the relation, refreshing them?
+ * @return {Array} An array of ids that need to be fetched.
+ */
+ getIdsToFetch: function( attr, refresh ) {
+ var rel = attr instanceof Backbone.Relation ? attr : this.getRelation( attr ),
+ ids = rel ? ( rel.keyIds && rel.keyIds.slice( 0 ) ) || ( ( rel.keyId || rel.keyId === 0 ) ? [ rel.keyId ] : [] ) : [];
+
+ // On `refresh`, add the ids for current models in the relation to `idsToFetch`
+ if ( refresh ) {
+ var models = rel.related && ( rel.related.models || [ rel.related ] );
+ _.each( models, function( model ) {
+ if ( model.id || model.id === 0 ) {
+ ids.push( model.id );
+ }
+ });
+ }
+
+ return ids;
+ },
+
+ /**
+ * Get related objects. Returns a single promise, which can either resolve immediately (if the related model[s])
+ * are already present locally, or after fetching the contents of the requested attribute.
+ * @param {string} attr The relation key to fetch models for.
+ * @param {Object} [options] Options for 'Backbone.Model.fetch' and 'Backbone.sync'.
+ * @param {Boolean} [options.refresh=false] Fetch existing models from the server as well (in order to update them).
+ * @return {jQuery.Deferred} A jQuery promise object. When resolved, its `done` callback will be called with
+ * contents of `attr`.
+ */
+ getAsync: function( attr, options ) {
+ // Set default `options` for fetch
+ options = _.extend( { add: true, remove: false, refresh: false }, options );
+
+ var dit = this,
+ requests = [],
+ rel = this.getRelation( attr ),
+ idsToFetch = rel && this.getIdsToFetch( rel, options.refresh ),
+ coll = rel.related instanceof Backbone.Collection ? rel.related : rel.relatedCollection;
+
+ if ( idsToFetch && idsToFetch.length ) {
+ var models = [],
+ createdModels = [],
+ setUrl,
+ createModels = function() {
+ // Find (or create) a model for each one that is to be fetched
+ models = _.map( idsToFetch, function( id ) {
+ var model = rel.relatedModel.findModel( id );
+
+ if ( !model ) {
+ var attrs = {};
+ attrs[ rel.relatedModel.prototype.idAttribute ] = id;
+ model = rel.relatedModel.findOrCreate( attrs, options );
+ createdModels.push( model );
+ }
+
+ return model;
+ }, this );
+ };
+
+ // Try if the 'collection' can provide a url to fetch a set of models in one request.
+ // This assumes that when 'Backbone.Collection.url' is a function, it can handle building of set urls.
+ // To make sure it can, test if the url we got by supplying a list of models to fetch is different from
+ // the one supplied for the default fetch action (without args to 'url').
+ if ( coll instanceof Backbone.Collection && _.isFunction( coll.url ) ) {
+ var defaultUrl = coll.url();
+ setUrl = coll.url( idsToFetch );
+
+ if ( setUrl === defaultUrl ) {
+ createModels();
+ setUrl = coll.url( models );
+
+ if ( setUrl === defaultUrl ) {
+ setUrl = null;
+ }
+ }
+ }
+
+ if ( setUrl ) {
+ // Do a single request to fetch all models
+ var opts = _.defaults(
+ {
+ error: function() {
+ _.each( createdModels, function( model ) {
+ model.trigger( 'destroy', model, model.collection, options );
+ });
+
+ options.error && options.error.apply( models, arguments );
+ },
+ url: setUrl
+ },
+ options
+ );
+
+ requests = [ coll.fetch( opts ) ];
+ }
+ else {
+ // Make a request per model to fetch
+ if ( !models.length ) {
+ createModels();
+ }
+
+ requests = _.map( models, function( model ) {
+ var opts = _.defaults(
+ {
+ error: function() {
+ if ( _.contains( createdModels, model ) ) {
+ model.trigger( 'destroy', model, model.collection, options );
+ }
+ options.error && options.error.apply( models, arguments );
+ }
+ },
+ options
+ );
+ return model.fetch( opts );
+ }, this );
+ }
+ }
+
+ return this.deferArray(requests).then(
+ function() {
+ return Backbone.Model.prototype.get.call( dit, attr );
+ }
+ );
+ },
+
+ deferArray: function(deferArray) {
+ return Backbone.$.when.apply(null, deferArray);
+ },
+
+ set: function( key, value, options ) {
+ Backbone.Relational.eventQueue.block();
+
+ // Duplicate backbone's behavior to allow separate key/value parameters, instead of a single 'attributes' object
+ var attributes,
+ result;
+
+ if ( _.isObject( key ) || key == null ) {
+ attributes = key;
+ options = value;
+ }
+ else {
+ attributes = {};
+ attributes[ key ] = value;
+ }
+
+ try {
+ var id = this.id,
+ newId = attributes && this.idAttribute in attributes && attributes[ this.idAttribute ];
+
+ // Check if we're not setting a duplicate id before actually calling `set`.
+ Backbone.Relational.store.checkId( this, newId );
+
+ result = Backbone.Model.prototype.set.apply( this, arguments );
+
+ // Ideal place to set up relations, if this is the first time we're here for this model
+ if ( !this._isInitialized && !this.isLocked() ) {
+ this.constructor.initializeModelHierarchy();
+
+ // Only register models that have an id. A model will be registered when/if it gets an id later on.
+ if ( newId || newId === 0 ) {
+ Backbone.Relational.store.register( this );
+ }
+
+ this.initializeRelations( options );
+ }
+ // The store should know about an `id` update asap
+ else if ( newId && newId !== id ) {
+ Backbone.Relational.store.update( this );
+ }
+
+ if ( attributes ) {
+ this.updateRelations( attributes, options );
+ }
+ }
+ finally {
+ // Try to run the global queue holding external events
+ Backbone.Relational.eventQueue.unblock();
+ }
+
+ return result;
+ },
+
+ clone: function() {
+ var attributes = _.clone( this.attributes );
+ if ( !_.isUndefined( attributes[ this.idAttribute ] ) ) {
+ attributes[ this.idAttribute ] = null;
+ }
+
+ _.each( this.getRelations(), function( rel ) {
+ delete attributes[ rel.key ];
+ });
+
+ return new this.constructor( attributes );
+ },
+
+ /**
+ * Convert relations to JSON, omits them when required
+ */
+ toJSON: function( options ) {
+ // If this Model has already been fully serialized in this branch once, return to avoid loops
+ if ( this.isLocked() ) {
+ return this.id;
+ }
+
+ this.acquire();
+ var json = Backbone.Model.prototype.toJSON.call( this, options );
+
+ if ( this.constructor._superModel && !( this.constructor._subModelTypeAttribute in json ) ) {
+ json[ this.constructor._subModelTypeAttribute ] = this.constructor._subModelTypeValue;
+ }
+
+ _.each( this._relations, function( rel ) {
+ var related = json[ rel.key ],
+ includeInJSON = rel.options.includeInJSON,
+ value = null;
+
+ if ( includeInJSON === true ) {
+ if ( related && _.isFunction( related.toJSON ) ) {
+ value = related.toJSON( options );
+ }
+ }
+ else if ( _.isString( includeInJSON ) ) {
+ if ( related instanceof Backbone.Collection ) {
+ value = related.pluck( includeInJSON );
+ }
+ else if ( related instanceof Backbone.Model ) {
+ value = related.get( includeInJSON );
+ }
+
+ // Add ids for 'unfound' models if includeInJSON is equal to (only) the relatedModel's `idAttribute`
+ if ( includeInJSON === rel.relatedModel.prototype.idAttribute ) {
+ if ( rel instanceof Backbone.HasMany ) {
+ value = value.concat( rel.keyIds );
+ }
+ else if ( rel instanceof Backbone.HasOne ) {
+ value = value || rel.keyId;
+
+ if ( !value && !_.isObject( rel.keyContents ) ) {
+ value = rel.keyContents || null;
+ }
+ }
+ }
+ }
+ else if ( _.isArray( includeInJSON ) ) {
+ if ( related instanceof Backbone.Collection ) {
+ value = [];
+ related.each( function( model ) {
+ var curJson = {};
+ _.each( includeInJSON, function( key ) {
+ curJson[ key ] = model.get( key );
+ });
+ value.push( curJson );
+ });
+ }
+ else if ( related instanceof Backbone.Model ) {
+ value = {};
+ _.each( includeInJSON, function( key ) {
+ value[ key ] = related.get( key );
+ });
+ }
+ }
+ else {
+ delete json[ rel.key ];
+ }
+
+ // In case of `wait: true`, Backbone will simply push whatever's passed into `save` into attributes.
+ // We'll want to get this information into the JSON, even if it doesn't conform to our normal
+ // expectations of what's contained in it (no model/collection for a relation, etc).
+ if ( value === null && options && options.wait ) {
+ value = related;
+ }
+
+ if ( includeInJSON ) {
+ json[ rel.keyDestination ] = value;
+ }
+
+ if ( rel.keyDestination !== rel.key ) {
+ delete json[ rel.key ];
+ }
+ });
+
+ this.release();
+ return json;
+ }
+ },
+ {
+ /**
+ *
+ * @param superModel
+ * @returns {Backbone.RelationalModel.constructor}
+ */
+ setup: function( superModel ) {
+ // We don't want to share a relations array with a parent, as this will cause problems with reverse
+ // relations. Since `relations` may also be a property or function, only use slice if we have an array.
+ this.prototype.relations = ( this.prototype.relations || [] ).slice( 0 );
+
+ this._subModels = {};
+ this._superModel = null;
+
+ // If this model has 'subModelTypes' itself, remember them in the store
+ if ( this.prototype.hasOwnProperty( 'subModelTypes' ) ) {
+ Backbone.Relational.store.addSubModels( this.prototype.subModelTypes, this );
+ }
+ // The 'subModelTypes' property should not be inherited, so reset it.
+ else {
+ this.prototype.subModelTypes = null;
+ }
+
+ // Initialize all reverseRelations that belong to this new model.
+ _.each( this.prototype.relations || [], function( rel ) {
+ if ( !rel.model ) {
+ rel.model = this;
+ }
+
+ if ( rel.reverseRelation && rel.model === this ) {
+ var preInitialize = true;
+ if ( _.isString( rel.relatedModel ) ) {
+ /**
+ * The related model might not be defined for two reasons
+ * 1. it is related to itself
+ * 2. it never gets defined, e.g. a typo
+ * 3. the model hasn't been defined yet, but will be later
+ * In neither of these cases do we need to pre-initialize reverse relations.
+ * However, for 3. (which is, to us, indistinguishable from 2.), we do need to attempt
+ * setting up this relation again later, in case the related model is defined later.
+ */
+ var relatedModel = Backbone.Relational.store.getObjectByName( rel.relatedModel );
+ preInitialize = relatedModel && ( relatedModel.prototype instanceof Backbone.RelationalModel );
+ }
+
+ if ( preInitialize ) {
+ Backbone.Relational.store.initializeRelation( null, rel );
+ }
+ else if ( _.isString( rel.relatedModel ) ) {
+ Backbone.Relational.store.addOrphanRelation( rel );
+ }
+ }
+ }, this );
+
+ return this;
+ },
+
+ /**
+ * Create a 'Backbone.Model' instance based on 'attributes'.
+ * @param {Object} attributes
+ * @param {Object} [options]
+ * @return {Backbone.Model}
+ */
+ build: function( attributes, options ) {
+ // 'build' is a possible entrypoint; it's possible no model hierarchy has been determined yet.
+ this.initializeModelHierarchy();
+
+ // Determine what type of (sub)model should be built if applicable.
+ var model = this._findSubModelType( this, attributes ) || this;
+
+ return new model( attributes, options );
+ },
+
+ /**
+ * Determines what type of (sub)model should be built if applicable.
+ * Looks up the proper subModelType in 'this._subModels', recursing into
+ * types until a match is found. Returns the applicable 'Backbone.Model'
+ * or null if no match is found.
+ * @param {Backbone.Model} type
+ * @param {Object} attributes
+ * @return {Backbone.Model}
+ */
+ _findSubModelType: function( type, attributes ) {
+ if ( type._subModels && type.prototype.subModelTypeAttribute in attributes ) {
+ var subModelTypeAttribute = attributes[ type.prototype.subModelTypeAttribute ];
+ var subModelType = type._subModels[ subModelTypeAttribute ];
+ if ( subModelType ) {
+ return subModelType;
+ }
+ else {
+ // Recurse into subModelTypes to find a match
+ for ( subModelTypeAttribute in type._subModels ) {
+ subModelType = this._findSubModelType( type._subModels[ subModelTypeAttribute ], attributes );
+ if ( subModelType ) {
+ return subModelType;
+ }
+ }
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ *
+ */
+ initializeModelHierarchy: function() {
+ // Inherit any relations that have been defined in the parent model.
+ this.inheritRelations();
+
+ // If we came here through 'build' for a model that has 'subModelTypes' then try to initialize the ones that
+ // haven't been resolved yet.
+ if ( this.prototype.subModelTypes ) {
+ var resolvedSubModels = _.keys( this._subModels );
+ var unresolvedSubModels = _.omit( this.prototype.subModelTypes, resolvedSubModels );
+ _.each( unresolvedSubModels, function( subModelTypeName ) {
+ var subModelType = Backbone.Relational.store.getObjectByName( subModelTypeName );
+ subModelType && subModelType.initializeModelHierarchy();
+ });
+ }
+ },
+
+ inheritRelations: function() {
+ // Bail out if we've been here before.
+ if ( !_.isUndefined( this._superModel ) && !_.isNull( this._superModel ) ) {
+ return;
+ }
+ // Try to initialize the _superModel.
+ Backbone.Relational.store.setupSuperModel( this );
+
+ // If a superModel has been found, copy relations from the _superModel if they haven't been inherited automatically
+ // (due to a redefinition of 'relations').
+ if ( this._superModel ) {
+ // The _superModel needs a chance to initialize its own inherited relations before we attempt to inherit relations
+ // from the _superModel. You don't want to call 'initializeModelHierarchy' because that could cause sub-models of
+ // this class to inherit their relations before this class has had chance to inherit it's relations.
+ this._superModel.inheritRelations();
+ if ( this._superModel.prototype.relations ) {
+ // Find relations that exist on the '_superModel', but not yet on this model.
+ var inheritedRelations = _.filter( this._superModel.prototype.relations || [], function( superRel ) {
+ return !_.any( this.prototype.relations || [], function( rel ) {
+ return superRel.relatedModel === rel.relatedModel && superRel.key === rel.key;
+ }, this );
+ }, this );
+
+ this.prototype.relations = inheritedRelations.concat( this.prototype.relations );
+ }
+ }
+ // Otherwise, make sure we don't get here again for this type by making '_superModel' false so we fail the
+ // isUndefined/isNull check next time.
+ else {
+ this._superModel = false;
+ }
+ },
+
+ /**
+ * Find an instance of `this` type in 'Backbone.Relational.store'.
+ * A new model is created if no matching model is found, `attributes` is an object, and `options.create` is true.
+ * - If `attributes` is a string or a number, `findOrCreate` will query the `store` and return a model if found.
+ * - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.merge` is `false`.
+ * @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model.
+ * @param {Object} [options]
+ * @param {Boolean} [options.create=true]
+ * @param {Boolean} [options.merge=true]
+ * @param {Boolean} [options.parse=false]
+ * @return {Backbone.RelationalModel}
+ */
+ findOrCreate: function( attributes, options ) {
+ options || ( options = {} );
+ var parsedAttributes = ( _.isObject( attributes ) && options.parse && this.prototype.parse ) ?
+ this.prototype.parse( _.clone( attributes ), options ) : attributes;
+
+ // If specified, use a custom `find` function to match up existing models to the given attributes.
+ // Otherwise, try to find an instance of 'this' model type in the store
+ var model = this.findModel( parsedAttributes );
+
+ // If we found an instance, update it with the data in 'item' (unless 'options.merge' is false).
+ // If not, create an instance (unless 'options.create' is false).
+ if ( _.isObject( attributes ) ) {
+ if ( model && options.merge !== false ) {
+ // Make sure `options.collection` and `options.url` doesn't cascade to nested models
+ delete options.collection;
+ delete options.url;
+
+ model.set( parsedAttributes, options );
+ }
+ else if ( !model && options.create !== false ) {
+ model = this.build( parsedAttributes, _.defaults( { parse: false }, options ) );
+ }
+ }
+
+ return model;
+ },
+
+ /**
+ * Find an instance of `this` type in 'Backbone.Relational.store'.
+ * - If `attributes` is a string or a number, `find` will query the `store` and return a model if found.
+ * - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.merge` is `false`.
+ * @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model.
+ * @param {Object} [options]
+ * @param {Boolean} [options.merge=true]
+ * @param {Boolean} [options.parse=false]
+ * @return {Backbone.RelationalModel}
+ */
+ find: function( attributes, options ) {
+ options || ( options = {} );
+ options.create = false;
+ return this.findOrCreate( attributes, options );
+ },
+
+ /**
+ * A hook to override the matching when updating (or creating) a model.
+ * The default implementation is to look up the model by id in the store.
+ * @param {Object} attributes
+ * @returns {Backbone.RelationalModel}
+ */
+ findModel: function( attributes ) {
+ return Backbone.Relational.store.find( this, attributes );
+ }
+ });
+ _.extend( Backbone.RelationalModel.prototype, Backbone.Semaphore );
+
+ /**
+ * Override Backbone.Collection._prepareModel, so objects will be built using the correct type
+ * if the collection.model has subModels.
+ * Attempts to find a model for `attrs` in Backbone.store through `findOrCreate`
+ * (which sets the new properties on it if found), or instantiates a new model.
+ */
+ Backbone.Collection.prototype.__prepareModel = Backbone.Collection.prototype._prepareModel;
+ Backbone.Collection.prototype._prepareModel = function( attrs, options ) {
+ var model;
+
+ if ( attrs instanceof Backbone.Model ) {
+ if ( !attrs.collection ) {
+ attrs.collection = this;
+ }
+ model = attrs;
+ }
+ else {
+ options = options ? _.clone( options ) : {};
+ options.collection = this;
+
+ if ( typeof this.model.findOrCreate !== 'undefined' ) {
+ model = this.model.findOrCreate( attrs, options );
+ }
+ else {
+ model = new this.model( attrs, options );
+ }
+
+ if ( model && model.validationError ) {
+ this.trigger( 'invalid', this, attrs, options );
+ model = false;
+ }
+ }
+
+ return model;
+ };
+
+
+ /**
+ * Override Backbone.Collection.set, so we'll create objects from attributes where required,
+ * and update the existing models. Also, trigger 'relational:add'.
+ */
+ var set = Backbone.Collection.prototype.__set = Backbone.Collection.prototype.set;
+ Backbone.Collection.prototype.set = function( models, options ) {
+ // Short-circuit if this Collection doesn't hold RelationalModels
+ if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
+ return set.call( this, models, options );
+ }
+
+ if ( options && options.parse ) {
+ models = this.parse( models, options );
+ }
+
+ var singular = !_.isArray( models ),
+ newModels = [],
+ toAdd = [],
+ model = null;
+
+ models = singular ? ( models ? [ models ] : [] ) : _.clone( models );
+
+ //console.debug( 'calling add on coll=%o; model=%o, options=%o', this, models, options );
+ for ( var i = 0; i < models.length; i++ ) {
+ model = models[i];
+ if ( !( model instanceof Backbone.Model ) ) {
+ model = Backbone.Collection.prototype._prepareModel.call( this, model, options );
+ }
+ if ( model ) {
+ toAdd.push( model );
+ if ( !( this.get( model ) || this.get( model.cid ) ) ) {
+ newModels.push( model );
+ }
+ // If we arrive in `add` while performing a `set` (after a create, so the model gains an `id`),
+ // we may get here before `_onModelEvent` has had the chance to update `_byId`.
+ else if ( model.id !== null && model.id !== undefined ) {
+ this._byId[ model.id ] = model;
+ }
+ }
+ }
+
+ // Add 'models' in a single batch, so the original add will only be called once (and thus 'sort', etc).
+ // If `parse` was specified, the collection and contained models have been parsed now.
+ toAdd = singular ? ( toAdd.length ? toAdd[ 0 ] : null ) : toAdd;
+ var result = set.call( this, toAdd, _.defaults( { merge: false, parse: false }, options ) );
+
+ for ( i = 0; i < newModels.length; i++ ) {
+ model = newModels[i];
+ // Fire a `relational:add` event for any model in `newModels` that has actually been added to the collection.
+ if ( this.get( model ) || this.get( model.cid ) ) {
+ this.trigger( 'relational:add', model, this, options );
+ }
+ }
+
+ return result;
+ };
+
+ /**
+ * Override 'Backbone.Collection._removeModels' to trigger 'relational:remove'.
+ */
+ var _removeModels = Backbone.Collection.prototype.___removeModels = Backbone.Collection.prototype._removeModels;
+ Backbone.Collection.prototype._removeModels = function( models, options ) {
+ // Short-circuit if this Collection doesn't hold RelationalModels
+ if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
+ return _removeModels.call( this, models, options );
+ }
+
+ var toRemove = [];
+
+ //console.debug('calling remove on coll=%o; models=%o, options=%o', this, models, options );
+ _.each( models, function( model ) {
+ model = this.get( model ) || ( model && this.get( model.cid ) );
+ model && toRemove.push( model );
+ }, this );
+
+ var result = _removeModels.call( this, toRemove, options );
+
+ _.each( toRemove, function( model ) {
+ this.trigger( 'relational:remove', model, this, options );
+ }, this );
+
+ return result;
+ };
+
+ /**
+ * Override 'Backbone.Collection.reset' to trigger 'relational:reset'.
+ */
+ var reset = Backbone.Collection.prototype.__reset = Backbone.Collection.prototype.reset;
+ Backbone.Collection.prototype.reset = function( models, options ) {
+ options = _.extend( { merge: true }, options );
+ var result = reset.call( this, models, options );
+
+ if ( this.model.prototype instanceof Backbone.RelationalModel ) {
+ this.trigger( 'relational:reset', this, options );
+ }
+
+ return result;
+ };
+
+ /**
+ * Override 'Backbone.Collection.sort' to trigger 'relational:reset'.
+ */
+ var sort = Backbone.Collection.prototype.__sort = Backbone.Collection.prototype.sort;
+ Backbone.Collection.prototype.sort = function( options ) {
+ var result = sort.call( this, options );
+
+ if ( this.model.prototype instanceof Backbone.RelationalModel ) {
+ this.trigger( 'relational:reset', this, options );
+ }
+
+ return result;
+ };
+
+ /**
+ * Override 'Backbone.Collection.trigger' so 'add', 'remove' and 'reset' events are queued until relations
+ * are ready.
+ */
+ var trigger = Backbone.Collection.prototype.__trigger = Backbone.Collection.prototype.trigger;
+ Backbone.Collection.prototype.trigger = function( eventName ) {
+ // Short-circuit if this Collection doesn't hold RelationalModels
+ if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
+ return trigger.apply( this, arguments );
+ }
+
+ if ( eventName === 'add' || eventName === 'remove' || eventName === 'reset' || eventName === 'sort' ) {
+ var dit = this,
+ args = arguments;
+
+ if ( _.isObject( args[ 3 ] ) ) {
+ args = _.toArray( args );
+ // the fourth argument is the option object.
+ // we need to clone it, as it could be modified while we wait on the eventQueue to be unblocked
+ args[ 3 ] = _.clone( args[ 3 ] );
+ }
+
+ Backbone.Relational.eventQueue.add( function() {
+ trigger.apply( dit, args );
+ });
+ }
+ else {
+ trigger.apply( this, arguments );
+ }
+
+ return this;
+ };
+
+ // Override .extend() to automatically call .setup()
+ Backbone.RelationalModel.extend = function( protoProps, classProps ) {
+ var child = Backbone.Model.extend.call( this, protoProps, classProps );
+
+ child.setup( this );
+
+ return child;
+ };
+}));
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/backbone/backbone.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/backbone/backbone.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1894 @@
+// Backbone.js 1.2.3
+
+// (c) 2010-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+
+(function(factory) {
+
+ // Establish the root object, `window` (`self`) in the browser, or `global` on the server.
+ // We use `self` instead of `window` for `WebWorker` support.
+ var root = (typeof self == 'object' && self.self == self && self) ||
+ (typeof global == 'object' && global.global == global && global);
+
+ // Set up Backbone appropriately for the environment. Start with AMD.
+ if (typeof define === 'function' && define.amd) {
+ define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
+ // Export global even in AMD case in case this script is loaded with
+ // others that may still expect a global Backbone.
+ root.Backbone = factory(root, exports, _, $);
+ });
+
+ // Next for Node.js or CommonJS. jQuery may not be needed as a module.
+ } else if (typeof exports !== 'undefined') {
+ var _ = require('underscore'), $;
+ try { $ = require('jquery'); } catch(e) {}
+ factory(root, exports, _, $);
+
+ // Finally, as a browser global.
+ } else {
+ root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
+ }
+
+}(function(root, Backbone, _, $) {
+
+ // Initial Setup
+ // -------------
+
+ // Save the previous value of the `Backbone` variable, so that it can be
+ // restored later on, if `noConflict` is used.
+ var previousBackbone = root.Backbone;
+
+ // Create a local reference to a common array method we'll want to use later.
+ var slice = Array.prototype.slice;
+
+ // Current version of the library. Keep in sync with `package.json`.
+ Backbone.VERSION = '1.2.3';
+
+ // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
+ // the `$` variable.
+ Backbone.$ = $;
+
+ // 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 `"PATCH"`, `"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 ... this 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;
+
+ // Proxy Backbone class methods to Underscore functions, wrapping the model's
+ // `attributes` object or collection's `models` array behind the scenes.
+ //
+ // collection.filter(function(model) { return model.get('age') > 10 });
+ // collection.each(this.addView);
+ //
+ // `Function#apply` can be slow so we use the method's arg count, if we know it.
+ var addMethod = function(length, method, attribute) {
+ switch (length) {
+ case 1: return function() {
+ return _[method](this[attribute]);
+ };
+ case 2: return function(value) {
+ return _[method](this[attribute], value);
+ };
+ case 3: return function(iteratee, context) {
+ return _[method](this[attribute], cb(iteratee, this), context);
+ };
+ case 4: return function(iteratee, defaultVal, context) {
+ return _[method](this[attribute], cb(iteratee, this), defaultVal, context);
+ };
+ default: return function() {
+ var args = slice.call(arguments);
+ args.unshift(this[attribute]);
+ return _[method].apply(_, args);
+ };
+ }
+ };
+ var addUnderscoreMethods = function(Class, methods, attribute) {
+ _.each(methods, function(length, method) {
+ if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
+ });
+ };
+
+ // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
+ var cb = function(iteratee, instance) {
+ if (_.isFunction(iteratee)) return iteratee;
+ if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
+ if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
+ return iteratee;
+ };
+ var modelMatcher = function(attrs) {
+ var matcher = _.matches(attrs);
+ return function(model) {
+ return matcher(model.attributes);
+ };
+ };
+
+ // Backbone.Events
+ // ---------------
+
+ // A module that can be mixed in to *any object* in order to provide it with
+ // a custom event channel. You may bind a callback to an event with `on` or
+ // remove with `off`; `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 = {};
+
+ // Regular expression used to split event strings.
+ var eventSplitter = /\s+/;
+
+ // Iterates over the standard `event, callback` (as well as the fancy multiple
+ // space-separated events `"change blur", callback` and jQuery-style event
+ // maps `{event: callback}`).
+ var eventsApi = function(iteratee, events, name, callback, opts) {
+ var i = 0, names;
+ if (name && typeof name === 'object') {
+ // Handle event maps.
+ if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
+ for (names = _.keys(name); i < names.length ; i++) {
+ events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
+ }
+ } else if (name && eventSplitter.test(name)) {
+ // Handle space separated event names by delegating them individually.
+ for (names = name.split(eventSplitter); i < names.length; i++) {
+ events = iteratee(events, names[i], callback, opts);
+ }
+ } else {
+ // Finally, standard events.
+ events = iteratee(events, name, callback, opts);
+ }
+ return events;
+ };
+
+ // Bind an event to a `callback` function. Passing `"all"` will bind
+ // the callback to all events fired.
+ Events.on = function(name, callback, context) {
+ return internalOn(this, name, callback, context);
+ };
+
+ // Guard the `listening` argument from the public API.
+ var internalOn = function(obj, name, callback, context, listening) {
+ obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
+ context: context,
+ ctx: obj,
+ listening: listening
+ });
+
+ if (listening) {
+ var listeners = obj._listeners || (obj._listeners = {});
+ listeners[listening.id] = listening;
+ }
+
+ return obj;
+ };
+
+ // Inversion-of-control versions of `on`. Tell *this* object to listen to
+ // an event in another object... keeping track of what it's listening to
+ // for easier unbinding later.
+ Events.listenTo = function(obj, name, callback) {
+ if (!obj) return this;
+ var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
+ var listeningTo = this._listeningTo || (this._listeningTo = {});
+ var listening = listeningTo[id];
+
+ // This object is not listening to any other events on `obj` yet.
+ // Setup the necessary references to track the listening callbacks.
+ if (!listening) {
+ var thisId = this._listenId || (this._listenId = _.uniqueId('l'));
+ listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0};
+ }
+
+ // Bind callbacks on obj, and keep track of them on listening.
+ internalOn(obj, name, callback, this, listening);
+ return this;
+ };
+
+ // The reducing API that adds a callback to the `events` object.
+ var onApi = function(events, name, callback, options) {
+ if (callback) {
+ var handlers = events[name] || (events[name] = []);
+ var context = options.context, ctx = options.ctx, listening = options.listening;
+ if (listening) listening.count++;
+
+ handlers.push({ callback: callback, context: context, ctx: context || ctx, listening: listening });
+ }
+ return events;
+ };
+
+ // 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.
+ Events.off = function(name, callback, context) {
+ if (!this._events) return this;
+ this._events = eventsApi(offApi, this._events, name, callback, {
+ context: context,
+ listeners: this._listeners
+ });
+ return this;
+ };
+
+ // Tell this object to stop listening to either specific events ... or
+ // to every object it's currently listening to.
+ Events.stopListening = function(obj, name, callback) {
+ var listeningTo = this._listeningTo;
+ if (!listeningTo) return this;
+
+ var ids = obj ? [obj._listenId] : _.keys(listeningTo);
+
+ for (var i = 0; i < ids.length; i++) {
+ var listening = listeningTo[ids[i]];
+
+ // If listening doesn't exist, this object is not currently
+ // listening to obj. Break out early.
+ if (!listening) break;
+
+ listening.obj.off(name, callback, this);
+ }
+ if (_.isEmpty(listeningTo)) this._listeningTo = void 0;
+
+ return this;
+ };
+
+ // The reducing API that removes a callback from the `events` object.
+ var offApi = function(events, name, callback, options) {
+ if (!events) return;
+
+ var i = 0, listening;
+ var context = options.context, listeners = options.listeners;
+
+ // Delete all events listeners and "drop" events.
+ if (!name && !callback && !context) {
+ var ids = _.keys(listeners);
+ for (; i < ids.length; i++) {
+ listening = listeners[ids[i]];
+ delete listeners[listening.id];
+ delete listening.listeningTo[listening.objId];
+ }
+ return;
+ }
+
+ var names = name ? [name] : _.keys(events);
+ for (; i < names.length; i++) {
+ name = names[i];
+ var handlers = events[name];
+
+ // Bail out if there are no events stored.
+ if (!handlers) break;
+
+ // Replace events if there are any remaining. Otherwise, clean up.
+ var remaining = [];
+ for (var j = 0; j < handlers.length; j++) {
+ var handler = handlers[j];
+ if (
+ callback && callback !== handler.callback &&
+ callback !== handler.callback._callback ||
+ context && context !== handler.context
+ ) {
+ remaining.push(handler);
+ } else {
+ listening = handler.listening;
+ if (listening && --listening.count === 0) {
+ delete listeners[listening.id];
+ delete listening.listeningTo[listening.objId];
+ }
+ }
+ }
+
+ // Update tail event if the list has any events. Otherwise, clean up.
+ if (remaining.length) {
+ events[name] = remaining;
+ } else {
+ delete events[name];
+ }
+ }
+ if (_.size(events)) return events;
+ };
+
+ // Bind an event to only be triggered a single time. After the first time
+ // the callback is invoked, its listener will be removed. If multiple events
+ // are passed in using the space-separated syntax, the handler will fire
+ // once for each event, not once for a combination of all events.
+ Events.once = function(name, callback, context) {
+ // Map the event into a `{event: once}` object.
+ var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
+ return this.on(events, void 0, context);
+ };
+
+ // Inversion-of-control versions of `once`.
+ Events.listenToOnce = function(obj, name, callback) {
+ // Map the event into a `{event: once}` object.
+ var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj));
+ return this.listenTo(obj, events);
+ };
+
+ // Reduces the event callbacks into a map of `{event: onceWrapper}`.
+ // `offer` unbinds the `onceWrapper` after it has been called.
+ var onceMap = function(map, name, callback, offer) {
+ if (callback) {
+ var once = map[name] = _.once(function() {
+ offer(name, once);
+ callback.apply(this, arguments);
+ });
+ once._callback = callback;
+ }
+ return map;
+ };
+
+ // 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).
+ Events.trigger = function(name) {
+ if (!this._events) return this;
+
+ var length = Math.max(0, arguments.length - 1);
+ var args = Array(length);
+ for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
+
+ eventsApi(triggerApi, this._events, name, void 0, args);
+ return this;
+ };
+
+ // Handles triggering the appropriate event callbacks.
+ var triggerApi = function(objEvents, name, cb, args) {
+ if (objEvents) {
+ var events = objEvents[name];
+ var allEvents = objEvents.all;
+ if (events && allEvents) allEvents = allEvents.slice();
+ if (events) triggerEvents(events, args);
+ if (allEvents) triggerEvents(allEvents, [name].concat(args));
+ }
+ return objEvents;
+ };
+
+ // 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); return;
+ }
+ };
+
+ // 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 attrs = attributes || {};
+ options || (options = {});
+ this.cid = _.uniqueId(this.cidPrefix);
+ this.attributes = {};
+ if (options.collection) this.collection = options.collection;
+ if (options.parse) attrs = this.parse(attrs, options) || {};
+ attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
+ this.set(attrs, options);
+ this.changed = {};
+ this.initialize.apply(this, arguments);
+ };
+
+ // Attach all inheritable methods to the Model prototype.
+ _.extend(Model.prototype, Events, {
+
+ // A hash of attributes whose current and previous value differ.
+ changed: null,
+
+ // 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',
+
+ // The prefix is used to create the client id which is used to identify models locally.
+ // You may want to override this if you're experiencing name clashes with model ids.
+ cidPrefix: 'c',
+
+ // 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;
+ },
+
+ // Special-cased proxy to underscore's `_.matches` method.
+ matches: function(attrs) {
+ return !!_.iteratee(attrs, this)(this.attributes);
+ },
+
+ // 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) {
+ if (key == null) return this;
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ var attrs;
+ 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.
+ var unset = options.unset;
+ var silent = options.silent;
+ var changes = [];
+ var changing = this._changing;
+ this._changing = true;
+
+ if (!changing) {
+ this._previousAttributes = _.clone(this.attributes);
+ this.changed = {};
+ }
+
+ var current = this.attributes;
+ var changed = this.changed;
+ var prev = this._previousAttributes;
+
+ // For each `set` attribute, update or delete the current value.
+ for (var attr in attrs) {
+ val = attrs[attr];
+ if (!_.isEqual(current[attr], val)) changes.push(attr);
+ if (!_.isEqual(prev[attr], val)) {
+ changed[attr] = val;
+ } else {
+ delete changed[attr];
+ }
+ unset ? delete current[attr] : current[attr] = val;
+ }
+
+ // Update the `id`.
+ this.id = this.get(this.idAttribute);
+
+ // Trigger all relevant attribute changes.
+ if (!silent) {
+ if (changes.length) this._pending = options;
+ for (var i = 0; i < changes.length; 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) {
+ options = 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 old = this._changing ? this._previousAttributes : this.attributes;
+ var changed = {};
+ for (var attr in diff) {
+ var val = diff[attr];
+ if (_.isEqual(old[attr], val)) continue;
+ changed[attr] = val;
+ }
+ return _.size(changed) ? changed : false;
+ },
+
+ // 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, merging the response with the model's
+ // local attributes. Any changed attributes will trigger a "change" event.
+ fetch: function(options) {
+ options = _.extend({parse: true}, options);
+ var model = this;
+ var success = options.success;
+ options.success = function(resp) {
+ var serverAttrs = options.parse ? model.parse(resp, options) : resp;
+ if (!model.set(serverAttrs, options)) return false;
+ if (success) success.call(options.context, 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) {
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ var attrs;
+ if (key == null || typeof key === 'object') {
+ attrs = key;
+ options = val;
+ } else {
+ (attrs = {})[key] = val;
+ }
+
+ options = _.extend({validate: true, parse: true}, options);
+ var wait = options.wait;
+
+ // If we're not waiting and attributes exist, save acts as
+ // `set(attr).save(null, opts)` with validation. Otherwise, check if
+ // the model will be valid when the attributes, if any, are set.
+ if (attrs && !wait) {
+ if (!this.set(attrs, options)) return false;
+ } else {
+ if (!this._validate(attrs, options)) return false;
+ }
+
+ // After a successful server-side save, the client is (optionally)
+ // updated with the server-side state.
+ var model = this;
+ var success = options.success;
+ var attributes = this.attributes;
+ options.success = function(resp) {
+ // Ensure attributes are restored during synchronous saves.
+ model.attributes = attributes;
+ var serverAttrs = options.parse ? model.parse(resp, options) : resp;
+ if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
+ if (serverAttrs && !model.set(serverAttrs, options)) return false;
+ if (success) success.call(options.context, model, resp, options);
+ model.trigger('sync', model, resp, options);
+ };
+ wrapError(this, options);
+
+ // Set temporary attributes if `{wait: true}` to properly find new ids.
+ if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
+
+ var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
+ if (method === 'patch' && !options.attrs) options.attrs = attrs;
+ var xhr = this.sync(method, this, options);
+
+ // Restore attributes.
+ 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 wait = options.wait;
+
+ var destroy = function() {
+ model.stopListening();
+ model.trigger('destroy', model, model.collection, options);
+ };
+
+ options.success = function(resp) {
+ if (wait) destroy();
+ if (success) success.call(options.context, model, resp, options);
+ if (!model.isNew()) model.trigger('sync', model, resp, options);
+ };
+
+ var xhr = false;
+ if (this.isNew()) {
+ _.defer(options.success);
+ } else {
+ wrapError(this, options);
+ xhr = this.sync('delete', this, options);
+ }
+ if (!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;
+ var id = this.get(this.idAttribute);
+ return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(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.has(this.idAttribute);
+ },
+
+ // Check if the model is currently in a valid state.
+ isValid: function(options) {
+ return this._validate({}, _.defaults({validate: true}, options));
+ },
+
+ // 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, mapped to the
+ // number of arguments they take.
+ var modelMethods = { keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
+ omit: 0, chain: 1, isEmpty: 1 };
+
+ // Mix in each Underscore method as a proxy to `Model#attributes`.
+ addUnderscoreMethods(Model, modelMethods, 'attributes');
+
+ // Backbone.Collection
+ // -------------------
+
+ // If models tend to represent a single row of data, a Backbone Collection is
+ // more analogous 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.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, remove: false};
+
+ // Splices `insert` into `array` at index `at`.
+ var splice = function(array, insert, at) {
+ at = Math.min(Math.max(at, 0), array.length);
+ var tail = Array(array.length - at);
+ var length = insert.length;
+ for (var i = 0; i < tail.length; i++) tail[i] = array[i + at];
+ for (i = 0; i < length; i++) array[i + at] = insert[i];
+ for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
+ };
+
+ // 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. `models` may be Backbone
+ // Models or raw JavaScript objects to be converted to Models, or any
+ // combination of the two.
+ add: function(models, options) {
+ return this.set(models, _.extend({merge: false}, options, addOptions));
+ },
+
+ // Remove a model, or a list of models from the set.
+ remove: function(models, options) {
+ options = _.extend({}, options);
+ var singular = !_.isArray(models);
+ models = singular ? [models] : _.clone(models);
+ var removed = this._removeModels(models, options);
+ if (!options.silent && removed) this.trigger('update', this, options);
+ return singular ? removed[0] : removed;
+ },
+
+ // 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) {
+ if (models == null) return;
+
+ options = _.defaults({}, options, setOptions);
+ if (options.parse && !this._isModel(models)) models = this.parse(models, options);
+
+ var singular = !_.isArray(models);
+ models = singular ? [models] : models.slice();
+
+ var at = options.at;
+ if (at != null) at = +at;
+ if (at < 0) at += this.length + 1;
+
+ var set = [];
+ var toAdd = [];
+ var toRemove = [];
+ var modelMap = {};
+
+ var add = options.add;
+ var merge = options.merge;
+ var remove = options.remove;
+
+ var sort = false;
+ var sortable = this.comparator && (at == null) && options.sort !== false;
+ var sortAttr = _.isString(this.comparator) ? this.comparator : null;
+
+ // Turn bare objects into model references, and prevent invalid models
+ // from being added.
+ var model;
+ for (var i = 0; i < models.length; i++) {
+ model = models[i];
+
+ // If a duplicate is found, prevent it from being added and
+ // optionally merge it into the existing model.
+ var existing = this.get(model);
+ if (existing) {
+ if (merge && model !== existing) {
+ var attrs = this._isModel(model) ? model.attributes : model;
+ if (options.parse) attrs = existing.parse(attrs, options);
+ existing.set(attrs, options);
+ if (sortable && !sort) sort = existing.hasChanged(sortAttr);
+ }
+ if (!modelMap[existing.cid]) {
+ modelMap[existing.cid] = true;
+ set.push(existing);
+ }
+ models[i] = existing;
+
+ // If this is a new, valid model, push it to the `toAdd` list.
+ } else if (add) {
+ model = models[i] = this._prepareModel(model, options);
+ if (model) {
+ toAdd.push(model);
+ this._addReference(model, options);
+ modelMap[model.cid] = true;
+ set.push(model);
+ }
+ }
+ }
+
+ // Remove stale models.
+ if (remove) {
+ for (i = 0; i < this.length; i++) {
+ model = this.models[i];
+ if (!modelMap[model.cid]) toRemove.push(model);
+ }
+ if (toRemove.length) this._removeModels(toRemove, options);
+ }
+
+ // See if sorting is needed, update `length` and splice in new models.
+ var orderChanged = false;
+ var replace = !sortable && add && remove;
+ if (set.length && replace) {
+ orderChanged = this.length != set.length || _.some(this.models, function(model, index) {
+ return model !== set[index];
+ });
+ this.models.length = 0;
+ splice(this.models, set, 0);
+ this.length = this.models.length;
+ } else if (toAdd.length) {
+ if (sortable) sort = true;
+ splice(this.models, toAdd, at == null ? this.length : at);
+ this.length = this.models.length;
+ }
+
+ // Silently sort the collection if appropriate.
+ if (sort) this.sort({silent: true});
+
+ // Unless silenced, it's time to fire all appropriate add/sort events.
+ if (!options.silent) {
+ for (i = 0; i < toAdd.length; i++) {
+ if (at != null) options.index = at + i;
+ model = toAdd[i];
+ model.trigger('add', model, this, options);
+ }
+ if (sort || orderChanged) this.trigger('sort', this, options);
+ if (toAdd.length || toRemove.length) this.trigger('update', this, options);
+ }
+
+ // Return the added (or merged) model (or models).
+ return singular ? models[0] : models;
+ },
+
+ // 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 ? _.clone(options) : {};
+ for (var i = 0; i < this.models.length; i++) {
+ this._removeReference(this.models[i], options);
+ }
+ options.previousModels = this.models;
+ this._reset();
+ models = this.add(models, _.extend({silent: true}, options));
+ if (!options.silent) this.trigger('reset', this, options);
+ return models;
+ },
+
+ // Add a model to the end of the collection.
+ push: function(model, options) {
+ return this.add(model, _.extend({at: this.length}, options));
+ },
+
+ // Remove a model from the end of the collection.
+ pop: function(options) {
+ var model = this.at(this.length - 1);
+ return this.remove(model, options);
+ },
+
+ // Add a model to the beginning of the collection.
+ unshift: function(model, options) {
+ return this.add(model, _.extend({at: 0}, options));
+ },
+
+ // Remove a model from the beginning of the collection.
+ shift: function(options) {
+ var model = this.at(0);
+ return this.remove(model, options);
+ },
+
+ // Slice out a sub-array of models from the collection.
+ slice: function() {
+ return slice.apply(this.models, arguments);
+ },
+
+ // Get a model from the set by id.
+ get: function(obj) {
+ if (obj == null) return void 0;
+ var id = this.modelId(this._isModel(obj) ? obj.attributes : obj);
+ return this._byId[obj] || this._byId[id] || this._byId[obj.cid];
+ },
+
+ // Get the model at the given index.
+ at: function(index) {
+ if (index < 0) index += this.length;
+ return this.models[index];
+ },
+
+ // Return models with matching attributes. Useful for simple cases of
+ // `filter`.
+ where: function(attrs, first) {
+ return this[first ? 'find' : 'filter'](attrs);
+ },
+
+ // 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) {
+ var comparator = this.comparator;
+ if (!comparator) throw new Error('Cannot sort a set without a comparator');
+ options || (options = {});
+
+ var length = comparator.length;
+ if (_.isFunction(comparator)) comparator = _.bind(comparator, this);
+
+ // Run sort based on type of `comparator`.
+ if (length === 1 || _.isString(comparator)) {
+ this.models = this.sortBy(comparator);
+ } else {
+ this.models.sort(comparator);
+ }
+ if (!options.silent) this.trigger('sort', this, options);
+ return this;
+ },
+
+ // 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 = _.extend({parse: true}, options);
+ var success = options.success;
+ var collection = this;
+ options.success = function(resp) {
+ var method = options.reset ? 'reset' : 'set';
+ collection[method](resp, options);
+ if (success) success.call(options.context, 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) : {};
+ var wait = options.wait;
+ model = this._prepareModel(model, options);
+ if (!model) return false;
+ if (!wait) this.add(model, options);
+ var collection = this;
+ var success = options.success;
+ options.success = function(model, resp, callbackOpts) {
+ if (wait) collection.add(model, callbackOpts);
+ if (success) success.call(callbackOpts.context, model, resp, callbackOpts);
+ };
+ 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, {
+ model: this.model,
+ comparator: this.comparator
+ });
+ },
+
+ // Define how to uniquely identify models in the collection.
+ modelId: function (attrs) {
+ return attrs[this.model.prototype.idAttribute || 'id'];
+ },
+
+ // 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 (this._isModel(attrs)) {
+ if (!attrs.collection) attrs.collection = this;
+ return attrs;
+ }
+ options = options ? _.clone(options) : {};
+ options.collection = this;
+ var model = new this.model(attrs, options);
+ if (!model.validationError) return model;
+ this.trigger('invalid', this, model.validationError, options);
+ return false;
+ },
+
+ // Internal method called by both remove and set.
+ _removeModels: function(models, options) {
+ var removed = [];
+ for (var i = 0; i < models.length; i++) {
+ var model = this.get(models[i]);
+ if (!model) continue;
+
+ var index = this.indexOf(model);
+ this.models.splice(index, 1);
+ this.length--;
+
+ if (!options.silent) {
+ options.index = index;
+ model.trigger('remove', model, this, options);
+ }
+
+ removed.push(model);
+ this._removeReference(model, options);
+ }
+ return removed.length ? removed : false;
+ },
+
+ // Method for checking whether an object should be considered a model for
+ // the purposes of adding to the collection.
+ _isModel: function (model) {
+ return model instanceof Model;
+ },
+
+ // Internal method to create a model's ties to a collection.
+ _addReference: function(model, options) {
+ this._byId[model.cid] = model;
+ var id = this.modelId(model.attributes);
+ if (id != null) this._byId[id] = model;
+ model.on('all', this._onModelEvent, this);
+ },
+
+ // Internal method to sever a model's ties to a collection.
+ _removeReference: function(model, options) {
+ delete this._byId[model.cid];
+ var id = this.modelId(model.attributes);
+ if (id != null) delete this._byId[id];
+ 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 (event === 'change') {
+ var prevId = this.modelId(model.previousAttributes());
+ var id = this.modelId(model.attributes);
+ if (prevId !== id) {
+ if (prevId != null) delete this._byId[prevId];
+ if (id != null) this._byId[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 collectionMethods = { forEach: 3, each: 3, map: 3, collect: 3, reduce: 4,
+ foldl: 4, inject: 4, reduceRight: 4, foldr: 4, find: 3, detect: 3, filter: 3,
+ select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
+ contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
+ head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
+ without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
+ isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
+ sortBy: 3, indexBy: 3};
+
+ // Mix in each Underscore method as a proxy to `Collection#models`.
+ addUnderscoreMethods(Collection, collectionMethods, 'models');
+
+ // 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');
+ _.extend(this, _.pick(options, viewOptions));
+ this._ensureElement();
+ this.initialize.apply(this, arguments);
+ };
+
+ // Cached regex to split keys for `delegate`.
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+ // List of view options to be set 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 preferred 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._removeElement();
+ this.stopListening();
+ return this;
+ },
+
+ // Remove this view's element from the document and all event listeners
+ // attached to it. Exposed for subclasses using an alternative DOM
+ // manipulation API.
+ _removeElement: function() {
+ this.$el.remove();
+ },
+
+ // Change the view's element (`this.el` property) and re-delegate the
+ // view's events on the new element.
+ setElement: function(element) {
+ this.undelegateEvents();
+ this._setElement(element);
+ this.delegateEvents();
+ return this;
+ },
+
+ // Creates the `this.el` and `this.$el` references for this view using the
+ // given `el`. `el` can be a CSS selector or an HTML string, a jQuery
+ // context or an element. Subclasses can override this to utilize an
+ // alternative DOM manipulation API and are only required to set the
+ // `this.el` property.
+ _setElement: function(el) {
+ this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
+ this.el = this.$el[0];
+ },
+
+ // 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`.
+ delegateEvents: function(events) {
+ events || (events = _.result(this, 'events'));
+ if (!events) return this;
+ this.undelegateEvents();
+ for (var key in events) {
+ var method = events[key];
+ if (!_.isFunction(method)) method = this[method];
+ if (!method) continue;
+ var match = key.match(delegateEventSplitter);
+ this.delegate(match[1], match[2], _.bind(method, this));
+ }
+ return this;
+ },
+
+ // Add a single event listener to the view's element (or a child element
+ // using `selector`). This only works for delegate-able events: not `focus`,
+ // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
+ delegate: function(eventName, selector, listener) {
+ this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
+ return this;
+ },
+
+ // Clears all callbacks previously bound to the view by `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() {
+ if (this.$el) this.$el.off('.delegateEvents' + this.cid);
+ return this;
+ },
+
+ // A finer-grained `undelegateEvents` for removing a single delegated event.
+ // `selector` and `listener` are both optional.
+ undelegate: function(eventName, selector, listener) {
+ this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
+ return this;
+ },
+
+ // Produces a DOM element to be assigned to your view. Exposed for
+ // subclasses using an alternative DOM manipulation API.
+ _createElement: function(tagName) {
+ return document.createElement(tagName);
+ },
+
+ // 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');
+ this.setElement(this._createElement(_.result(this, 'tagName')));
+ this._setAttributes(attrs);
+ } else {
+ this.setElement(_.result(this, 'el'));
+ }
+ },
+
+ // Set attributes from a hash on this view's element. Exposed for
+ // subclasses using an alternative DOM manipulation API.
+ _setAttributes: function(attributes) {
+ this.$el.attr(attributes);
+ }
+
+ });
+
+ // 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;
+ }
+
+ // Pass along `textStatus` and `errorThrown` from jQuery.
+ var error = options.error;
+ options.error = function(xhr, textStatus, errorThrown) {
+ options.textStatus = textStatus;
+ options.errorThrown = errorThrown;
+ if (error) error.call(options.context, xhr, textStatus, errorThrown);
+ };
+
+ // 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);
+ if (router.execute(callback, args, name) !== false) {
+ router.trigger.apply(router, ['route:' + name].concat(args));
+ router.trigger('route', name, args);
+ Backbone.history.trigger('route', router, name, args);
+ }
+ });
+ return this;
+ },
+
+ // Execute a route handler with the provided parameters. This is an
+ // excellent place to do pre-route setup or post-route cleanup.
+ execute: function(callback, args, name) {
+ if (callback) callback.apply(this, args);
+ },
+
+ // 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 + '(?:\\?([\\s\\S]*))?$');
+ },
+
+ // 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, i) {
+ // Don't decode the search params.
+ if (i === params.length - 1) return param || null;
+ 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 = [];
+ this.checkUrl = _.bind(this.checkUrl, this);
+
+ // 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 stripping urls of hash.
+ var pathStripper = /#.*$/;
+
+ // 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,
+
+ // Are we at the app root?
+ atRoot: function() {
+ var path = this.location.pathname.replace(/[^\/]$/, '$&/');
+ return path === this.root && !this.getSearch();
+ },
+
+ // Does the pathname match the root?
+ matchRoot: function() {
+ var path = this.decodeFragment(this.location.pathname);
+ var root = path.slice(0, this.root.length - 1) + '/';
+ return root === this.root;
+ },
+
+ // Unicode characters in `location.pathname` are percent encoded so they're
+ // decoded for comparison. `%25` should not be decoded since it may be part
+ // of an encoded parameter.
+ decodeFragment: function(fragment) {
+ return decodeURI(fragment.replace(/%25/g, '%2525'));
+ },
+
+ // In IE6, the hash fragment and search params are incorrect if the
+ // fragment contains `?`.
+ getSearch: function() {
+ var match = this.location.href.replace(/#.*/, '').match(/\?.+/);
+ return match ? match[0] : '';
+ },
+
+ // 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 pathname and search params, without the root.
+ getPath: function() {
+ var path = this.decodeFragment(
+ this.location.pathname + this.getSearch()
+ ).slice(this.root.length - 1);
+ return path.charAt(0) === '/' ? path.slice(1) : path;
+ },
+
+ // Get the cross-browser normalized URL fragment from the path or hash.
+ getFragment: function(fragment) {
+ if (fragment == null) {
+ if (this._usePushState || !this._wantsHashChange) {
+ fragment = this.getPath();
+ } 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._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
+ this._useHashChange = this._wantsHashChange && this._hasHashChange;
+ this._wantsPushState = !!this.options.pushState;
+ this._hasPushState = !!(this.history && this.history.pushState);
+ this._usePushState = this._wantsPushState && this._hasPushState;
+ this.fragment = this.getFragment();
+
+ // Normalize root to always include a leading and trailing slash.
+ this.root = ('/' + this.root + '/').replace(rootStripper, '/');
+
+ // Transition from hashChange to pushState or vice versa if both are
+ // requested.
+ if (this._wantsHashChange && this._wantsPushState) {
+
+ // 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._hasPushState && !this.atRoot()) {
+ var root = this.root.slice(0, -1) || '/';
+ this.location.replace(root + '#' + this.getPath());
+ // 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._hasPushState && this.atRoot()) {
+ this.navigate(this.getHash(), {replace: true});
+ }
+
+ }
+
+ // Proxy an iframe to handle location events if the browser doesn't
+ // support the `hashchange` event, HTML5 history, or the user wants
+ // `hashChange` but not `pushState`.
+ if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
+ this.iframe = document.createElement('iframe');
+ this.iframe.src = 'javascript:0';
+ this.iframe.style.display = 'none';
+ this.iframe.tabIndex = -1;
+ var body = document.body;
+ // Using `appendChild` will throw on IE < 9 if the document is not ready.
+ var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
+ iWindow.document.open();
+ iWindow.document.close();
+ iWindow.location.hash = '#' + this.fragment;
+ }
+
+ // Add a cross-platform `addEventListener` shim for older browsers.
+ var addEventListener = window.addEventListener || function (eventName, listener) {
+ return attachEvent('on' + eventName, listener);
+ };
+
+ // Depending on whether we're using pushState or hashes, and whether
+ // 'onhashchange' is supported, determine how we check the URL state.
+ if (this._usePushState) {
+ addEventListener('popstate', this.checkUrl, false);
+ } else if (this._useHashChange && !this.iframe) {
+ addEventListener('hashchange', this.checkUrl, false);
+ } else if (this._wantsHashChange) {
+ this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
+ }
+
+ 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() {
+ // Add a cross-platform `removeEventListener` shim for older browsers.
+ var removeEventListener = window.removeEventListener || function (eventName, listener) {
+ return detachEvent('on' + eventName, listener);
+ };
+
+ // Remove window listeners.
+ if (this._usePushState) {
+ removeEventListener('popstate', this.checkUrl, false);
+ } else if (this._useHashChange && !this.iframe) {
+ removeEventListener('hashchange', this.checkUrl, false);
+ }
+
+ // Clean up the iframe if necessary.
+ if (this.iframe) {
+ document.body.removeChild(this.iframe);
+ this.iframe = null;
+ }
+
+ // Some environments will throw when clearing an undefined interval.
+ if (this._checkUrlInterval) 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 the user pressed the back button, the iframe's hash will have
+ // changed and we should use that for comparison.
+ if (current === this.fragment && this.iframe) {
+ current = this.getHash(this.iframe.contentWindow);
+ }
+
+ if (current === this.fragment) return false;
+ if (this.iframe) this.navigate(current);
+ this.loadUrl();
+ },
+
+ // 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(fragment) {
+ // If the root doesn't match, no routes can match either.
+ if (!this.matchRoot()) return false;
+ fragment = this.fragment = this.getFragment(fragment);
+ return _.some(this.handlers, function(handler) {
+ if (handler.route.test(fragment)) {
+ handler.callback(fragment);
+ return true;
+ }
+ });
+ },
+
+ // 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};
+
+ // Normalize the fragment.
+ fragment = this.getFragment(fragment || '');
+
+ // Don't include a trailing slash on the root.
+ var root = this.root;
+ if (fragment === '' || fragment.charAt(0) === '?') {
+ root = root.slice(0, -1) || '/';
+ }
+ var url = root + fragment;
+
+ // Strip the hash and decode for matching.
+ fragment = this.decodeFragment(fragment.replace(pathStripper, ''));
+
+ if (this.fragment === fragment) return;
+ this.fragment = fragment;
+
+ // If pushState is available, we use it to set the fragment as a real URL.
+ if (this._usePushState) {
+ 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.getHash(this.iframe.contentWindow))) {
+ var iWindow = this.iframe.contentWindow;
+
+ // 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) {
+ iWindow.document.open();
+ iWindow.document.close();
+ }
+
+ this._updateHash(iWindow.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) return 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 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` 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.call(options.context, model, resp, options);
+ model.trigger('error', model, resp, options);
+ };
+ };
+
+ return Backbone;
+
+}));
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/CHANGES.md
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/CHANGES.md Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,761 @@
+CKEditor 4 Changelog
+====================
+
+## CKEditor 4.4.8
+
+**Security Updates:**
+
+* Fixed XSS vulnerability in the HTML parser reported by [Dheeraj Joshi](https://twitter.com/dheerajhere) and [Prem Kumar](https://twitter.com/iAmPr3m).
+
+ Issue summary: It was possible to execute XSS inside CKEditor after persuading the victim to: (i) switch CKEditor to source mode, then (ii) paste a specially crafted HTML code, prepared by the attacker, into the opened CKEditor source area, and (iii) switch back to WYSIWYG mode.
+
+**An upgrade is highly recommended!**
+
+Fixed Issues:
+
+* [#12899](http://dev.ckeditor.com/ticket/12899): Fixed: Corrected wrong tag ending for horizontal box definition in the [Dialog User Interface](http://ckeditor.com/addon/dialogui) plugin. Thanks to [mizafish](https://github.com/mizafish)!
+* [#13254](http://dev.ckeditor.com/ticket/13254): Fixed: Cannot outdent block after indent when using the [Div Editing Area](http://ckeditor.com/addon/divarea) plugin. Thanks to [Jonathan Cottrill](https://github.com/jcttrll)!
+* [#13268](http://dev.ckeditor.com/ticket/13268): Fixed: Documentation for [`CKEDITOR.dom.text`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.text) is incorrect. Thanks to [Ben Kiefer](https://github.com/benkiefer)!
+* [#12739](http://dev.ckeditor.com/ticket/12739): Fixed: Link loses inline styles when edited without the [Advanced Tab for Dialogs](http://ckeditor.com/addon/dialogadvtab) plugin. Thanks to [Віталій Крутько](https://github.com/asmforce)!
+* [#13292](http://dev.ckeditor.com/ticket/13292): Fixed: Protection pattern does not work in attribute in self-closing elements with no space before `/>`. Thanks to [Віталій Крутько](https://github.com/asmforce)!
+* [PR#192](https://github.com/ckeditor/ckeditor-dev/pull/192): Fixed: Variable name typo in the [Dialog User Interface](http://ckeditor.com/addon/dialogui) plugin which caused [`CKEDITOR.ui.dialog.radio`](http://docs.ckeditor.com/#!/api/CKEDITOR.ui.dialog.radio) validation to not work. Thanks to [Florian Ludwig](https://github.com/FlorianLudwig)!
+* [#13232](http://dev.ckeditor.com/ticket/13232): [Safari] Fixed: The [`element.appendText()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-appendText) method does not work properly for empty elements.
+* [#13233](http://dev.ckeditor.com/ticket/13233): Fixed: [HTMLDataProcessor](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlDataProcessor) can process `foo:href` attributes.
+* [#12796](http://dev.ckeditor.com/ticket/12796): Fixed: The [Indent List](http://ckeditor.com/addon/indentlist) plugin unwraps parent `
` elements. Thanks to [Andrew Stucki](https://github.com/andrewstucki)!
+* [#12885](http://dev.ckeditor.com/ticket/12885): Added missing [`editor.getData()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-getData) parameter documentation.
+* [#11982](http://dev.ckeditor.com/ticket/11982): Fixed: Bullet added in a wrong position after the *Enter* key is pressed in a nested list.
+* [#13027](http://dev.ckeditor.com/ticket/13027): Fixed: Keyboard navigation in dialog windows with multiple tabs not following IBM CI 162 instructions or [ARIA Authoring Practices](http://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#tabpanel).
+* [#12256](http://dev.ckeditor.com/ticket/12256): Fixed: Basic styles classes are lost when pasting from Microsoft Word if [basic styles](http://ckeditor.com/addon/basicstyles) were configured to use classes.
+* [#12729](http://dev.ckeditor.com/ticket/12729): Fixed: Incorrect structure created when merging a block into a list item on *Backspace* and *Delete*.
+* [#13031](http://dev.ckeditor.com/ticket/13031): [Firefox] Fixed: No more line breaks in source view since Firefox 36.
+* [#13131](http://dev.ckeditor.com/ticket/13131): Fixed: The [Code Snippet](http://ckeditor.com/addon/codesnippet) plugin cannot be used without the [IFrame Editing Area](http://ckeditor.com/addon/wysiwygarea) plugin.
+* [#9086](http://dev.ckeditor.com/ticket/9086): Fixed: Invalid ARIA property used on paste area `
'+f.options+""},{id:"chkInsertOpt",type:"checkbox",label:f.insertOption,
+"default":l.templates_replaceContent}]}]}],buttons:[CKEDITOR.dialog.cancelButton],onShow:function(){var a=this.getContentElement("selectTpl","templatesList");g=a.getElement();CKEDITOR.loadTemplates(l.templates_files,function(){var b=(l.templates||"default").split(",");if(b.length){var c=g;c.setHtml("");for(var d=0,h=b.length;d'+f.emptyListMsg+"")});this._.element.on("keydown",i)},onHide:function(){this._.element.removeListener("keydown",i)}}})})();
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/icons/hidpi/templates-rtl.png
Binary file server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/icons/hidpi/templates-rtl.png has changed
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/icons/hidpi/templates.png
Binary file server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/icons/hidpi/templates.png has changed
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/icons/templates-rtl.png
Binary file server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/icons/templates-rtl.png has changed
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/icons/templates.png
Binary file server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/icons/templates.png has changed
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/af.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/af.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","af",{button:"Sjablone",emptyListMsg:"(Geen sjablone gedefineer nie)",insertOption:"Vervang huidige inhoud",options:"Sjabloon opsies",selectPromptMsg:"Kies die sjabloon om te gebruik in die redigeerder (huidige inhoud gaan verlore):",title:"Inhoud Sjablone"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ar.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ar.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","ar",{button:"القوالب",emptyListMsg:"(لم يتم تعريف أي قالب)",insertOption:"استبدال المحتوى",options:"خصائص القوالب",selectPromptMsg:"اختر القالب الذي تود وضعه في المحرر",title:"قوالب المحتوى"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/bg.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/bg.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","bg",{button:"Шаблони",emptyListMsg:"(Няма дефинирани шаблони)",insertOption:"Препокрива актуалното съдържание",options:"Опции за шаблона",selectPromptMsg:"Изберете шаблон (текущото съдържание на редактора ще бъде загубено):",title:"Шаблони"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/bn.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/bn.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","bn",{button:"টেমপ্লেট",emptyListMsg:"(কোন টেমপ্লেট ডিফাইন করা নেই)",insertOption:"Replace actual contents",options:"Template Options",selectPromptMsg:"অনুগ্রহ করে এডিটরে ওপেন করার জন্য টেমপ্লেট বাছাই করুন (আসল কনটেন্ট হারিয়ে যাবে):",title:"কনটেন্ট টেমপ্লেট"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/bs.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/bs.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","bs",{button:"Templates",emptyListMsg:"(No templates defined)",insertOption:"Replace actual contents",options:"Template Options",selectPromptMsg:"Please select the template to open in the editor",title:"Content Templates"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ca.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ca.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","ca",{button:"Plantilles",emptyListMsg:"(No hi ha plantilles definides)",insertOption:"Reemplaça el contingut actual",options:"Opcions de plantilla",selectPromptMsg:"Seleccioneu una plantilla per usar a l'editor (per defecte s'elimina el contingut actual):",title:"Plantilles de contingut"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/cs.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/cs.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","cs",{button:"Šablony",emptyListMsg:"(Není definována žádná šablona)",insertOption:"Nahradit aktuální obsah",options:"Nastavení šablon",selectPromptMsg:"Prosím zvolte šablonu pro otevření v editoru (aktuální obsah editoru bude ztracen):",title:"Šablony obsahu"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/cy.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/cy.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","cy",{button:"Templedi",emptyListMsg:"(Dim templedi wedi'u diffinio)",insertOption:"Amnewid y cynnwys go iawn",options:"Opsiynau Templedi",selectPromptMsg:"Dewiswch dempled i'w agor yn y golygydd",title:"Templedi Cynnwys"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/da.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/da.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","da",{button:"Skabeloner",emptyListMsg:"(Der er ikke defineret nogen skabelon)",insertOption:"Erstat det faktiske indhold",options:"Skabelon muligheder",selectPromptMsg:"Vælg den skabelon, som skal åbnes i editoren (nuværende indhold vil blive overskrevet):",title:"Indholdsskabeloner"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/de.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/de.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","de",{button:"Vorlagen",emptyListMsg:"(keine Vorlagen definiert)",insertOption:"Aktuellen Inhalt ersetzen",options:"Vorlagen Optionen",selectPromptMsg:"Klicken Sie auf eine Vorlage, um sie im Editor zu öffnen (der aktuelle Inhalt wird dabei gelöscht!):",title:"Vorlagen"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/el.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/el.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","el",{button:"Πρότυπα",emptyListMsg:"(Δεν έχουν καθοριστεί πρότυπα)",insertOption:"Αντικατάσταση υπάρχοντων περιεχομένων",options:"Επιλογές Προτύπου",selectPromptMsg:"Παρακαλώ επιλέξτε πρότυπο για εισαγωγή στο πρόγραμμα",title:"Πρότυπα Περιεχομένου"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/en-au.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/en-au.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","en-au",{button:"Templates",emptyListMsg:"(No templates defined)",insertOption:"Replace actual contents",options:"Template Options",selectPromptMsg:"Please select the template to open in the editor",title:"Content Templates"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/en-ca.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/en-ca.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","en-ca",{button:"Templates",emptyListMsg:"(No templates defined)",insertOption:"Replace actual contents",options:"Template Options",selectPromptMsg:"Please select the template to open in the editor",title:"Content Templates"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/en-gb.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/en-gb.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","en-gb",{button:"Templates",emptyListMsg:"(No templates defined)",insertOption:"Replace actual contents",options:"Template Options",selectPromptMsg:"Please select the template to open in the editor",title:"Content Templates"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/en.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/en.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","en",{button:"Templates",emptyListMsg:"(No templates defined)",insertOption:"Replace actual contents",options:"Template Options",selectPromptMsg:"Please select the template to open in the editor",title:"Content Templates"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/eo.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/eo.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","eo",{button:"Ŝablonoj",emptyListMsg:"(Neniu ŝablono difinita)",insertOption:"Anstataŭigi la nunan enhavon",options:"Opcioj pri ŝablonoj",selectPromptMsg:"Bonvolu selekti la ŝablonon por malfermi ĝin en la redaktilo",title:"Enhavo de ŝablonoj"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/es.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/es.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","es",{button:"Plantillas",emptyListMsg:"(No hay plantillas definidas)",insertOption:"Reemplazar el contenido actual",options:"Opciones de plantillas",selectPromptMsg:"Por favor selecciona la plantilla a abrir en el editor (el contenido actual se perderá):",title:"Contenido de Plantillas"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/et.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/et.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","et",{button:"Mall",emptyListMsg:"(Ühtegi malli ei ole defineeritud)",insertOption:"Praegune sisu asendatakse",options:"Malli valikud",selectPromptMsg:"Palun vali mall, mis avada redaktoris (praegune sisu läheb kaotsi):",title:"Sisumallid"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/eu.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/eu.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","eu",{button:"Txantiloiak",emptyListMsg:"(Ez dago definitutako txantiloirik)",insertOption:"Ordeztu oraingo edukiak",options:"Txantiloi Aukerak",selectPromptMsg:"Mesedez txantiloia aukeratu editorean kargatzeko (orain dauden edukiak galduko dira):",title:"Eduki Txantiloiak"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/fa.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/fa.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","fa",{button:"الگوها",emptyListMsg:"(الگوئی تعریف نشده است)",insertOption:"محتویات کنونی جایگزین شوند",options:"گزینههای الگو",selectPromptMsg:"لطفاً الگوی مورد نظر را برای بازکردن در ویرایشگر انتخاب کنید",title:"الگوهای محتویات"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/fi.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/fi.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","fi",{button:"Pohjat",emptyListMsg:"(Ei määriteltyjä pohjia)",insertOption:"Korvaa koko sisältö",options:"Sisältöpohjan ominaisuudet",selectPromptMsg:"Valitse editoriin avattava pohja",title:"Sisältöpohjat"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/fo.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/fo.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","fo",{button:"Skabelónir",emptyListMsg:"(Ongar skabelónir tøkar)",insertOption:"Yvirskriva núverandi innihald",options:"Møguleikar fyri Template",selectPromptMsg:"Vinarliga vel ta skabelón, ið skal opnast í tekstviðgeranum (Hetta yvirskrivar núverandi innihald):",title:"Innihaldsskabelónir"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/fr-ca.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/fr-ca.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","fr-ca",{button:"Modèles",emptyListMsg:"(Aucun modèle disponible)",insertOption:"Remplacer tout le contenu actuel",options:"Options de modèles",selectPromptMsg:"Sélectionner le modèle à ouvrir dans l'éditeur",title:"Modèles de contenu"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/fr.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/fr.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","fr",{button:"Modèles",emptyListMsg:"(Aucun modèle disponible)",insertOption:"Remplacer le contenu actuel",options:"Options des modèles",selectPromptMsg:"Veuillez sélectionner le modèle pour l'ouvrir dans l'éditeur",title:"Contenu des modèles"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/gl.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/gl.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","gl",{button:"Modelos",emptyListMsg:"(Non hai modelos definidos)",insertOption:"Substituír o contido actual",options:"Opcións de modelos",selectPromptMsg:"Seleccione o modelo a abrir no editor",title:"Modelos de contido"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/gu.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/gu.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","gu",{button:"ટેમ્પ્લેટ",emptyListMsg:"(કોઈ ટેમ્પ્લેટ ડિફાઇન નથી)",insertOption:"મૂળ શબ્દને બદલો",options:"ટેમ્પ્લેટના વિકલ્પો",selectPromptMsg:"એડિટરમાં ઓપન કરવા ટેમ્પ્લેટ પસંદ કરો (વર્તમાન કન્ટેન્ટ સેવ નહીં થાય):",title:"કન્ટેન્ટ ટેમ્પ્લેટ"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/he.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/he.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","he",{button:"תבניות",emptyListMsg:"(לא הוגדרו תבניות)",insertOption:"החלפת תוכן ממשי",options:"אפשרויות התבניות",selectPromptMsg:"יש לבחור תבנית לפתיחה בעורך. התוכן המקורי ימחק:",title:"תביות תוכן"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/hi.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/hi.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","hi",{button:"टॅम्प्लेट",emptyListMsg:"(कोई टॅम्प्लेट डिफ़ाइन नहीं किया गया है)",insertOption:"मूल शब्दों को बदलें",options:"Template Options",selectPromptMsg:"ऍडिटर में ओपन करने हेतु टॅम्प्लेट चुनें(वर्तमान कन्टॅन्ट सेव नहीं होंगे):",title:"कन्टेन्ट टॅम्प्लेट"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/hr.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/hr.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","hr",{button:"Predlošci",emptyListMsg:"(Nema definiranih predložaka)",insertOption:"Zamijeni trenutne sadržaje",options:"Opcije predložaka",selectPromptMsg:"Molimo odaberite predložak koji želite otvoriti (stvarni sadržaj će biti izgubljen):",title:"Predlošci sadržaja"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/hu.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/hu.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","hu",{button:"Sablonok",emptyListMsg:"(Nincs sablon megadva)",insertOption:"Kicseréli a jelenlegi tartalmat",options:"Sablon opciók",selectPromptMsg:"Válassza ki melyik sablon nyíljon meg a szerkesztőben (a jelenlegi tartalom elveszik):",title:"Elérhető sablonok"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/id.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/id.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","id",{button:"Contoh",emptyListMsg:"(Tidak ada contoh didefinisikan)",insertOption:"Ganti konten sebenarnya",options:"Opsi Contoh",selectPromptMsg:"Mohon pilih contoh untuk dibuka di editor",title:"Contoh Konten"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/is.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/is.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","is",{button:"Sniðmát",emptyListMsg:"(Ekkert sniðmát er skilgreint!)",insertOption:"Skipta út raunverulegu innihaldi",options:"Template Options",selectPromptMsg:"Veldu sniðmát til að opna í ritlinum. (Núverandi innihald víkur fyrir því!):",title:"Innihaldssniðmát"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/it.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/it.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","it",{button:"Modelli",emptyListMsg:"(Nessun modello definito)",insertOption:"Cancella il contenuto corrente",options:"Opzioni del Modello",selectPromptMsg:"Seleziona il modello da aprire nell'editor",title:"Contenuto dei modelli"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ja.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ja.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","ja",{button:"テンプレート",emptyListMsg:"(テンプレートが定義されていません)",insertOption:"現在のエディタの内容と置き換えます",options:"テンプレートオプション",selectPromptMsg:"エディターで使用するテンプレートを選択してください。 (現在のエディタの内容は失われます):",title:"内容テンプレート"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ka.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ka.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","ka",{button:"თარგები",emptyListMsg:"(თარგი არაა განსაზღვრული)",insertOption:"მიმდინარე შეგთავსის შეცვლა",options:"თარგების პარამეტრები",selectPromptMsg:"აირჩიეთ თარგი რედაქტორისთვის",title:"თარგები"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/km.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/km.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","km",{button:"ពុម្ពគំរូ",emptyListMsg:"(មិនមានពុម្ពគំរូត្រូវបានកំណត់)",insertOption:"ជំនួសក្នុងមាតិកាបច្ចុប្បន្ន",options:"ជម្រើសពុម្ពគំរូ",selectPromptMsg:"សូមរើសពុម្ពគំរូដើម្បីបើកក្នុងកម្មវិធីសរសេរអត្ថបទ",title:"ពុម្ពគំរូមាតិកា"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ko.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ko.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","ko",{button:"템플릿",emptyListMsg:"(템플릿이 없습니다.)",insertOption:"현재 내용 바꾸기",options:"템플릿 옵션",selectPromptMsg:"에디터에서 사용할 템플릿을 선택하십시요. (지금까지 작성된 내용은 사라집니다.):",title:"내용 템플릿"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ku.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ku.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","ku",{button:"ڕووکار",emptyListMsg:"(هیچ ڕووکارێك دیارینەکراوە)",insertOption:"لە شوێن دانانی ئەم پێکهاتانەی ئێستا",options:"هەڵبژاردەکانی ڕووکار",selectPromptMsg:"ڕووکارێك هەڵبژێره بۆ کردنەوەی له سەرنووسەر:",title:"پێکهاتەی ڕووکار"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/lt.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/lt.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","lt",{button:"Šablonai",emptyListMsg:"(Šablonų sąrašas tuščias)",insertOption:"Pakeisti dabartinį turinį pasirinktu šablonu",options:"Template Options",selectPromptMsg:"Pasirinkite norimą šabloną (Dėmesio! esamas turinys bus prarastas):",title:"Turinio šablonai"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/lv.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/lv.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","lv",{button:"Sagataves",emptyListMsg:"(Nav norādītas sagataves)",insertOption:"Aizvietot pašreizējo saturu",options:"Sagataves uzstādījumi",selectPromptMsg:"Lūdzu, norādiet sagatavi, ko atvērt editorā (patreizējie dati tiks zaudēti):",title:"Satura sagataves"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/mk.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/mk.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","mk",{button:"Templates",emptyListMsg:"(No templates defined)",insertOption:"Replace actual contents",options:"Template Options",selectPromptMsg:"Please select the template to open in the editor",title:"Content Templates"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/mn.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/mn.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","mn",{button:"Загварууд",emptyListMsg:"(Загвар тодорхойлогдоогүй байна)",insertOption:"Одоогийн агууллагыг дарж бичих",options:"Template Options",selectPromptMsg:"Загварыг нээж editor-рүү сонгож оруулна уу (Одоогийн агууллагыг устаж магадгүй):",title:"Загварын агуулга"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ms.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ms.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","ms",{button:"Templat",emptyListMsg:"(Tiada Templat Disimpan)",insertOption:"Replace actual contents",options:"Template Options",selectPromptMsg:"Sila pilih templat untuk dibuka oleh editor (kandungan sebenar akan hilang):",title:"Templat Kandungan"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/nb.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/nb.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","nb",{button:"Maler",emptyListMsg:"(Ingen maler definert)",insertOption:"Erstatt gjeldende innhold",options:"Alternativer for mal",selectPromptMsg:"Velg malen du vil åpne i redigeringsverktøyet:",title:"Innholdsmaler"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/nl.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/nl.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","nl",{button:"Sjablonen",emptyListMsg:"(Geen sjablonen gedefinieerd)",insertOption:"Vervang de huidige inhoud",options:"Template opties",selectPromptMsg:"Selecteer het sjabloon dat in de editor geopend moet worden (de actuele inhoud gaat verloren):",title:"Inhoud sjablonen"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/no.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/no.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","no",{button:"Maler",emptyListMsg:"(Ingen maler definert)",insertOption:"Erstatt gjeldende innhold",options:"Alternativer for mal",selectPromptMsg:"Velg malen du vil åpne i redigeringsverktøyet:",title:"Innholdsmaler"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/pl.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/pl.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","pl",{button:"Szablony",emptyListMsg:"(Brak zdefiniowanych szablonów)",insertOption:"Zastąp obecną zawartość",options:"Opcje szablonów",selectPromptMsg:"Wybierz szablon do otwarcia w edytorze (obecna zawartość okna edytora zostanie utracona):",title:"Szablony zawartości"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/pt-br.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/pt-br.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","pt-br",{button:"Modelos de layout",emptyListMsg:"(Não foram definidos modelos de layout)",insertOption:"Substituir o conteúdo atual",options:"Opções de Template",selectPromptMsg:"Selecione um modelo de layout para ser aberto no editor (o conteúdo atual será perdido):",title:"Modelo de layout de conteúdo"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/pt.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/pt.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","pt",{button:"Modelos",emptyListMsg:"(Sem modelos definidos)",insertOption:"Substituir conteúdos atuais",options:"Opções do modelo",selectPromptMsg:"Por favor, selecione o modelo para abrir no editor",title:"Conteúdo dos Modelos"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ro.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ro.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","ro",{button:"Template-uri (şabloane)",emptyListMsg:"(Niciun template (şablon) definit)",insertOption:"Înlocuieşte cuprinsul actual",options:"Opțiuni șabloane",selectPromptMsg:"Vă rugăm selectaţi template-ul (şablonul) ce se va deschide în editor (conţinutul actual va fi pierdut):",title:"Template-uri (şabloane) de conţinut"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ru.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ru.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","ru",{button:"Шаблоны",emptyListMsg:"(не определено ни одного шаблона)",insertOption:"Заменить текущее содержимое",options:"Параметры шаблона",selectPromptMsg:"Пожалуйста, выберите, какой шаблон следует открыть в редакторе",title:"Шаблоны содержимого"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/si.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/si.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","si",{button:"අච්චුව",emptyListMsg:"කිසිම අච්චුවක් කලින් තීරණය කර ",insertOption:"සත්ය අන්තර්ගතයන් ප්රතිස්ථාපනය කරන්න",options:"අච්චු ",selectPromptMsg:"කරුණාකර සංස්කරණය සදහා අච්චුවක් ",title:"අන්තර්ගත් අච්චුන්"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sk.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sk.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","sk",{button:"Šablóny",emptyListMsg:"(Žiadne šablóny nedefinované)",insertOption:"Nahradiť aktuálny obsah",options:"Možnosti šablóny",selectPromptMsg:"Prosím vyberte šablónu na otvorenie v editore",title:"Šablóny obsahu"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sl.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sl.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","sl",{button:"Predloge",emptyListMsg:"(Ni pripravljenih predlog)",insertOption:"Zamenjaj trenutno vsebino",options:"Možnosti Predloge",selectPromptMsg:"Izberite predlogo, ki jo želite odpreti v urejevalniku (trenutna vsebina bo izgubljena):",title:"Vsebinske predloge"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sq.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sq.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","sq",{button:"Shabllonet",emptyListMsg:"(Asnjë shabllon nuk është paradefinuar)",insertOption:"Zëvendëso përmbajtjen aktuale",options:"Opsionet e Shabllonit",selectPromptMsg:"Përzgjidhni shabllonin për të hapur tek redaktuesi",title:"Përmbajtja e Shabllonit"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sr-latn.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sr-latn.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","sr-latn",{button:"Obrasci",emptyListMsg:"(Nema definisanih obrazaca)",insertOption:"Replace actual contents",options:"Template Options",selectPromptMsg:"Molimo Vas da odaberete obrazac koji ce biti primenjen na stranicu (trenutni sadržaj ce biti obrisan):",title:"Obrasci za sadržaj"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sr.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sr.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","sr",{button:"Обрасци",emptyListMsg:"(Нема дефинисаних образаца)",insertOption:"Replace actual contents",options:"Template Options",selectPromptMsg:"Молимо Вас да одаберете образац који ће бити примењен на страницу (тренутни садржај ће бити обрисан):",title:"Обрасци за садржај"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sv.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/sv.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","sv",{button:"Sidmallar",emptyListMsg:"(Ingen mall är vald)",insertOption:"Ersätt aktuellt innehåll",options:"Inställningar för mall",selectPromptMsg:"Var god välj en mall att använda med editorn (allt nuvarande innehåll raderas):",title:"Sidmallar"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/th.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/th.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","th",{button:"เทมเพลต",emptyListMsg:"(ยังไม่มีการกำหนดเทมเพลต)",insertOption:"แทนที่เนื้อหาเว็บไซต์ที่เลือก",options:"ตัวเลือกเกี่ยวกับเทมเพลท",selectPromptMsg:"กรุณาเลือก เทมเพลต เพื่อนำไปแก้ไขในอีดิตเตอร์ (เนื้อหาส่วนนี้จะหายไป):",title:"เทมเพลตของส่วนเนื้อหาเว็บไซต์"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/tr.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/tr.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","tr",{button:"Şablonlar",emptyListMsg:"(Belirli bir şablon seçilmedi)",insertOption:"Mevcut içerik ile değiştir",options:"Şablon Seçenekleri",selectPromptMsg:"Düzenleyicide açmak için lütfen bir şablon seçin. (hali hazırdaki içerik kaybolacaktır.):",title:"İçerik Şablonları"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/tt.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/tt.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","tt",{button:"Шаблоннар",emptyListMsg:"(Шаблоннар билгеләнмәгән)",insertOption:"Әлеге эчтәлекне алмаштыру",options:"Шаблон үзлекләре",selectPromptMsg:"Please select the template to open in the editor",title:"Эчтәлек шаблоннары"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ug.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/ug.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","ug",{button:"قېلىپ",emptyListMsg:"(قېلىپ يوق)",insertOption:"نۆۋەتتىكى مەزمۇننى ئالماشتۇر",options:"قېلىپ تاللانمىسى",selectPromptMsg:"تەھرىرلىگۈچنىڭ مەزمۇن قېلىپىنى تاللاڭ:",title:"مەزمۇن قېلىپى"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/uk.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/uk.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","uk",{button:"Шаблони",emptyListMsg:"(Не знайдено жодного шаблону)",insertOption:"Замінити поточний вміст",options:"Опції шаблону",selectPromptMsg:"Оберіть, будь ласка, шаблон для відкриття в редакторі (поточний зміст буде втрачено):",title:"Шаблони змісту"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/vi.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/vi.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","vi",{button:"Mẫu dựng sẵn",emptyListMsg:"(Không có mẫu dựng sẵn nào được định nghĩa)",insertOption:"Thay thế nội dung hiện tại",options:"Tùy chọn mẫu dựng sẵn",selectPromptMsg:"Hãy chọn mẫu dựng sẵn để mở trong trình biên tập (nội dung hiện tại sẽ bị mất):",title:"Nội dung Mẫu dựng sẵn"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/zh-cn.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/zh-cn.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","zh-cn",{button:"模板",emptyListMsg:"(没有模板)",insertOption:"替换当前内容",options:"模板选项",selectPromptMsg:"请选择要在编辑器中使用的模板:",title:"内容模板"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/zh.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/lang/zh.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,1 @@
+CKEDITOR.plugins.setLang("templates","zh",{button:"範本",emptyListMsg:"(尚未定義任何範本)",insertOption:"替代實際內容",options:"範本選項",selectPromptMsg:"請選擇要在編輯器中開啟的範本。",title:"內容範本"});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/plugin.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/plugin.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,7 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+(function(){CKEDITOR.plugins.add("templates",{requires:"dialog",lang:"af,ar,bg,bn,bs,ca,cs,cy,da,de,el,en,en-au,en-ca,en-gb,eo,es,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn",icons:"templates,templates-rtl",hidpi:!0,init:function(a){CKEDITOR.dialog.add("templates",CKEDITOR.getUrl(this.path+"dialogs/templates.js"));a.addCommand("templates",new CKEDITOR.dialogCommand("templates"));a.ui.addButton&&
+a.ui.addButton("Templates",{label:a.lang.templates.button,command:"templates",toolbar:"doctools,10"})}});var c={},f={};CKEDITOR.addTemplates=function(a,d){c[a]=d};CKEDITOR.getTemplates=function(a){return c[a]};CKEDITOR.loadTemplates=function(a,d){for(var e=[],b=0,c=a.length;bType the title here
Type the text here
'},{title:"Strange Template",image:"template2.gif",description:"A template that defines two colums, each one with a title, and some text.",
+html:'
Title 1
Title 2
Text 1
Text 2
More text goes here.
'},{title:"Text and Table",image:"template3.gif",description:"A title with some text and a table.",html:'
Title goes here
Table title
Type the text here
'}]});
\ No newline at end of file
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/templates/images/template1.gif
Binary file server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/templates/images/template1.gif has changed
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/templates/images/template2.gif
Binary file server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/templates/images/template2.gif has changed
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/templates/images/template3.gif
Binary file server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/templates/templates/images/template3.gif has changed
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/uicolor/dialogs/uicolor.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/plugins/uicolor/dialogs/uicolor.js Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,9 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.dialog.add("uicolor",function(b){function f(a){/^#/.test(a)&&(a=window.YAHOO.util.Color.hex2rgb(a.substr(1)));c.setValue(a,!0);c.refresh(e)}function g(a){b.setUiColor(a);d._.contents.tab1.configBox.setValue('config.uiColor = "#'+c.get("hex")+'"')}var d,c,h=b.getUiColor(),e="cke_uicolor_picker"+CKEDITOR.tools.getNextNumber();return{title:b.lang.uicolor.title,minWidth:360,minHeight:320,onLoad:function(){d=this;this.setupContent();CKEDITOR.env.ie7Compat&&d.parts.contents.setStyle("overflow",
+"hidden")},contents:[{id:"tab1",label:"",title:"",expand:!0,padding:0,elements:[{id:"yuiColorPicker",type:"html",html:"",onLoad:function(){var a=CKEDITOR.getUrl("plugins/uicolor/yui/");this.picker=c=new window.YAHOO.widget.ColorPicker(e,{showhsvcontrols:!0,showhexcontrols:!0,images:{PICKER_THUMB:a+"assets/picker_thumb.png",HUE_THUMB:a+"assets/hue_thumb.png"}});h&&f(h);c.on("rgbChange",function(){d._.contents.tab1.predefined.setValue("");
+g("#"+c.get("hex"))});for(var a=new CKEDITOR.dom.nodeList(c.getElementsByTagName("input")),b=0;b
+ CKEditor Samples » Create and Destroy Editor Instances for Ajax Applications
+
+
+
+ This sample shows how to create and destroy CKEditor instances on the fly. After the removal of CKEditor the content created inside the editing
+ area will be displayed in a <div> element.
+
+
+ For details of how to create this setup check the source code of this sample page
+ for JavaScript code responsible for the creation and destruction of a CKEditor instance.
+
+
+
Click the buttons to create and remove a CKEditor instance.
+ CKEditor Samples » Append To Page Element Using JavaScript Code
+
+
+
+
+ The CKEDITOR.appendTo() method serves to to place editors inside existing DOM elements. Unlike CKEDITOR.replace(),
+ a target container to be replaced is no longer necessary. A new editor
+ instance is inserted directly wherever it is desired.
+
+
CKEDITOR.appendTo( 'container_id',
+ { /* Configuration options to be used. */ }
+ 'Editor content to be used.'
+);
+ This sample page demonstrates the idea of Advanced Content Filter
+ (ACF), a sophisticated
+ tool that takes control over what kind of data is accepted by the editor and what
+ kind of output is produced.
+
+
When and what is being filtered?
+
+ ACF controls
+ every single source of data that comes to the editor.
+ It process both HTML that is inserted manually (i.e. pasted by the user)
+ and programmatically like:
+
+
+editor.setData( '<p>Hello world!</p>' );
+
+
+ ACF discards invalid,
+ useless HTML tags and attributes so the editor remains "clean" during
+ runtime. ACF behaviour
+ can be configured and adjusted for a particular case to prevent the
+ output HTML (i.e. in CMS systems) from being polluted.
+
+ This kind of filtering is a first, client-side line of defense
+ against "tag soups",
+ the tool that precisely restricts which tags, attributes and styles
+ are allowed (desired). When properly configured, ACF
+ is an easy and fast way to produce a high-quality, intentionally filtered HTML.
+
+
+
How to configure or disable ACF?
+
+ Advanced Content Filter is enabled by default, working in "automatic mode", yet
+ it provides a set of easy rules that allow adjusting filtering rules
+ and disabling the entire feature when necessary. The config property
+ responsible for this feature is config.allowedContent.
+
+
+ By "automatic mode" is meant that loaded plugins decide which kind
+ of content is enabled and which is not. For example, if the link
+ plugin is loaded it implies that <a> tag is
+ automatically allowed. Each plugin is given a set
+ of predefined ACF rules
+ that control the editor until
+ config.allowedContent
+ is defined manually.
+
+
+ Let's assume our intention is to restrict the editor to accept (produce) paragraphs
+ only: no attributes, no styles, no other tags.
+ With ACF
+ this is very simple. Basically set
+ config.allowedContent to 'p':
+
+// Trying to insert disallowed tag and attribute.
+editor.setData( '<p style="color: red">Hello <em>world</em>!</p>' );
+alert( editor.getData() );
+
+// Filtered data is returned.
+"<p>Hello world!</p>"
+
+
+ What happened? Since config.allowedContent: 'p' is set the editor assumes
+ that only plain <p> are accepted. Nothing more. This is why
+ style attribute and <em> tag are gone. The same
+ filtering would happen if we pasted disallowed HTML into this editor.
+
+ ACF is far more than
+ I/O control: the entire
+ UI of the editor is adjusted to what
+ filters restrict. For example: if <a> tag is
+ disallowed
+ by ACF,
+ then accordingly link command, toolbar button and link dialog
+ are also disabled. Editor is smart: it knows which features must be
+ removed from the interface to match filtering rules.
+
+
+ CKEditor can be far more specific. If <a> tag is
+ allowed by filtering rules to be used but it is restricted
+ to have only one attribute (href)
+ config.allowedContent = 'a[!href]', then
+ "Target" tab of the link dialog is automatically disabled as target
+ attribute isn't included in ACF rules
+ for <a>. This behaviour applies to dialog fields, context
+ menus and toolbar buttons.
+
+
+
Sample configurations
+
+ There are several editor instances below that present different
+ ACF setups. All of them,
+ except the inline instance, share the same HTML content to visualize
+ how different filtering rules affect the same input data.
+
+
+
+
+
+
+
+ This editor is using default configuration ("automatic mode"). It means that
+
+ config.allowedContent is defined by loaded plugins.
+ Each plugin extends filtering rules to make it's own associated content
+ available for the user.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This editor is using a custom configuration for
+ ACF:
+
+ The following rules may require additional explanation:
+
+
+
+ h1 h2 h3 p blockquote strong em - These tags
+ are accepted by the editor. Any tag attributes will be discarded.
+
+
+ a[!href] - href attribute is obligatory
+ for <a> tag. Tags without this attribute
+ are disarded. No other attribute will be accepted.
+
+
+ img(left,right)[!src,alt,width,height] - src
+ attribute is obligatory for <img> tag.
+ alt, width, height
+ and class attributes are accepted but
+ class must be either class="left"
+ or class="right"
+
+
+ table tr th td caption - These tags
+ are accepted by the editor. Any tag attributes will be discarded.
+
+
+ span{!font-family}, span{!color},
+ span(!marker) - <span> tags
+ will be accepted if either font-family or
+ color style is set or class="marker"
+ is present.
+
+
+ del ins - These tags
+ are accepted by the editor. Any tag attributes will be discarded.
+
+
+
+ Please note that UI of the
+ editor is different. It's a response to what happened to the filters.
+ Since text-align isn't allowed, the align toolbar is gone.
+ The same thing happened to subscript/superscript, strike, underline
+ (<u>, <sub>, <sup>
+ are disallowed by
+ config.allowedContent) and many other buttons.
+
+
+
+
+
+
+
+
+
+
+
+
+ This editor is using a custom configuration for
+ ACF.
+ Note that filters can be configured as an object literal
+ as an alternative to a string-based definition.
+
+ As you can see, removing plugins and buttons implies filtering.
+ Several tags are not allowed in the editor because there's no
+ plugin/button that is responsible for creating and editing this
+ kind of content (for example: the image is missing because
+ of removeButtons: 'Image'). The conclusion is that
+ ACF works "backwards"
+ as well: modifying UI
+ elements is changing allowed content rules.
+
+
+
+
+
+
+
+
+
+
+
+
+ This editor is built on editable <h1> element.
+ ACF takes care of
+ what can be included in <h1>. Note that there
+ are no block styles in Styles combo. Also why lists, indentation,
+ blockquote, div, form and other buttons are missing.
+
+
+ ACF makes sure that
+ no disallowed tags will come to <h1> so the final
+ markup is valid. If the user tried to paste some invalid HTML
+ into this editor (let's say a list), it would be automatically
+ converted into plain text.
+
+
+
+ Apollo 11 was the spaceflight that landed the first humans, Americans Neil Armstrong and Buzz Aldrin, on the Moon on July 20, 1969, at 20:18 UTC.
+
+
+
+
+
+
+
+
+
+ This editor is using a custom configuration for ACF.
+ It's using the
+ Disallowed Content property of the filter to eliminate all title attributes.
+
+ This editor is using a custom configuration for ACF.
+ It's using the
+ Disallowed Content property of the filter to eliminate all a and img tags,
+ while allowing all other tags.
+
+ This sample shows how to automatically replace <div> elements
+ with a CKEditor instance on the fly, following user's doubleclick. The content
+ that was previously placed inside the <div> element will now
+ be moved into CKEditor editing area.
+
+
+ For details on how to create this setup check the source code of this sample page.
+
+
+
+ Double-click any of the following <div> elements to transform them into
+ editor instances.
+
+
+
+ Part 1
+
+
+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi
+ semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna
+ rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla
+ nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce
+ eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus.
+
+
+
+
+ Part 2
+
+
+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi
+ semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna
+ rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla
+ nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce
+ eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus.
+
+
+ Donec velit. Mauris massa. Vestibulum non nulla. Nam suscipit arcu nec elit. Phasellus
+ sollicitudin iaculis ante. Ut non mauris et sapien tincidunt adipiscing. Vestibulum
+ vitae leo. Suspendisse nec mi tristique nulla laoreet vulputate.
+
+
+
+
+ Part 3
+
+
+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi
+ semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna
+ rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla
+ nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce
+ eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus.
+
This sample page demonstrates the inline editing feature - CKEditor instances will be created automatically from page elements with contentEditable attribute set to value true:
+
<div contenteditable="true" > ... </div>
+
Click inside of any element below to start editing.
+
+
+
+
+
+
+ CKEditor Goes Inline!
+
+
+ Lorem ipsum dolor sit amet dolor duis blandit vestibulum faucibus a, tortor.
+
+
+
+
+
+ Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies.
+
+
+ Curabitur et ligula. Ut molestie a, ultricies porta urna. Vestibulum commodo volutpat a, convallis ac, laoreet enim. Phasellus fermentum in, dolor. Pellentesque facilisis. Nulla imperdiet sit amet magna. Vestibulum dapibus, mauris nec malesuada fames ac.
+
+
+
+
+
+
+
+
+ Fusce vitae porttitor
+
+
+
+ Lorem ipsum dolor sit amet dolor. Duis blandit vestibulum faucibus a, tortor.
+
+
+
+ Proin nunc justo felis mollis tincidunt, risus risus pede, posuere cubilia Curae, Nullam euismod, enim. Etiam nibh ultricies dolor ac dignissim erat volutpat. Vivamus fermentum nisl nulla sem in metus. Maecenas wisi. Donec nec erat volutpat.
+
+
+
+ Fusce vitae porttitor a, euismod convallis nisl, blandit risus tortor, pretium.
+ Vehicula vitae, imperdiet vel, ornare enim vel sodales rutrum
+
+
+
+
+ Libero nunc, rhoncus ante ipsum non ipsum. Nunc eleifend pede turpis id sollicitudin fringilla. Phasellus ultrices, velit ac arcu.
+
+
+
Pellentesque nunc. Donec suscipit erat. Pellentesque habitant morbi tristique ullamcorper.
+
Mauris mattis feugiat lectus nec mauris. Nullam vitae ante.
+
+
+
+
+
+ Integer condimentum sit amet
+
+
+ Aenean nonummy a, mattis varius. Cras aliquet.
+ Praesent magna non mattis ac, rhoncus nunc, rhoncus eget, cursus pulvinar mollis.
+
Proin id nibh. Sed eu libero posuere sed, lectus. Phasellus dui gravida gravida feugiat mattis ac, felis.
+
Integer condimentum sit amet, tempor elit odio, a dolor non ante at sapien. Sed ac lectus. Nulla ligula quis eleifend mi, id leo velit pede cursus arcu id nulla ac lectus. Phasellus vestibulum. Nunc viverra enim quis diam.
+
+
+
+ Praesent wisi accumsan sit amet nibh
+
+
Donec ullamcorper, risus tortor, pretium porttitor. Morbi quam quis lectus non leo.
+
Integer faucibus scelerisque. Proin faucibus at, aliquet vulputate, odio at eros. Fusce gravida, erat vitae augue. Fusce urna fringilla gravida.
+
In hac habitasse platea dictumst. Praesent wisi accumsan sit amet nibh. Maecenas orci luctus a, lacinia quam sem, posuere commodo, odio condimentum tempor, pede semper risus. Suspendisse pede. In hac habitasse platea dictumst. Nam sed laoreet sit amet erat. Integer.
+
+
+
+
+
+
+
+
Quisque justo neque, mattis sed, fermentum ultrices posuere cubilia Curae, Vestibulum elit metus, quis placerat ut, lectus. Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis. Fusce porttitor, nulla quis turpis. Nullam laoreet vel, consectetuer tellus suscipit ultricies, hendrerit wisi. Donec odio nec velit ac nunc sit amet, accumsan cursus aliquet. Vestibulum ante sit amet sagittis mi.
+
+ Nullam laoreet vel consectetuer tellus suscipit
+
+
+
Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis.
Nullam laoreet vel, consectetuer tellus suscipit ultricies, hendrerit wisi. Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis. Fusce porttitor, nulla quis turpis.
+
Donec odio nec velit ac nunc sit amet, accumsan cursus aliquet. Vestibulum ante sit amet sagittis mi. Sed in nonummy faucibus turpis. Mauris eget tellus. Donec non felis. Nam eget dolor. Vestibulum enim. Donec.
+ This sample shows how to create an inline editor instance of CKEditor. It is created
+ with a JavaScript call using the following code:
+
+
+// This property tells CKEditor to not activate every element with contenteditable=true element.
+CKEDITOR.disableAutoInline = true;
+
+var editor = CKEDITOR.inline( document.getElementById( 'editable' ) );
+
+
+ Note that editable in the code above is the id
+ attribute of the <div> element to be converted into an inline instance.
+
+
+
+
Apollo 11
+
+
Apollo 11 was the spaceflight that landed the first humans, Americans Neil Armstrong and Buzz Aldrin, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.
+
+
Armstrong spent about three and a half two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5 kg) of lunar material for return to Earth. A third member of the mission, Michael Collins, piloted the command spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.
+
+
Broadcasting and quotes
+
+
Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:
+
+
+
One small step for [a] man, one giant leap for mankind.
+
+
+
Apollo 11 effectively ended the Space Race and fulfilled a national goal proposed in 1961 by the late U.S. President John F. Kennedy in a speech before the United States Congress:
+
+
+
[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.
+
+
+
Technical details
+
+
+
Mission crew
+
+
+
Position
+
Astronaut
+
+
+
+
+
Commander
+
Neil A. Armstrong
+
+
+
Command Module Pilot
+
Michael Collins
+
+
+
Lunar Module Pilot
+
Edwin "Buzz" E. Aldrin, Jr.
+
+
+
+
+
Launched by a Saturn V rocket from Kennedy Space Center in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of NASA's Apollo program. The Apollo spacecraft had three parts:
+
+
+
Command Module with a cabin for the three astronauts which was the only part which landed back on Earth
+
Service Module which supported the Command Module with propulsion, electrical power, oxygen and water
+
Lunar Module for landing on the Moon.
+
+
+
After being sent to the Moon by the Saturn V's upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the Sea of Tranquility. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the Pacific Ocean on July 24.
+ You can also create an inline editor from a textarea
+ element. In this case the textarea will be replaced
+ by a div element with inline editing enabled.
+
+
+// "article-body" is the name of a textarea element.
+var editor = CKEDITOR.inline( 'article-body' );
+
+ This sample shows how to configure CKEditor instances to use the
+ AutoGrow (autogrow) plugin that lets the editor window expand
+ and shrink depending on the amount and size of content entered in the editing area.
+
+
+ In its default implementation the AutoGrow feature can expand the
+ CKEditor window infinitely in order to avoid introducing scrollbars to the editing area.
+
+
+ It is also possible to set a maximum height for the editor window. Once CKEditor
+ editing area reaches the value in pixels specified in the
+ autoGrow_maxHeight
+ configuration setting, scrollbars will be added and the editor window will no longer expand.
+
+
+ To add a CKEditor instance using the autogrow plugin and its
+ autoGrow_maxHeight attribute, insert the following JavaScript call to your code:
+
+
+CKEDITOR.replace( 'textarea_id', {
+ extraPlugins: 'autogrow',
+ autoGrow_maxHeight: 800,
+
+ // Remove the Resize plugin as it does not make sense to use it in conjunction with the AutoGrow plugin.
+ removePlugins: 'resize'
+});
+
+ Note that textarea_id in the code above is the id attribute of
+ the <textarea> element to be replaced with CKEditor. The maximum height should
+ be given in pixels.
+
+ This sample shows how to configure CKEditor to output BBCode format instead of HTML.
+ Please note that the editor configuration was modified to reflect what is needed in a BBCode editing environment.
+ Smiley images, for example, were stripped to the emoticons that are commonly used in some BBCode dialects.
+
+
+ Please note that currently there is no standard for the BBCode markup language, so its implementation
+ for different platforms (message boards, blogs etc.) can vary. This means that before using CKEditor to
+ output BBCode you may need to adjust the implementation to your own environment.
+
+
+ A snippet of the configuration code can be seen below; check the source of this page for
+ a full definition:
+
+ This editor is using the Code Snippet plugin which introduces beautiful code snippets.
+ By default the codesnippet plugin depends on the built-in client-side syntax highlighting
+ library highlight.js.
+
+ Please note that this plugin is not compatible with Internet Explorer 8.
+
+
+
+
+
+
Inline editor
+
+
+
+ The following sample shows the Code Snippet plugin running inside
+ an inline CKEditor instance. The CKEditor instance below was created by using the following configuration settings:
+
+ Note: The highlight.js themes
+ must be loaded manually to be applied inside an inline editor instance, as the
+ codeSnippet_theme setting will not work in that case.
+ You need to include the stylesheet in the <head> section of the page, for example:
+
+ This sample shows how to configure CKEditor instances to use the
+ Developer Tools (devtools) plugin that displays
+ information about dialog window elements, including the name of the dialog window,
+ tab, and UI element. Please note that the tooltip also contains a link to the
+ CKEditor JavaScript API
+ documentation for each of the selected elements.
+
+
+ This plugin is aimed at developers who would like to customize their CKEditor
+ instances and create their own plugins. By default it is turned off; it is
+ usually useful to only turn it on in the development phase. Note that it works with
+ all CKEditor dialog windows, including the ones that were created by custom plugins.
+
+
+ To add a CKEditor instance using the devtools plugin, insert
+ the following JavaScript call into your code:
+
+ This sample shows how to use the
+ CKEditor Dialog API
+ to customize CKEditor dialog windows without changing the original editor code.
+ The following customizations are being done in the example below:
+
+
+ For details on how to create this setup check the source code of this sample page.
+
+
+
A custom dialog is added to the editors using the pluginsLoaded event, from an external dialog definition file:
+
+
Creating a custom dialog window – "My Dialog" dialog window opened with the "My Dialog" toolbar button.
+
Creating a custom button – Add button to open the dialog with "My Dialog" toolbar button.
+
+
+
+
The below editor modify the dialog definition of the above added dialog using the dialogDefinition event:
+
+
Adding dialog tab – Add new tab "My Tab" to dialog window.
+
Removing a dialog window tab – Remove "Second Tab" page from the dialog window.
+
Adding dialog window fields – Add "My Custom Field" to the dialog window.
+
Removing dialog window field – Remove "Select Field" selection field from the dialog window.
+
Setting default values for dialog window fields – Set default value of "Text Field" text field.
+
Setup initial focus for dialog window – Put initial focus on "My Custom Field" text field.
+ This sample shows how to configure CKEditor to use the Document Properties plugin.
+ This plugin allows you to set the metadata of the page, including the page encoding, margins,
+ meta tags, or background.
+
+
Note: This plugin is to be used along with the fullPage configuration.
+
+ The CKEditor instance below is inserted with a JavaScript call using the following code:
+
+ Note that textarea_id in the code above is the id attribute of
+ the <textarea> element to be replaced.
+
+
+ The allowedContent in the code above is set to true to disable content filtering.
+ Setting this option is not obligatory, but in full page mode there is a strong chance that one may want be able to freely enter any HTML content in source mode without any limitations.
+
+ This sample shows how to configure the Enter and Shift+Enter keys
+ to perform actions specified in the
+ enterMode
+ and shiftEnterMode
+ parameters, respectively.
+ You can choose from the following options:
+
+
+
ENTER_P – new <p> paragraphs are created;
+
ENTER_BR – lines are broken with <br> elements;
+
ENTER_DIV – new <div> blocks are created.
+
+
+ The sample code below shows how to configure CKEditor to create a <div> block when Enter key is pressed.
+
+ This sample shows how to configure CKEditor to output
+ HTML code that can be used with
+
+ Adobe Flash.
+ The code will contain a subset of standard HTML elements like <b>,
+ <i>, and <p> as well as HTML attributes.
+
+
+ To add a CKEditor instance outputting Flash compliant HTML code, load the editor using a standard
+ JavaScript call, and define CKEditor features to use HTML elements and attributes.
+
+
+ For details on how to create this setup check the source code of this sample page.
+
+
+
+ To see how it works, create some content in the editing area of CKEditor on the left
+ and send it to the Flash object on the right side of the page by using the
+ Send to Flash button.
+
+ This sample shows how to configure CKEditor to output valid
+ HTML 4.01 code.
+ Traditional HTML elements like <b>,
+ <i>, and <font> are used in place of
+ <strong>, <em>, and CSS styles.
+
+
+ To add a CKEditor instance outputting legacy HTML 4.01 code, load the editor using a standard
+ JavaScript call, and define CKEditor features to use the HTML compliant elements and attributes.
+
+
+ A snippet of the configuration code can be seen below; check the source of this page for
+ full definition:
+
+ This sample shows the advantages of Magicline plugin
+ which is to enhance the editing process. Thanks to this plugin,
+ a number of difficult focus spaces which are inaccessible due to
+ browser issues can now be focused.
+
+
+ Magicline plugin shows a red line with a handler
+ which, when clicked, inserts a paragraph and allows typing. To see this,
+ focus an editor and move your mouse above the focus space you want
+ to access. The plugin is enabled by default so no additional
+ configuration is necessary.
+
+ This sample shows the usage of the CKEditor mathematical plugin that introduces a MathJax widget. You can now use it to create or modify equations using TeX.
+
+
+ TeX content will be automatically replaced by a widget when you put it in a <span class="math-tex"> element. You can also add new equations by using the Math toolbar button and entering TeX content in the plugin dialog window. After you click OK, a widget will be inserted into the editor content.
+
+
+ The output of the editor will be plain TeX with MathJax delimiters: \( and \), as in the code below:
+
+ This sample shows how to configure CKEditor instances to use the
+ Placeholder plugin that lets you insert read-only elements
+ into your content. To enter and modify read-only text, use the
+ Create Placeholder button and its matching dialog window.
+
+
+ To add a CKEditor instance that uses the placeholder plugin and a related
+ Create Placeholder toolbar button, insert the following JavaScript
+ call to your code:
+
+ This sample shows several editor instances that share the very same spaces for both the toolbar and the bottom bar.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integer condimentum sit amet
+
+
+ Aenean nonummy a, mattis varius. Cras aliquet.
+ Praesent magna non mattis ac, rhoncus nunc, rhoncus eget, cursus pulvinar mollis.
+
Proin id nibh. Sed eu libero posuere sed, lectus. Phasellus dui gravida gravida feugiat mattis ac, felis.
+
Integer condimentum sit amet, tempor elit odio, a dolor non ante at sapien. Sed ac lectus. Nulla ligula quis eleifend mi, id leo velit pede cursus arcu id nulla ac lectus. Phasellus vestibulum. Nunc viverra enim quis diam.
+
+
+
+ Praesent wisi accumsan sit amet nibh
+
+
Donec ullamcorper, risus tortor, pretium porttitor. Morbi quam quis lectus non leo.
+
Integer faucibus scelerisque. Proin faucibus at, aliquet vulputate, odio at eros. Fusce gravida, erat vitae augue. Fusce urna fringilla gravida.
+
In hac habitasse platea dictumst. Praesent wisi accumsan sit amet nibh. Maecenas orci luctus a, lacinia quam sem, posuere commodo, odio condimentum tempor, pede semper risus. Suspendisse pede. In hac habitasse platea dictumst. Nam sed laoreet sit amet erat. Integer.
+ Sourcedialog plugin provides an easy way to edit raw HTML content
+ of an editor, similarly to what is possible with Sourcearea
+ plugin for classic (iframe-based) instances but using dialogs. Thanks to that, it's also possible
+ to manipulate raw content of inline editor instances.
+
+
+ This plugin extends the toolbar with a button,
+ which opens a dialog window with a source code editor. It works with both classic
+ and inline instances. To enable this
+ plugin, basically add extraPlugins: 'sourcedialog' to editor's
+ config:
+
+ Note that you may want to include removePlugins: 'sourcearea'
+ in your config when using Sourcedialog in classic editor instances.
+ This prevents feature redundancy.
+
+
+ Note that editable in the code above is the id
+ attribute of the <div> element to be converted into an inline instance.
+
+
+ Note that textarea_id in the code above is the id attribute of
+ the <textarea> element to be replaced with CKEditor.
+
+ This sample shows how to configure CKEditor instances to use the
+ Stylesheet Parser (stylesheetparser) plugin that fills
+ the Styles drop-down list based on the CSS rules available in the document stylesheet.
+
+
+ To add a CKEditor instance using the stylesheetparser plugin, insert
+ the following JavaScript call into your code:
+
+ This sample shows how to configure CKEditor instances to use the
+ TableResize (tableresize) plugin that allows
+ the user to edit table columns by using the mouse.
+
+
+ The TableResize plugin makes it possible to modify table column width. Hover
+ your mouse over the column border to see the cursor change to indicate that
+ the column can be resized. Click and drag your mouse to set the desired width.
+
+
+ By default the plugin is turned off. To add a CKEditor instance using the
+ TableResize plugin, insert the following JavaScript call into your code:
+
+ This sample page demonstrates editor with loaded full toolbar (all registered buttons) and, if
+ current editor's configuration modifies default settings, also editor with modified toolbar.
+
+
+
Since CKEditor 4 there are two ways to configure toolbar buttons.
+ You can explicitly define which buttons are displayed in which groups and in which order.
+ This is the more precise setting, but less flexible. If newly added plugin adds its
+ own button you'll have to add it manually to your config.toolbar setting as well.
+
+
+
To add a CKEditor instance with custom toolbar setting, insert the following JavaScript call to your code:
+
+
+CKEDITOR.replace( 'textarea_id', {
+ toolbar: [
+ { name: 'document', items: [ 'Source', '-', 'NewPage', 'Preview', '-', 'Templates' ] }, // Defines toolbar group with name (used to create voice label) and items in 3 subgroups.
+ [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ], // Defines toolbar group without name.
+ '/', // Line break - next group will be placed in new line.
+ { name: 'basicstyles', items: [ 'Bold', 'Italic' ] }
+ ]
+});
+ You can define which groups of buttons (like e.g. basicstyles, clipboard
+ and forms) are displayed and in which order. Registered buttons are associated
+ with toolbar groups by toolbar property in their definition.
+ This setting's advantage is that you don't have to modify toolbar configuration
+ when adding/removing plugins which register their own buttons.
+
+
+
To add a CKEditor instance with custom toolbar groups setting, insert the following JavaScript call to your code:
+
+
+CKEDITOR.replace( 'textarea_id', {
+ toolbarGroups: [
+ { name: 'document', groups: [ 'mode', 'document' ] }, // Displays document group with its two subgroups.
+ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] }, // Group's name will be used to create voice label.
+ '/', // Line break - next group will be placed in new line.
+ { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
+ { name: 'links' }
+ ]
+
+ // NOTE: Remember to leave 'toolbar' property with the default value (null).
+});
+
+
+
+
Current toolbar configuration
+
Below you can see editor with current toolbar definition.
+
+
+
+
+
+
Full toolbar configuration
+
Below you can see editor with full toolbar, generated automatically by the editor.
+
+ Note: To create editor instance with full toolbar you don't have to set anything.
+ Just leave toolbar and toolbarGroups with the default, null values.
+
+ This sample shows how to use the UI Color picker toolbar button to preview the skin color of the editor.
+ Note:The UI skin color feature depends on the CKEditor skin
+ compatibility. The Moono and Kama skins are examples of skins that work with it.
+
+ Note that textarea_id in the code above is the id attribute of
+ the <textarea> element to be replaced.
+
+
+ The allowedContent in the code above is set to true to disable content filtering.
+ Setting this option is not obligatory, but in full page mode there is a strong chance that one may want be able to freely enter any HTML content in source mode without any limitations.
+
+
+
+
+
+
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/samples/readonly.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/samples/readonly.html Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,73 @@
+
+
+
+
+
+ Using the CKEditor Read-Only API — CKEditor Sample
+
+
+
+
+
+
+ This sample shows how to use the
+ setReadOnly
+ API to put editor into the read-only state that makes it impossible for users to change the editor contents.
+
+
+ For details on how to create this setup check the source code of this sample page.
+
+
+
+
+
+
diff -r e2e952fcaf7c -r fb0041aa74d3 server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/samples/replacebyclass.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python2/django/renkanmanager/static/renkanmanager/lib/ckeditor/samples/replacebyclass.html Mon Jan 11 18:24:14 2016 +0100
@@ -0,0 +1,57 @@
+
+
+
+
+
+ Replace Textareas by Class Name — CKEditor Sample
+
+
+
+
+
+
+-------------------------------------------------------------------------------------------
+ CKEditor - Posted Data
+
+ We are sorry, but your Web server does not support the PHP language used in this script.
+
+ Please note that CKEditor can be used with any other server-side language than just PHP.
+ To save the content created with CKEditor you need to read the POST data on the server
+ side and write it to a file or the database.
+
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+-------------------------------------------------------------------------------------------
+
+
+ This sample shows how tab key navigation among editor instances is
+ affected by the tabIndex attribute from
+ the original page element. Use TAB key to move between the editors.
+
+ This sample shows how to automatically replace <textarea> elements
+ with a CKEditor instance with an option to change the color of its user interface.
+ Note:The UI skin color feature depends on the CKEditor skin
+ compatibility. The Moono and Kama skins are examples of skins that work with it.
+
+ This sample shows how to automatically replace <textarea> elements
+ with a CKEditor instance with an option to change the language of its user interface.
+
+
+ It pulls the language list from CKEditor _languages.js file that contains the list of supported languages and creates
+ a drop-down list that lets the user change the UI language.
+
+
+ By default, CKEditor automatically localizes the editor to the language of the user.
+ The UI language can be controlled with two configuration options:
+ language and
+
+ defaultLanguage. The defaultLanguage setting specifies the
+ default CKEditor language to be used when a localization suitable for user's settings is not available.
+
+
+ To specify the user interface language that will be used no matter what language is
+ specified in user's browser or operating system, set the language property:
+
+
+CKEDITOR.replace( 'textarea_id', {
+ // Load the German interface.
+ language: 'de'
+});
+
+ Note that textarea_id in the code above is the id attribute of
+ the <textarea> element to be replaced.
+
+ This sample shows how to configure CKEditor to output valid
+ XHTML 1.1 code.
+ Deprecated elements (<font>, <u>) or attributes
+ (size, face) will be replaced with XHTML compliant code.
+
+
+ To add a CKEditor instance outputting valid XHTML code, load the editor using a standard
+ JavaScript call and define CKEditor features to use the XHTML compliant elements and styles.
+
+
+ A snippet of the configuration code can be seen below; check the source of this page for
+ full definition:
+