From 3ee149958d5cf0e4e8caeb178dfc7498601a2035 Mon Sep 17 00:00:00 2001 From: Zatvor Date: Wed, 17 Jun 2015 14:30:07 +0300 Subject: [PATCH 01/48] Initial commit for new edge --- .gitignore | 8 - Gruntfile.js | 98 ---- README.md | 53 +- bower.json | 22 - dist/ember-couchdb-kit.js | 825 ---------------------------- dist/ember-couchdb-kit.min.js | 1 - example/Gruntfile.js | 47 -- example/README.md | 54 -- example/assets/bootstrap.min.css | 9 - example/assets/gh-fork-ribbon.css | 135 ----- example/index.html | 130 ----- example/package.json | 9 - example/src/app.js | 6 - example/src/controllers.js | 94 ---- example/src/models.js | 26 - example/src/routes.js | 87 --- example/src/views.js | 133 ----- package.json | 31 -- spec/attachment-adapter_spec.coffee | 66 --- spec/document-adapter_spec.coffee | 69 --- spec/env.coffee | 153 ------ spec/index.html | 23 - spec/revs-adapter_spec.coffee | 42 -- src/attachment-adapter.coffee | 118 ---- src/changes-feed.coffee | 88 --- src/document-adapter.coffee | 331 ----------- src/ember-couchdb-kit.coffee | 25 - src/revs-adapter.coffee | 94 ---- 28 files changed, 8 insertions(+), 2769 deletions(-) delete mode 100644 Gruntfile.js delete mode 100644 bower.json delete mode 100644 dist/ember-couchdb-kit.js delete mode 100644 dist/ember-couchdb-kit.min.js delete mode 100644 example/Gruntfile.js delete mode 100644 example/README.md delete mode 100755 example/assets/bootstrap.min.css delete mode 100644 example/assets/gh-fork-ribbon.css delete mode 100644 example/index.html delete mode 100644 example/package.json delete mode 100644 example/src/app.js delete mode 100644 example/src/controllers.js delete mode 100644 example/src/models.js delete mode 100644 example/src/routes.js delete mode 100644 example/src/views.js delete mode 100644 package.json delete mode 100644 spec/attachment-adapter_spec.coffee delete mode 100644 spec/document-adapter_spec.coffee delete mode 100644 spec/env.coffee delete mode 100644 spec/index.html delete mode 100644 spec/revs-adapter_spec.coffee delete mode 100644 src/attachment-adapter.coffee delete mode 100644 src/changes-feed.coffee delete mode 100644 src/document-adapter.coffee delete mode 100644 src/ember-couchdb-kit.coffee delete mode 100644 src/revs-adapter.coffee diff --git a/.gitignore b/.gitignore index 4872e02..62c15b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,3 @@ -.idea/ -vendor/bundle -.bundle -.rvmrc -bin/ node_modules/ bower_components/ tmp/ -.DS_Store -example/public -dist/ diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index d7db3d5..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,98 +0,0 @@ -'use strict'; - -module.exports = function (grunt) { - - require('load-grunt-tasks')(grunt); - - grunt.initConfig({ - coffee: { - compileSources: { - expand: true, - flatten: true, - src: ['src/*.coffee'], - dest: 'tmp/src/', - ext: '.js' - }, - compileSpecs: { - expand: true, - flatten: true, - src: ['spec/*.coffee', 'qunitspec/*.coffee'], - dest: 'tmp/spec/javascripts/', - ext: '.js' - } - }, - concat: { - options: { - separator: ';' - }, - dist: { - src: [ - 'tmp/src/ember-couchdb-kit.js', - 'tmp/src/document-adapter.js', - 'tmp/src/attachment-adapter.js', - 'tmp/src/revs-adapter.js', - 'tmp/src/registry.js', - 'tmp/src/changes-feed.js' - ], - dest: 'dist/ember-couchdb-kit.js' - } - }, - uglify: { - minify_distribution: { - files: { - 'dist/ember-couchdb-kit.min.js': ['dist/ember-couchdb-kit.js'] - } - } - }, - mkdir: { - all: { - options: { - create: ['tmp', 'tmp/src', 'tmp/spec'] - } - } - }, - qunit: { - local: { - options: { - urls: ['http://localhost:9997/spec/index.html'] - } - } - }, - connect: { - server: { - options: { - base: '.', - port: 9997, - keepalive: true - } - } - }, - watch: { - tests: { - files: ['src/*.coffee', 'spec/*.coffee', 'spec/index.html'], - tasks: ['test'] - } - }, - clean: ['dist/*.js', 'tmp/src/*.js', 'tmp/spec/*.js'] - }); - - grunt.registerTask('build', [ - 'clean', - 'mkdir', - 'coffee:compileSources', - 'coffee:compileSpecs', - 'concat:dist', - 'uglify:minify_distribution' - ]); - - grunt.registerTask('test', [ - 'build', - 'connect', - 'qunit' - ]); - - grunt.registerTask('dev', [ - 'test', - 'watch' - ]); -}; diff --git a/README.md b/README.md index 6f6bdf5..e02b643 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -![Bower version](http://img.shields.io/bower/v/ember-couchdb-kit.svg) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE) -ember-couchdb-kit -================= +#### ember-couchdb-kit + +An `ember-data` kit for Apache CouchDB. A collection of adapters to work with CouchDB documents, attachments, revisions, changes feed. Versions: @@ -13,49 +14,11 @@ Versions: * `v0.7.0` works with `ember rc.6.1` and `ember-data 0.13` -An `ember-data` kit for Apache CouchDB. A collection of adapters to work -with CouchDB documents, attachments, revisions, changes feed. - -We love a CouchDB and its RESTful and many other core things such as MVCC, attachments and `/_changes`. These all brings your data and application flow together as well. - -Inspired by [pangratz/ember-couchdb-adapter](https://github.com/pangratz/ember-couchdb-adapter) and contains many fixes and newbie features. - -There are some of these: - -* natural `find/create/deleteRecord` functions; -* ability to work with `views`; -* document's attachements designed as `hasMany` relationship; -* document's revisions designed as `belongsTo` and `hasMany` relationships; -* ability to work with `/_changes` feeds; - - -Usage -===== - -Check the usage example in `/example` directory. - -Ready to use as a regular JS assets ------------------------------------ - -An `ember-couchdb-kit` ships with compiled assets which is placed in `dist` directory. - - -Install with bower ----------------- - -``` -bower install ember-couchdb-kit -``` - -Contribution ------------- - +#### Contribution + See [CONTRIBUTING.md](CONTRIBUTING.md) -License -------- -`ember-couchdb-kit` source code is released under MIT-License. -Check [MIT-LICENSE](MIT-LICENSE) for more details. +#### License -[![Analytics](https://ga-beacon.appspot.com/UA-61065309-1/Zatvobor/ember-couchdb-kit/README)](https://github.com/igrigorik/ga-beacon) +`ember-couchdb-kit` source code is released under MIT-License. Check [MIT-LICENSE](MIT-LICENSE) for more details. diff --git a/bower.json b/bower.json deleted file mode 100644 index c2421fb..0000000 --- a/bower.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "ember-couchdb-kit", - "description": "An Ember.js adapter for Apache CouchDB", - "repository": { - "type": "git", - "url": "git://github.com/zatvobor/ember-couchdb-kit.git" - }, - "main": "dist/ember-couchdb-kit.min.js", - "ignore": [ - "example", - "spec", - "src", - "Gruntfile.js", - "package.json" - ], - "devDependencies": { - "jquery": "1.11.1", - "ember": "1.11.0", - "ember-data": "1.0.0-beta.15", - "qunit": "~1.17.1" - } -} diff --git a/dist/ember-couchdb-kit.js b/dist/ember-couchdb-kit.js deleted file mode 100644 index 185bf36..0000000 --- a/dist/ember-couchdb-kit.js +++ /dev/null @@ -1,825 +0,0 @@ -(function() { - window.EmberCouchDBKit = Ember.Namespace.create({ - VERSION: '1.0.x' - }); - - EmberCouchDBKit.sharedStore = (function() { - var _data; - _data = {}; - return { - add: function(type, key, value) { - return _data[type + ':' + key] = value; - }, - get: function(type, key) { - return _data[type + ':' + key]; - }, - remove: function(type, key) { - return delete _data[type + ':' + key]; - }, - mapRevIds: function(type, key) { - var _this = this; - return this.get(type, key)._revs_info.map(function(_rev) { - return "%@/%@".fmt(_this.get(type, key)._id, _rev.rev); - }); - }, - stopAll: function() { - var k, v, _results; - _results = []; - for (k in _data) { - v = _data[k]; - if (k.indexOf('changes_worker') === 0) { - _results.push(v.stop()); - } else { - _results.push(void 0); - } - } - return _results; - } - }; - })(); - -}).call(this); -;/* -@namespace EmberCouchDBKit -@class DocumentSerializer -@extends DS.RESTSerializer -*/ - - -(function() { - EmberCouchDBKit.DocumentSerializer = DS.RESTSerializer.extend({ - primaryKey: '_id', - normalize: function(type, hash, prop) { - this.normalizeId(hash); - this.normalizeAttachments(hash["_attachments"], type.typeKey, hash); - this.addHistoryId(hash); - this.normalizeUsingDeclaredMapping(type, hash); - this.normalizeAttributes(type, hash); - this.normalizeRelationships(type, hash); - if (this.normalizeHash && this.normalizeHash[prop]) { - return this.normalizeHash[prop](hash); - } - if (!hash) { - return hash; - } - this.applyTransforms(type, hash); - return hash; - }, - extractSingle: function(store, type, payload, id, requestType) { - return this._super(store, type, payload, id, requestType); - }, - serialize: function(record, options) { - return this._super(record, options); - }, - addHistoryId: function(hash) { - return hash.history = "%@/history".fmt(hash.id); - }, - normalizeAttachments: function(attachments, type, hash) { - var attachment, k, key, v, _attachments; - _attachments = []; - for (k in attachments) { - v = attachments[k]; - key = "" + hash._id + "/" + k; - attachment = { - id: key, - content_type: v.content_type, - digest: v.digest, - length: v.length, - stub: v.stub, - doc_id: hash._id, - rev: hash.rev, - file_name: k, - model_name: type, - revpos: v.revpos, - db: v.db - }; - EmberCouchDBKit.sharedStore.add('attachment', key, attachment); - _attachments.push(key); - } - return hash.attachments = _attachments; - }, - normalizeId: function(hash) { - return hash.id = hash["_id"] || hash["id"]; - }, - normalizeRelationships: function(type, hash) { - var key, payloadKey; - payloadKey = void 0; - key = void 0; - if (this.keyForRelationship) { - return type.eachRelationship((function(key, relationship) { - payloadKey = this.keyForRelationship(key, relationship.kind); - if (key === payloadKey) { - return; - } - hash[key] = hash[payloadKey]; - return delete hash[payloadKey]; - }), this); - } - }, - serializeBelongsTo: function(record, json, relationship) { - var attribute, belongsTo, key; - attribute = relationship.options.attribute || "id"; - key = relationship.key; - belongsTo = record.belongsTo(key); - if (Ember.isNone(belongsTo)) { - return; - } - json[key] = attribute === "id" ? belongsTo.id : belongsTo.attr(attribute); - if (relationship.options.polymorphic) { - return json[key + "_type"] = belongsTo.typeKey; - } - }, - serializeHasMany: function(record, json, relationship) { - var attribute, key, relationshipType; - attribute = relationship.options.attribute || "id"; - key = relationship.key; - relationshipType = record.type.determineRelationshipType(relationship); - switch (relationshipType) { - case "manyToNone": - case "manyToMany": - case "manyToOne": - return json[key] = record.hasMany(key).mapBy(attribute); - } - } - }); - - /* - - A `DocumentAdapter` should be used as a main adapter for working with models as a CouchDB documents. - - Let's consider: - - ```coffee - EmberApp.DocumentAdapter = EmberCouchDBKit.DocumentAdapter.extend({db: db, host: host}) - EmberApp.DocumentSerializer = EmberCouchDBKit.DocumentSerializer.extend() - - EmberApp.Document = DS.Model.extend - title: DS.attr('title') - type: DS.attr('string', {defaultValue: 'document'}) - ``` - - The following available operations: - - ```coffee - # GET /my_couchdb/:id - @get('store').find('document', id) - - # POST /my_couchdb - @get('store').createRecord('document', {title: "title"}).save() - - # update PUT /my_couchdb/:id - @get('store').find('document', id).then((document) -> - document.set('title', title) - document.save() - ) - - # DELETE /my_couchdb/:id - @get('store').find('document', id).deleteRecord().save() - ``` - - For more advanced tips and tricks, you should check available specs - - @namespace EmberCouchDBKit - @class DocumentAdapter - @extends DS.Adapter - */ - - - EmberCouchDBKit.DocumentAdapter = DS.Adapter.extend({ - defaultSerializer: '_default', - customTypeLookup: false, - typeViewName: "all", - buildURL: function() { - var host, namespace, url; - host = Ember.get(this, "host"); - namespace = Ember.get(this, "namespace"); - url = []; - if (host) { - url.push(host); - } - if (namespace) { - url.push(namespace); - } - url.push(this.get('db')); - url = url.join("/"); - if (!host) { - url = "/" + url; - } - return url; - }, - ajax: function(url, type, normalizeResponce, hash) { - return this._ajax('%@/%@'.fmt(this.buildURL(), url || ''), type, normalizeResponce, hash); - }, - _ajax: function(url, type, normalizeResponce, hash) { - var adapter; - if (hash == null) { - hash = {}; - } - adapter = this; - return new Ember.RSVP.Promise(function(resolve, reject) { - var headers; - if (url.split("/").pop() === "") { - url = url.substr(0, url.length - 1); - } - hash.url = url; - hash.type = type; - hash.dataType = 'json'; - hash.contentType = 'application/json; charset=utf-8'; - hash.context = adapter; - if (hash.data && type !== 'GET') { - hash.data = JSON.stringify(hash.data); - } - if (adapter.headers) { - headers = adapter.headers; - hash.beforeSend = function(xhr) { - return Ember.keys(headers).forEach(function(key) { - return xhr.setRequestHeader(key, headers[key]); - }); - }; - } - if (!hash.success) { - hash.success = function(json) { - var _modelJson; - _modelJson = normalizeResponce.call(adapter, json); - return Ember.run(null, resolve, _modelJson); - }; - } - hash.error = function(jqXHR, textStatus, errorThrown) { - if (jqXHR) { - jqXHR.then = null; - } - return Ember.run(null, reject, jqXHR); - }; - return Ember.$.ajax(hash); - }); - }, - _normalizeRevision: function(json) { - if (json && json._rev) { - json.rev = json._rev; - delete json._rev; - } - return json; - }, - shouldCommit: function(record, relationships) { - return this._super.apply(arguments); - }, - find: function(store, type, id) { - var normalizeResponce; - if (this._checkForRevision(id)) { - return this.findWithRev(store, type, id); - } else { - normalizeResponce = function(data) { - var _modelJson; - this._normalizeRevision(data); - _modelJson = {}; - _modelJson[type.typeKey] = data; - return _modelJson; - }; - return this.ajax(id, 'GET', normalizeResponce); - } - }, - findWithRev: function(store, type, id, hash) { - var normalizeResponce, url, _id, _ref, _rev; - _ref = id.split("/").slice(0, 2), _id = _ref[0], _rev = _ref[1]; - url = "%@?rev=%@".fmt(_id, _rev); - normalizeResponce = function(data) { - var _modelJson; - this._normalizeRevision(data); - _modelJson = {}; - data._id = id; - _modelJson[type.typeKey] = data; - return _modelJson; - }; - return this.ajax(url, 'GET', normalizeResponce, hash); - }, - findManyWithRev: function(store, type, ids) { - var docs, hash, key, self, - _this = this; - key = Ember.String.pluralize(type.typeKey); - self = this; - docs = {}; - docs[key] = []; - hash = { - async: false - }; - ids.forEach(function(id) { - var url, _id, _ref, _rev; - _ref = id.split("/").slice(0, 2), _id = _ref[0], _rev = _ref[1]; - url = "%@?rev=%@".fmt(_id, _rev); - url = '%@/%@'.fmt(_this.buildURL(), url); - hash.url = url; - hash.type = 'GET'; - hash.dataType = 'json'; - hash.contentType = 'application/json; charset=utf-8'; - hash.success = function(json) { - json._id = id; - self._normalizeRevision(json); - return docs[key].push(json); - }; - return Ember.$.ajax(hash); - }); - return docs; - }, - findMany: function(store, type, ids) { - var data, normalizeResponce; - if (this._checkForRevision(ids[0])) { - return this.findManyWithRev(store, type, ids); - } else { - data = { - include_docs: true, - keys: ids - }; - normalizeResponce = function(data) { - var json, - _this = this; - json = {}; - json[Ember.String.pluralize(type.typeKey)] = data.rows.getEach('doc').map(function(doc) { - return _this._normalizeRevision(doc); - }); - return json; - }; - return this.ajax('_all_docs?include_docs=true', 'POST', normalizeResponce, { - data: data - }); - } - }, - findQuery: function(store, type, query, modelArray) { - var designDoc, normalizeResponce; - designDoc = query.designDoc || this.get('designDoc'); - if (!query.options) { - query.options = {}; - } - query.options.include_docs = true; - normalizeResponce = function(data) { - var json, - _this = this; - json = {}; - json[designDoc] = data.rows.getEach('doc').map(function(doc) { - return _this._normalizeRevision(doc); - }); - return json; - }; - return this.ajax('_design/%@/_view/%@'.fmt(designDoc, query.viewName), 'GET', normalizeResponce, { - context: this, - data: query.options - }); - }, - findAll: function(store, type) { - var data, designDoc, normalizeResponce, typeString, typeViewName; - typeString = Ember.String.singularize(type.typeKey); - designDoc = this.get('designDoc') || typeString; - typeViewName = this.get('typeViewName'); - normalizeResponce = function(data) { - var json, - _this = this; - json = {}; - json[[Ember.String.pluralize(type.typeKey)]] = data.rows.getEach('doc').map(function(doc) { - return _this._normalizeRevision(doc); - }); - return json; - }; - data = { - include_docs: true, - key: '"' + typeString + '"' - }; - return this.ajax('_design/%@/_view/%@'.fmt(designDoc, typeViewName), 'GET', normalizeResponce, { - data: data - }); - }, - createRecord: function(store, type, record) { - var json; - json = store.serializerFor(type.typeKey).serialize(record._createSnapshot()); - return this._push(store, type, record, json); - }, - updateRecord: function(store, type, record) { - var json; - json = this.serialize(record, { - associations: true, - includeId: true - }); - if (record.get('attachments')) { - this._updateAttachmnets(record, json); - } - return this._push(store, type, record, json); - }, - deleteRecord: function(store, type, record) { - return this.ajax("%@?rev=%@".fmt(record.get('id'), record.get('_data.rev')), 'DELETE', (function() {}), {}); - }, - _updateAttachmnets: function(record, json) { - var _attachments; - _attachments = {}; - record.get('attachments').forEach(function(item) { - var attachment; - attachment = EmberCouchDBKit.sharedStore.get('attachment', item.get('id')); - return _attachments[item.get('file_name')] = { - content_type: attachment.content_type, - digest: attachment.digest, - length: attachment.length, - stub: attachment.stub, - revpos: attachment.revpos - }; - }); - json._attachments = _attachments; - delete json.attachments; - return delete json.history; - }, - _checkForRevision: function(id) { - return id.split("/").length > 1; - }, - _push: function(store, type, record, json) { - var id, method, normalizeResponce; - id = record.get('id') || ''; - method = record.get('id') ? 'PUT' : 'POST'; - if (record.get('_data.rev')) { - json._rev = record.get('_data.rev'); - } - normalizeResponce = function(data) { - var _data, _modelJson; - _data = json || {}; - this._normalizeRevision(data); - _modelJson = {}; - _modelJson[type.typeKey] = $.extend(_data, data); - return _modelJson; - }; - return this.ajax(id, method, normalizeResponce, { - data: json - }); - } - }); - -}).call(this); -;/* -@namespace EmberCouchDBKit -@class AttachmentSerializer -@extends DS.RESTSerializer -*/ - - -(function() { - EmberCouchDBKit.AttachmentSerializer = DS.RESTSerializer.extend({ - primaryKey: 'id', - normalize: function(type, hash) { - var rev, self; - self = this; - rev = hash._rev || hash.rev; - this.store.find(hash.model_name, hash.doc_id).then(function(document) { - if (document.get('_data.rev') !== rev) { - if (self.getIntRevision(document.get('_data.rev')) < self.getIntRevision(rev)) { - return document.set('_data.rev', rev); - } - } - }); - return this._super(type, hash); - }, - getIntRevision: function(revision) { - return parseInt(revision.split("-")[0]); - }, - normalizeId: function(hash) { - return hash.id = hash["_id"] || hash["id"]; - } - }); - - /* - An `AttachmentAdapter` is an object which manages document's attachements and used - as a main adapter for `Attachment` enabled models. - - Let's consider an usual use case: - ```coffee - App.Task = DS.Model.extend - title: DS.attr('string') - attachments: DS.hasMany('attachment', {async: true}) - - App.Attachment = DS.Model.extend - content_type: DS.attr('string') - length: DS.attr('number') - file_name: DS.attr('string') - db: DS.attr('string') - - task = @get('store').find('task', id) - attachments = task.get('attachments') - ``` - - For getting more details check `spec/coffeescripts/attachment-adapter_spec.coffee` file. - - @namespace EmberCouchDBKit - @class AttachmentAdapter - @extends DS.Adapter - */ - - - EmberCouchDBKit.AttachmentAdapter = DS.Adapter.extend({ - find: function(store, type, id) { - return new Ember.RSVP.Promise(function(resolve, reject) { - return Ember.run(null, resolve, { - attachment: EmberCouchDBKit.sharedStore.get('attachment', id) - }); - }); - }, - findMany: function(store, type, ids) { - var docs, - _this = this; - docs = ids.map(function(item) { - item = EmberCouchDBKit.sharedStore.get('attachment', item); - item.db = _this.get('db'); - return item; - }); - return new Ember.RSVP.Promise(function(resolve, reject) { - return Ember.run(null, resolve, { - attachments: docs - }); - }); - }, - createRecord: function(store, type, record) { - var adapter, url; - url = "%@/%@?rev=%@".fmt(this.buildURL(), record.get('id'), record.get('rev')); - adapter = this; - return new Ember.RSVP.Promise(function(resolve, reject) { - var data, request, - _this = this; - data = {}; - data.context = adapter; - request = new XMLHttpRequest(); - request.open('PUT', url, true); - request.setRequestHeader('Content-Type', record.get('content_type')); - adapter._updateUploadState(record, request); - request.onreadystatechange = function() { - var json; - if (request.readyState === 4 && (request.status === 201 || request.status === 200)) { - data = JSON.parse(request.response); - data.model_name = record.get('model_name'); - data.doc_id = record.get('doc_id'); - json = adapter.serialize(record, { - includeId: true - }); - delete data.id; - return Ember.run(null, resolve, { - attachment: $.extend(json, data) - }); - } - }; - return request.send(record.get('file')); - }); - }, - updateRecord: function(store, type, record) {}, - deleteRecord: function(store, type, record) { - return new Ember.RSVP.Promise(function(resolve, reject) { - return Ember.run(null, resolve, {}); - }); - }, - _updateUploadState: function(record, request) { - var view, - _this = this; - view = record.get('view'); - if (view) { - view.startUpload(); - return request.onprogress = function(oEvent) { - var percentComplete; - if (oEvent.lengthComputable) { - percentComplete = (oEvent.loaded / oEvent.total) * 100; - return view.updateUpload(percentComplete); - } - }; - } - }, - buildURL: function() { - var host, namespace, url; - host = Ember.get(this, "host"); - namespace = Ember.get(this, "namespace"); - url = []; - if (host) { - url.push(host); - } - if (namespace) { - url.push(namespace); - } - url.push(this.get('db')); - url = url.join("/"); - if (!host) { - url = "/" + url; - } - return url; - } - }); - -}).call(this); -;/* -@namespace EmberCouchDBKit -@class RevSerializer -@extends DS.RESTSerializer -*/ - - -(function() { - EmberCouchDBKit.RevSerializer = DS.RESTSerializer.extend({ - primaryKey: 'id', - normalize: function(type, hash, prop) { - this.normalizeRelationships(type, hash); - return this._super(type, hash, prop); - }, - extractId: function(type, hash) { - return hash._id || hash.id; - }, - normalizeRelationships: function(type, hash) { - return type.eachRelationship((function(key, relationship) { - if (relationship.kind === "belongsTo") { - hash[key] = EmberCouchDBKit.sharedStore.mapRevIds('revs', this.extractId(type, hash))[1]; - } - if (relationship.kind === "hasMany") { - return hash[key] = EmberCouchDBKit.sharedStore.mapRevIds('revs', this.extractId(type, hash)); - } - }), this); - } - }); - - /* - `RevAdapter` is an adapter which gets revisions info by distinct document and used - as a main adapter for history enabled models. - - Let's consider `belongsTo` relation which returns previous version of document: - ```coffee - App.Task = DS.Model.extend - title: DS.attr('string') - history: DS.belongsTo('history') - - - App.History = DS.Model.extend - # previous version of task entry - task: DS.belongsTo('task', {inverse: null}) - # list of all available versions of task entry - tasks: DS.hasMany('task', {inverse: null, async: true}) - ``` - - For getting more details check `spec/coffeescripts/revs-adapter_spec.coffee` file. - - @namespace EmberCouchDBKit - @class RevAdapter - @extends DS.Adapter - */ - - - EmberCouchDBKit.RevAdapter = DS.Adapter.extend({ - find: function(store, type, id) { - return this.ajax("%@?revs_info=true".fmt(id.split("/")[0]), 'GET', { - context: this - }, id); - }, - updateRecord: function(store, type, record) {}, - deleteRecord: function(store, type, record) {}, - ajax: function(url, type, hash, id) { - return this._ajax('%@/%@'.fmt(this.buildURL(), url || ''), type, hash, id); - }, - _ajax: function(url, type, hash, id) { - hash.url = url; - hash.type = type; - hash.dataType = 'json'; - hash.contentType = 'application/json; charset=utf-8'; - hash.context = this; - if (hash.data && type !== 'GET') { - hash.data = JSON.stringify(hash.data); - } - return new Ember.RSVP.Promise(function(resolve, reject) { - hash.success = function(data) { - EmberCouchDBKit.sharedStore.add('revs', id, data); - return Ember.run(null, resolve, { - history: { - id: id - } - }); - }; - return Ember.$.ajax(hash); - }); - }, - buildURL: function() { - var host, namespace, url; - host = Ember.get(this, "host"); - namespace = Ember.get(this, "namespace"); - url = []; - if (host) { - url.push(host); - } - if (namespace) { - url.push(namespace); - } - url.push(this.get('db')); - url = url.join("/"); - if (!host) { - url = "/" + url; - } - return url; - } - }); - -}).call(this); -;/* - - This module provides convinience for working with CouchDB's `/changes` feeds - - For instance: - - ```coffee - # Create feed with custom parameters - feed = EmberCouchDBKit.ChangesFeed.create({ db: 'docs', content: params }) - feed.longpoll(callback) - - # Start listening from the last sequence - self = @ - feed.fromTail((=> feed.longpoll(self.filter, self))) - - # Destroy feed listening - feed.stop().destroy() - ``` - -@namespace EmberCouchDBKit -@class ChangesFeed -@extends Ember.ObjectProxy -*/ - - -(function() { - EmberCouchDBKit.ChangesFeed = Ember.ObjectProxy.extend({ - content: {}, - longpoll: function() { - this.feed = 'longpoll'; - return this._ajax.apply(this, arguments); - }, - normal: function() { - this.feed = 'normal'; - return this._ajax.apply(this, arguments); - }, - continuous: function() { - this.feed = 'continuous'; - return this._ajax.apply(this, arguments); - }, - fromTail: function(callback) { - var _this = this; - return $.ajax({ - url: "%@%@/_changes?descending=true&limit=1".fmt(this._buildUrl(), this.get('db')), - dataType: 'json', - success: function(data) { - _this.set('since', data.last_seq); - if (callback) { - return callback.call(_this); - } - } - }); - }, - stop: function() { - this.set('stopTracking', true); - return this; - }, - start: function(callback) { - this.set('stopTracking', false); - return this.fromTail(callback); - }, - _ajax: function(callback, self) { - var _this = this; - return $.ajax({ - type: "GET", - url: this._makeRequestPath(), - dataType: 'json', - success: function(data) { - var _ref; - if (!_this.get('stopTracking')) { - if ((data != null ? (_ref = data.results) != null ? _ref.length : void 0 : void 0) && callback) { - callback.call(self, data.results); - } - return _this.set('since', data.last_seq); - } - }, - complete: function() { - if (!_this.get('stopTracking')) { - return setTimeout((function() { - return _this._ajax(callback, self); - }), 1000); - } - } - }); - }, - _buildUrl: function() { - var url; - url = this.get('host') || "/"; - if (url.substring(url.length - 1) !== "/") { - url += "/"; - } - return url; - }, - _makeRequestPath: function() { - var feed, params; - feed = this.feed || 'longpool'; - params = this._makeFeedParams(); - return "%@%@/_changes?feed=%@%@".fmt(this._buildUrl(), this.get('db'), feed, params); - }, - _makeFeedParams: function() { - var path, - _this = this; - path = ''; - ["include_docs", "limit", "descending", "heartbeat", "timeout", "filter", "filter_param", "style", "since"].forEach(function(param) { - if (_this.get(param)) { - return path += "&%@=%@".fmt(param, _this.get(param)); - } - }); - return path; - } - }); - -}).call(this); diff --git a/dist/ember-couchdb-kit.min.js b/dist/ember-couchdb-kit.min.js deleted file mode 100644 index bac05fd..0000000 --- a/dist/ember-couchdb-kit.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){window.EmberCouchDBKit=Ember.Namespace.create({VERSION:"1.0.x"}),EmberCouchDBKit.sharedStore=function(){var a;return a={},{add:function(b,c,d){return a[b+":"+c]=d},get:function(b,c){return a[b+":"+c]},remove:function(b,c){return delete a[b+":"+c]},mapRevIds:function(a,b){var c=this;return this.get(a,b)._revs_info.map(function(d){return"%@/%@".fmt(c.get(a,b)._id,d.rev)})},stopAll:function(){var b,c,d;d=[];for(b in a)c=a[b],d.push(0===b.indexOf("changes_worker")?c.stop():void 0);return d}}}()}).call(this),function(){EmberCouchDBKit.DocumentSerializer=DS.RESTSerializer.extend({primaryKey:"_id",normalize:function(a,b,c){return this.normalizeId(b),this.normalizeAttachments(b._attachments,a.typeKey,b),this.addHistoryId(b),this.normalizeUsingDeclaredMapping(a,b),this.normalizeAttributes(a,b),this.normalizeRelationships(a,b),this.normalizeHash&&this.normalizeHash[c]?this.normalizeHash[c](b):b?(this.applyTransforms(a,b),b):b},extractSingle:function(a,b,c,d,e){return this._super(a,b,c,d,e)},serialize:function(a,b){return this._super(a,b)},addHistoryId:function(a){return a.history="%@/history".fmt(a.id)},normalizeAttachments:function(a,b,c){var d,e,f,g,h;h=[];for(e in a)g=a[e],f=""+c._id+"/"+e,d={id:f,content_type:g.content_type,digest:g.digest,length:g.length,stub:g.stub,doc_id:c._id,rev:c.rev,file_name:e,model_name:b,revpos:g.revpos,db:g.db},EmberCouchDBKit.sharedStore.add("attachment",f,d),h.push(f);return c.attachments=h},normalizeId:function(a){return a.id=a._id||a.id},normalizeRelationships:function(a,b){var c,d;return d=void 0,c=void 0,this.keyForRelationship?a.eachRelationship(function(a,c){return d=this.keyForRelationship(a,c.kind),a!==d?(b[a]=b[d],delete b[d]):void 0},this):void 0},serializeBelongsTo:function(a,b,c){var d,e,f;return d=c.options.attribute||"id",f=c.key,e=a.belongsTo(f),Ember.isNone(e)?void 0:(b[f]="id"===d?e.id:e.attr(d),c.options.polymorphic?b[f+"_type"]=e.typeKey:void 0)},serializeHasMany:function(a,b,c){var d,e,f;switch(d=c.options.attribute||"id",e=c.key,f=a.type.determineRelationshipType(c)){case"manyToNone":case"manyToMany":case"manyToOne":return b[e]=a.hasMany(e).mapBy(d)}}}),EmberCouchDBKit.DocumentAdapter=DS.Adapter.extend({defaultSerializer:"_default",customTypeLookup:!1,typeViewName:"all",buildURL:function(){var a,b,c;return a=Ember.get(this,"host"),b=Ember.get(this,"namespace"),c=[],a&&c.push(a),b&&c.push(b),c.push(this.get("db")),c=c.join("/"),a||(c="/"+c),c},ajax:function(a,b,c,d){return this._ajax("%@/%@".fmt(this.buildURL(),a||""),b,c,d)},_ajax:function(a,b,c,d){var e;return null==d&&(d={}),e=this,new Ember.RSVP.Promise(function(f,g){var h;return""===a.split("/").pop()&&(a=a.substr(0,a.length-1)),d.url=a,d.type=b,d.dataType="json",d.contentType="application/json; charset=utf-8",d.context=e,d.data&&"GET"!==b&&(d.data=JSON.stringify(d.data)),e.headers&&(h=e.headers,d.beforeSend=function(a){return Ember.keys(h).forEach(function(b){return a.setRequestHeader(b,h[b])})}),d.success||(d.success=function(a){var b;return b=c.call(e,a),Ember.run(null,f,b)}),d.error=function(a){return a&&(a.then=null),Ember.run(null,g,a)},Ember.$.ajax(d)})},_normalizeRevision:function(a){return a&&a._rev&&(a.rev=a._rev,delete a._rev),a},shouldCommit:function(){return this._super.apply(arguments)},find:function(a,b,c){var d;return this._checkForRevision(c)?this.findWithRev(a,b,c):(d=function(a){var c;return this._normalizeRevision(a),c={},c[b.typeKey]=a,c},this.ajax(c,"GET",d))},findWithRev:function(a,b,c,d){var e,f,g,h,i;return h=c.split("/").slice(0,2),g=h[0],i=h[1],f="%@?rev=%@".fmt(g,i),e=function(a){var d;return this._normalizeRevision(a),d={},a._id=c,d[b.typeKey]=a,d},this.ajax(f,"GET",e,d)},findManyWithRev:function(a,b,c){var d,e,f,g,h=this;return f=Ember.String.pluralize(b.typeKey),g=this,d={},d[f]=[],e={async:!1},c.forEach(function(a){var b,c,i,j;return i=a.split("/").slice(0,2),c=i[0],j=i[1],b="%@?rev=%@".fmt(c,j),b="%@/%@".fmt(h.buildURL(),b),e.url=b,e.type="GET",e.dataType="json",e.contentType="application/json; charset=utf-8",e.success=function(b){return b._id=a,g._normalizeRevision(b),d[f].push(b)},Ember.$.ajax(e)}),d},findMany:function(a,b,c){var d,e;return this._checkForRevision(c[0])?this.findManyWithRev(a,b,c):(d={include_docs:!0,keys:c},e=function(a){var c,d=this;return c={},c[Ember.String.pluralize(b.typeKey)]=a.rows.getEach("doc").map(function(a){return d._normalizeRevision(a)}),c},this.ajax("_all_docs?include_docs=true","POST",e,{data:d}))},findQuery:function(a,b,c){var d,e;return d=c.designDoc||this.get("designDoc"),c.options||(c.options={}),c.options.include_docs=!0,e=function(a){var b,c=this;return b={},b[d]=a.rows.getEach("doc").map(function(a){return c._normalizeRevision(a)}),b},this.ajax("_design/%@/_view/%@".fmt(d,c.viewName),"GET",e,{context:this,data:c.options})},findAll:function(a,b){var c,d,e,f,g;return f=Ember.String.singularize(b.typeKey),d=this.get("designDoc")||f,g=this.get("typeViewName"),e=function(a){var c,d=this;return c={},c[[Ember.String.pluralize(b.typeKey)]]=a.rows.getEach("doc").map(function(a){return d._normalizeRevision(a)}),c},c={include_docs:!0,key:'"'+f+'"'},this.ajax("_design/%@/_view/%@".fmt(d,g),"GET",e,{data:c})},createRecord:function(a,b,c){var d;return d=a.serializerFor(b.typeKey).serialize(c._createSnapshot()),this._push(a,b,c,d)},updateRecord:function(a,b,c){var d;return d=this.serialize(c,{associations:!0,includeId:!0}),c.get("attachments")&&this._updateAttachmnets(c,d),this._push(a,b,c,d)},deleteRecord:function(a,b,c){return this.ajax("%@?rev=%@".fmt(c.get("id"),c.get("_data.rev")),"DELETE",function(){},{})},_updateAttachmnets:function(a,b){var c;return c={},a.get("attachments").forEach(function(a){var b;return b=EmberCouchDBKit.sharedStore.get("attachment",a.get("id")),c[a.get("file_name")]={content_type:b.content_type,digest:b.digest,length:b.length,stub:b.stub,revpos:b.revpos}}),b._attachments=c,delete b.attachments,delete b.history},_checkForRevision:function(a){return a.split("/").length>1},_push:function(a,b,c,d){var e,f,g;return e=c.get("id")||"",f=c.get("id")?"PUT":"POST",c.get("_data.rev")&&(d._rev=c.get("_data.rev")),g=function(a){var c,e;return c=d||{},this._normalizeRevision(a),e={},e[b.typeKey]=$.extend(c,a),e},this.ajax(e,f,g,{data:d})}})}.call(this),function(){EmberCouchDBKit.AttachmentSerializer=DS.RESTSerializer.extend({primaryKey:"id",normalize:function(a,b){var c,d;return d=this,c=b._rev||b.rev,this.store.find(b.model_name,b.doc_id).then(function(a){return a.get("_data.rev")!==c&&d.getIntRevision(a.get("_data.rev")).caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}button,input,select[multiple],textarea{background-image:none}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}.img-responsive{display:block;height:auto;max-width:100%}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16.099999999999998px;font-weight:200;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#428bca}.text-warning{color:#c09853}.text-danger{color:#b94a48}.text-success{color:#468847}.text-info{color:#3a87ad}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{margin-top:20px;margin-bottom:10px}h4,h5,h6{margin-top:10px;margin-bottom:10px}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}h1 small,.h1 small{font-size:24px}h2 small,.h2 small{font-size:18px}h3 small,.h3 small,h4 small,.h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt,dd{line-height:1.428571429}dt{font-weight:bold}dd{margin-left:0}@media(min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small{display:block;line-height:1.428571429;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:1.428571429}code,pre{font-family:Monaco,Menlo,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;white-space:nowrap;background-color:#f9f2f4;border-radius:4px}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.row{margin-right:-15px;margin-left:-15px}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11{float:left}.col-xs-1{width:8.333333333333332%}.col-xs-2{width:16.666666666666664%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333333333%}.col-xs-5{width:41.66666666666667%}.col-xs-6{width:50%}.col-xs-7{width:58.333333333333336%}.col-xs-8{width:66.66666666666666%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333333334%}.col-xs-11{width:91.66666666666666%}.col-xs-12{width:100%}@media(min-width:768px){.container{max-width:750px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11{float:left}.col-sm-1{width:8.333333333333332%}.col-sm-2{width:16.666666666666664%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333333333%}.col-sm-5{width:41.66666666666667%}.col-sm-6{width:50%}.col-sm-7{width:58.333333333333336%}.col-sm-8{width:66.66666666666666%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333333334%}.col-sm-11{width:91.66666666666666%}.col-sm-12{width:100%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-11{left:91.66666666666666%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-11{margin-left:91.66666666666666%}}@media(min-width:992px){.container{max-width:970px}.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11{float:left}.col-md-1{width:8.333333333333332%}.col-md-2{width:16.666666666666664%}.col-md-3{width:25%}.col-md-4{width:33.33333333333333%}.col-md-5{width:41.66666666666667%}.col-md-6{width:50%}.col-md-7{width:58.333333333333336%}.col-md-8{width:66.66666666666666%}.col-md-9{width:75%}.col-md-10{width:83.33333333333334%}.col-md-11{width:91.66666666666666%}.col-md-12{width:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.333333333333332%}.col-md-push-2{left:16.666666666666664%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333333333%}.col-md-push-5{left:41.66666666666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.333333333333336%}.col-md-push-8{left:66.66666666666666%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333333334%}.col-md-push-11{left:91.66666666666666%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-11{right:91.66666666666666%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-11{margin-left:91.66666666666666%}}@media(min-width:1200px){.container{max-width:1170px}.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11{float:left}.col-lg-1{width:8.333333333333332%}.col-lg-2{width:16.666666666666664%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333333333%}.col-lg-5{width:41.66666666666667%}.col-lg-6{width:50%}.col-lg-7{width:58.333333333333336%}.col-lg-8{width:66.66666666666666%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333333334%}.col-lg-11{width:91.66666666666666%}.col-lg-12{width:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-11{left:91.66666666666666%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-offset-0{margin-left:0}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-11{margin-left:91.66666666666666%}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table thead>tr>th,.table tbody>tr>th,.table tfoot>tr>th,.table thead>tr>td,.table tbody>tr>td,.table tfoot>tr>td{padding:8px;line-height:1.428571429;vertical-align:top;border-top:1px solid #ddd}.table thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table caption+thead tr:first-child th,.table colgroup+thead tr:first-child th,.table thead:first-child tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed thead>tr>th,.table-condensed tbody>tr>th,.table-condensed tfoot>tr>th,.table-condensed thead>tr>td,.table-condensed tbody>tr>td,.table-condensed tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*="col-"]{display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8;border-color:#d6e9c6}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td{background-color:#d0e9c6;border-color:#c9e2b3}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede;border-color:#eed3d7}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td{background-color:#ebcccc;border-color:#e6c1c7}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3;border-color:#fbeed5}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td{background-color:#faf2cc;border-color:#f8e5be}@media(max-width:768px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:scroll;overflow-y:hidden;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0;background-color:#fff}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>thead>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>thead>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}select[multiple],select[size]{height:auto}select optgroup{font-family:inherit;font-size:inherit;font-style:inherit}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{height:auto}.form-control:-moz-placeholder{color:#999}.form-control::-moz-placeholder{color:#999}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee}textarea.form-control{height:auto}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;padding-left:20px;margin-top:10px;margin-bottom:10px;vertical-align:middle}.radio label,.checkbox label{display:inline;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:normal;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm{height:auto}.input-lg{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:45px;line-height:45px}textarea.input-lg{height:auto}.has-warning .help-block,.has-warning .control-label{color:#c09853}.has-warning .form-control{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.has-warning .input-group-addon{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.has-error .help-block,.has-error .control-label{color:#b94a48}.has-error .form-control{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.has-error .input-group-addon{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.has-success .help-block,.has-success .control-label{color:#468847}.has-success .form-control{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.has-success .input-group-addon{color:#468847;background-color:#dff0d8;border-color:#468847}.form-control-static{padding-top:7px;margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media(min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block}.form-inline .radio,.form-inline .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}@media(min-width:768px){.form-horizontal .control-label{text-align:right}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:normal;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;border:1px solid transparent;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-link{font-weight:normal;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-xs{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';-webkit-font-smoothing:antialiased;font-style:normal;font-weight:normal;line-height:1}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-print:before{content:"\e045"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-briefcase:before{content:"\1f4bc"}.glyphicon-calendar:before{content:"\1f4c5"}.glyphicon-pushpin:before{content:"\1f4cc"}.glyphicon-paperclip:before{content:"\1f4ce"}.glyphicon-camera:before{content:"\1f4f7"}.glyphicon-lock:before{content:"\1f512"}.glyphicon-bell:before{content:"\1f514"}.glyphicon-bookmark:before{content:"\1f516"}.glyphicon-fire:before{content:"\1f525"}.glyphicon-wrench:before{content:"\1f527"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid #000;border-right:4px solid transparent;border-bottom:0 dotted;border-left:4px solid transparent;content:""}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.428571429;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#fff;text-decoration:none;background-color:#428bca}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.428571429;color:#999}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0 dotted;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media(min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}}.btn-default .caret{border-top-color:#333}.btn-primary .caret,.btn-success .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret{border-top-color:#fff}.dropup .btn-default .caret{border-bottom-color:#333}.dropup .btn-primary .caret,.dropup .btn-success .caret,.dropup .btn-warning .caret,.dropup .btn-danger .caret,.dropup .btn-info .caret{border-bottom-color:#fff}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group{float:left}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group,.btn-toolbar>.btn-group+.btn-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group-xs>.btn{padding:5px 10px;padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-bottom-left-radius:4px;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child>.btn:last-child,.btn-group-vertical>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;border-collapse:separate;table-layout:fixed}.btn-group-justified .btn{display:table-cell;float:none;width:1%}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group.col{float:none;padding-right:0;padding-left:0}.input-group .form-control{width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:45px;line-height:45px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-4px}.input-group-btn>.btn:hover,.input-group-btn>.btn:active{z-index:2}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.428571429;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center}@media(min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}}.nav-tabs.nav-justified>li>a{margin-right:0;border-bottom:1px solid #ddd}.nav-tabs.nav-justified>.active>a{border-bottom-color:#fff}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:5px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center}@media(min-width:768px){.nav-justified>li{display:table-cell;width:1%}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-bottom:1px solid #ddd}.nav-tabs-justified>.active>a{border-bottom-color:#fff}.tabbable:before,.tabbable:after{display:table;content:" "}.tabbable:after{clear:both}.tabbable:before,.tabbable:after{display:table;content:" "}.tabbable:after{clear:both}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.nav .caret{border-top-color:#428bca;border-bottom-color:#428bca}.nav a:hover .caret{border-top-color:#2a6496;border-bottom-color:#2a6496}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;z-index:1000;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}@media(min-width:768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}@media(min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media(min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-collapse .navbar-nav.navbar-left:first-child{margin-left:-15px}.navbar-collapse .navbar-nav.navbar-right:last-child{margin-right:-15px}.navbar-collapse .navbar-text:last-child{margin-right:0}}.container>.navbar-header,.container>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media(min-width:768px){.container>.navbar-header,.container>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{border-width:0 0 1px}@media(min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;border-width:0 0 1px}@media(min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;z-index:1030}.navbar-fixed-bottom{bottom:0;margin-bottom:0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media(min-width:768px){.navbar>.container .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:4px}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media(min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media(max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media(min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}@media(min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}@media(min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{float:none;margin-left:0}}@media(max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media(min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-nav.pull-right>li>.dropdown-menu,.navbar-nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-text{float:left;margin-top:15px;margin-bottom:15px}@media(min-width:768px){.navbar-text{margin-right:15px;margin-left:15px}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#ccc}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e6e6e6}.navbar-default .navbar-nav>.dropdown>a:hover .caret,.navbar-default .navbar-nav>.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.open>a .caret,.navbar-default .navbar-nav>.open>a:hover .caret,.navbar-default .navbar-nav>.open>a:focus .caret{border-top-color:#555;border-bottom-color:#555}.navbar-default .navbar-nav>.dropdown>a .caret{border-top-color:#777;border-bottom-color:#777}@media(max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-nav>.dropdown>a .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .navbar-nav>.open>a .caret,.navbar-inverse .navbar-nav>.open>a:hover .caret,.navbar-inverse .navbar-nav>.open>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}@media(max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.428571429;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#eee}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#999;border-radius:10px}.badge:empty{display:none}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.btn .badge{position:relative;top:-1px}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;font-size:21px;font-weight:200;line-height:2.1428571435;color:inherit;background-color:#eee}.jumbotron h1{line-height:1;color:inherit}.jumbotron p{line-height:1.4}.container .jumbotron{border-radius:6px}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1{font-size:63px}}.thumbnail{display:inline-block;display:block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img{display:block;height:auto;max-width:100%}a.thumbnail:hover,a.thumbnail:focus{border-color:#428bca}.thumbnail>img{margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#356635}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#2d6987}.alert-warning{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.alert-warning hr{border-top-color:#f8e5be}.alert-warning .alert-link{color:#a47e3c}.alert-danger{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger hr{border-top-color:#e6c1c7}.alert-danger .alert-link{color:#953b39}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0}.panel>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.list-group .list-group-item:last-child{border-bottom:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table{margin-bottom:0}.panel>.panel-body+.table{border-top:1px solid #ddd}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-title{margin-top:0;margin-bottom:0;font-size:16px}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-group .panel{margin-bottom:0;overflow:hidden;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-warning{border-color:#fbeed5}.panel-warning>.panel-heading{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#fbeed5}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#fbeed5}.panel-danger{border-color:#eed3d7}.panel-danger>.panel-heading{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#eed3d7}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#eed3d7}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}body.modal-open,.modal-open .navbar-fixed-top,.modal-open .navbar-fixed-bottom{margin-right:15px}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:auto;overflow-y:scroll}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{z-index:1050;width:auto;padding:10px;margin-right:auto;margin-left:auto}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1030;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{min-height:16.428571429px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.428571429}.modal-body{position:relative;padding:20px}.modal-footer{padding:19px 20px 20px;margin-top:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media screen and (min-width:768px){.modal-dialog{right:auto;left:50%;width:600px;padding-top:30px;padding-bottom:30px}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}}.tooltip{position:absolute;z-index:1030;display:block;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0;content:" "}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0;content:" "}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0;content:" "}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0;content:" "}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;height:auto;max-width:100%;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);opacity:.5;filter:alpha(opacity=50)}.carousel-control.left{background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.5)),to(rgba(0,0,0,0.0001)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.5) 0),color-stop(rgba(0,0,0,0.0001) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000',endColorstr='#00000000',GradientType=1)}.carousel-control.right{right:0;left:auto;background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.0001)),to(rgba(0,0,0,0.5)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.0001) 0),color-stop(rgba(0,0,0,0.5) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000',endColorstr='#80000000',GradientType=1)}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;left:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix:after{clear:both}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.affix{position:fixed}@-ms-viewport{width:device-width}@media screen and (max-width:400px){@-ms-viewport{width:320px}}.hidden{display:none!important;visibility:hidden!important}.visible-xs{display:none!important}tr.visible-xs{display:none!important}th.visible-xs,td.visible-xs{display:none!important}@media(max-width:767px){.visible-xs{display:block!important}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-xs.visible-sm{display:block!important}tr.visible-xs.visible-sm{display:table-row!important}th.visible-xs.visible-sm,td.visible-xs.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-xs.visible-md{display:block!important}tr.visible-xs.visible-md{display:table-row!important}th.visible-xs.visible-md,td.visible-xs.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-xs.visible-lg{display:block!important}tr.visible-xs.visible-lg{display:table-row!important}th.visible-xs.visible-lg,td.visible-xs.visible-lg{display:table-cell!important}}.visible-sm{display:none!important}tr.visible-sm{display:none!important}th.visible-sm,td.visible-sm{display:none!important}@media(max-width:767px){.visible-sm.visible-xs{display:block!important}tr.visible-sm.visible-xs{display:table-row!important}th.visible-sm.visible-xs,td.visible-sm.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-sm{display:block!important}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-sm.visible-md{display:block!important}tr.visible-sm.visible-md{display:table-row!important}th.visible-sm.visible-md,td.visible-sm.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-sm.visible-lg{display:block!important}tr.visible-sm.visible-lg{display:table-row!important}th.visible-sm.visible-lg,td.visible-sm.visible-lg{display:table-cell!important}}.visible-md{display:none!important}tr.visible-md{display:none!important}th.visible-md,td.visible-md{display:none!important}@media(max-width:767px){.visible-md.visible-xs{display:block!important}tr.visible-md.visible-xs{display:table-row!important}th.visible-md.visible-xs,td.visible-md.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-md.visible-sm{display:block!important}tr.visible-md.visible-sm{display:table-row!important}th.visible-md.visible-sm,td.visible-md.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-md{display:block!important}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-md.visible-lg{display:block!important}tr.visible-md.visible-lg{display:table-row!important}th.visible-md.visible-lg,td.visible-md.visible-lg{display:table-cell!important}}.visible-lg{display:none!important}tr.visible-lg{display:none!important}th.visible-lg,td.visible-lg{display:none!important}@media(max-width:767px){.visible-lg.visible-xs{display:block!important}tr.visible-lg.visible-xs{display:table-row!important}th.visible-lg.visible-xs,td.visible-lg.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-lg.visible-sm{display:block!important}tr.visible-lg.visible-sm{display:table-row!important}th.visible-lg.visible-sm,td.visible-lg.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-lg.visible-md{display:block!important}tr.visible-lg.visible-md{display:table-row!important}th.visible-lg.visible-md,td.visible-lg.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-lg{display:block!important}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}.hidden-xs{display:block!important}tr.hidden-xs{display:table-row!important}th.hidden-xs,td.hidden-xs{display:table-cell!important}@media(max-width:767px){.hidden-xs{display:none!important}tr.hidden-xs{display:none!important}th.hidden-xs,td.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-xs.hidden-sm{display:none!important}tr.hidden-xs.hidden-sm{display:none!important}th.hidden-xs.hidden-sm,td.hidden-xs.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-xs.hidden-md{display:none!important}tr.hidden-xs.hidden-md{display:none!important}th.hidden-xs.hidden-md,td.hidden-xs.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-xs.hidden-lg{display:none!important}tr.hidden-xs.hidden-lg{display:none!important}th.hidden-xs.hidden-lg,td.hidden-xs.hidden-lg{display:none!important}}.hidden-sm{display:block!important}tr.hidden-sm{display:table-row!important}th.hidden-sm,td.hidden-sm{display:table-cell!important}@media(max-width:767px){.hidden-sm.hidden-xs{display:none!important}tr.hidden-sm.hidden-xs{display:none!important}th.hidden-sm.hidden-xs,td.hidden-sm.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}tr.hidden-sm{display:none!important}th.hidden-sm,td.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-sm.hidden-md{display:none!important}tr.hidden-sm.hidden-md{display:none!important}th.hidden-sm.hidden-md,td.hidden-sm.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-sm.hidden-lg{display:none!important}tr.hidden-sm.hidden-lg{display:none!important}th.hidden-sm.hidden-lg,td.hidden-sm.hidden-lg{display:none!important}}.hidden-md{display:block!important}tr.hidden-md{display:table-row!important}th.hidden-md,td.hidden-md{display:table-cell!important}@media(max-width:767px){.hidden-md.hidden-xs{display:none!important}tr.hidden-md.hidden-xs{display:none!important}th.hidden-md.hidden-xs,td.hidden-md.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-md.hidden-sm{display:none!important}tr.hidden-md.hidden-sm{display:none!important}th.hidden-md.hidden-sm,td.hidden-md.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}tr.hidden-md{display:none!important}th.hidden-md,td.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-md.hidden-lg{display:none!important}tr.hidden-md.hidden-lg{display:none!important}th.hidden-md.hidden-lg,td.hidden-md.hidden-lg{display:none!important}}.hidden-lg{display:block!important}tr.hidden-lg{display:table-row!important}th.hidden-lg,td.hidden-lg{display:table-cell!important}@media(max-width:767px){.hidden-lg.hidden-xs{display:none!important}tr.hidden-lg.hidden-xs{display:none!important}th.hidden-lg.hidden-xs,td.hidden-lg.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-lg.hidden-sm{display:none!important}tr.hidden-lg.hidden-sm{display:none!important}th.hidden-lg.hidden-sm,td.hidden-lg.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-lg.hidden-md{display:none!important}tr.hidden-lg.hidden-md{display:none!important}th.hidden-lg.hidden-md,td.hidden-lg.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-lg{display:none!important}tr.hidden-lg{display:none!important}th.hidden-lg,td.hidden-lg{display:none!important}}.visible-print{display:none!important}tr.visible-print{display:none!important}th.visible-print,td.visible-print{display:none!important}@media print{.visible-print{display:block!important}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}.hidden-print{display:none!important}tr.hidden-print{display:none!important}th.hidden-print,td.hidden-print{display:none!important}} \ No newline at end of file diff --git a/example/assets/gh-fork-ribbon.css b/example/assets/gh-fork-ribbon.css deleted file mode 100644 index 54c45bc..0000000 --- a/example/assets/gh-fork-ribbon.css +++ /dev/null @@ -1,135 +0,0 @@ -/* Left will inherit from right (so we don't need to duplicate code) */ -.github-fork-ribbon { - /* The right and left classes determine the side we attach our banner to */ - position: absolute; - - /* Add a bit of padding to give some substance outside the "stitching" */ - padding: 2px 0; - - /* Set the base colour */ - background-color: #a00; - - /* Set a gradient: transparent black at the top to almost-transparent black at the bottom */ - background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.15))); - background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)); - background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)); - background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)); - background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)); - background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)); - - /* Add a drop shadow */ - -webkit-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5); - -moz-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5); - box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5); - - z-index: 9999; - pointer-events: auto; -} - -.github-fork-ribbon a, -.github-fork-ribbon a:hover { - /* Set the font */ - font: 700 13px "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #fff; - - /* Set the text properties */ - text-decoration: none; - text-shadow: 0 -1px rgba(0, 0, 0, 0.5); - text-align: center; - - /* Set the geometry. If you fiddle with these you'll also need - to tweak the top and right values in .github-fork-ribbon. */ - width: 200px; - line-height: 20px; - - /* Set the layout properties */ - display: inline-block; - padding: 2px 0; - - /* Add "stitching" effect */ - border-width: 1px 0; - border-style: dotted; - border-color: #fff; - border-color: rgba(255, 255, 255, 0.7); -} - -.github-fork-ribbon-wrapper { - width: 150px; - height: 150px; - position: absolute; - overflow: hidden; - top: 0; - z-index: 9999; - pointer-events: none; -} - -.github-fork-ribbon-wrapper.fixed { - position: fixed; -} - -.github-fork-ribbon-wrapper.left { - left: 0; -} - -.github-fork-ribbon-wrapper.right { - right: 0; -} - -.github-fork-ribbon-wrapper.left-bottom { - position: fixed; - top: inherit; - bottom: 0; - left: 0; -} - -.github-fork-ribbon-wrapper.right-bottom { - position: fixed; - top: inherit; - bottom: 0; - right: 0; -} - -.github-fork-ribbon-wrapper.right .github-fork-ribbon { - top: 42px; - right: -43px; - - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); -} - -.github-fork-ribbon-wrapper.left .github-fork-ribbon { - top: 42px; - left: -43px; - - -webkit-transform: rotate(-45deg); - -moz-transform: rotate(-45deg); - -ms-transform: rotate(-45deg); - -o-transform: rotate(-45deg); - transform: rotate(-45deg); -} - - -.github-fork-ribbon-wrapper.left-bottom .github-fork-ribbon { - top: 80px; - left: -43px; - - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); -} - -.github-fork-ribbon-wrapper.right-bottom .github-fork-ribbon { - top: 80px; - right: -43px; - - -webkit-transform: rotate(-45deg); - -moz-transform: rotate(-45deg); - -ms-transform: rotate(-45deg); - -o-transform: rotate(-45deg); - transform: rotate(-45deg); -} diff --git a/example/index.html b/example/index.html deleted file mode 100644 index 60880d6..0000000 --- a/example/index.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - Ember CouchDB Kit In Action - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/example/package.json b/example/package.json deleted file mode 100644 index 9822930..0000000 --- a/example/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "example-app", - "devDependencies": { - "grunt": "0.4.4", - "grunt-contrib-copy": "0.5.0", - "grunt-contrib-concat": "0.4.0", - "grunt-express": "1.2.1" - } -} \ No newline at end of file diff --git a/example/src/app.js b/example/src/app.js deleted file mode 100644 index 470832b..0000000 --- a/example/src/app.js +++ /dev/null @@ -1,6 +0,0 @@ - -var App = Ember.Application.create(); - -App.Boards = ['common', 'intermediate', 'advanced']; - -App.Host = "http://ember-couchdb-kit.roundscope.pw:5984"; diff --git a/example/src/controllers.js b/example/src/controllers.js deleted file mode 100644 index ac69ff1..0000000 --- a/example/src/controllers.js +++ /dev/null @@ -1,94 +0,0 @@ - -// Controllers - -App.IndexController = Ember.Controller.extend({ - - content: Ember.computed.alias('position.issues'), - - actions: { - createIssue: function(text) { - var self = this; - var issue = this.get('store').createRecord('issue', {text: text}); - issue.save().then(function(issue) { - if (self.get('position.issues.isLoaded')){ - self.get('position.issues').pushObject(issue); - self.get('position').save(); - } - else{ - self.get('position.issues').then(function(issues){ - self.get('position.issues').pushObject(issue); - self.get('position').save(); - }); - } - }); - }, - - saveIssue: function(model) { - model.save(); - }, - - deleteIssue: function(issue) { - var self = this; - self.get('position.issues').removeObject(issue); - issue.deleteRecord(); - issue.save().then(function(){ - self.get('position').save() - }) - }, - - addAttachment: function(files, model){ - this._actions._addAttachment(0, files, files.length, model, this) - }, - - _addAttachment: function(count, files, size, model, self){ - file = files[count]; - attachmentId = "%@/%@".fmt(model.id, file.name); - - params = { - doc_id: model.id, - model_name: App.Issue, - rev: model._data.rev, - id: attachmentId, - file: file, - content_type: file.type, - length: file.size, - file_name: file.name - } - - var attachment = self.get('store').createRecord('attachment', params); - attachment.save().then(function() { - model.get('attachments').pushObject(attachment); - model.reload(); - count = count + 1; - if(count < size){ - self._actions._addAttachment(count, files, size, model, self); - } - }); - }, - - deleteAttachment: function(attachment){ - attachment.deleteRecord(); - attachment.save(); - }, - - dropIssue: function(viewController, viewModel, thisModel) { - var position = this.get('content').toArray().indexOf(thisModel); - if (position === -1) { position = 0 } - viewController.get('content').removeObject(viewModel); - - var self = this; - if(viewController.name !== this.name){ - viewController.get('position').save().then(function() { - self.get('position').reload(); - }); - } - - this.get('content').insertAt(position, viewModel); - this.get('position').save(); - } - } -}); - -App.CommonController = App.IndexController.extend({ name: 'common' }); -App.IntermediateController = App.IndexController.extend({ name: 'intermediate' }); -App.AdvancedController = App.IndexController.extend({ name: 'advanced' }); diff --git a/example/src/models.js b/example/src/models.js deleted file mode 100644 index eac1f61..0000000 --- a/example/src/models.js +++ /dev/null @@ -1,26 +0,0 @@ - -// Models - -App.ApplicationAdapter = EmberCouchDBKit.DocumentAdapter.extend({db: 'boards', host: App.Host}); -App.ApplicationSerializer = EmberCouchDBKit.DocumentSerializer.extend(); - -App.AttachmentAdapter = EmberCouchDBKit.AttachmentAdapter.extend({db: 'boards', host: App.Host}); -App.AttachmentSerializer = EmberCouchDBKit.AttachmentSerializer.extend(); - -App.Issue = DS.Model.extend({ - text: DS.attr('string'), - type: DS.attr('string', {defaultValue: 'issue'}), - attachments: DS.hasMany('attachment', {async: true}) -}); - -App.Attachment = DS.Model.extend({ - content_type: DS.attr('string'), - length: DS.attr('number'), - file_name: DS.attr('string'), - db: DS.attr('string', {defaultValue: 'boards'}) -}); - -App.Position = DS.Model.extend({ - issues: DS.hasMany('issue', {async: true}), - type: DS.attr('string', {defaultValue: 'position'}) -}); diff --git a/example/src/routes.js b/example/src/routes.js deleted file mode 100644 index 822a53c..0000000 --- a/example/src/routes.js +++ /dev/null @@ -1,87 +0,0 @@ - -// Routes - -App.IndexRoute = Ember.Route.extend({ - - setupController: function(controller, model) { - this._setupPositionHolders(); - this._position(); - this._issue(); - }, - - renderTemplate: function() { - this.render(); - // link particular controller with its outlet - var self = this; - App.Boards.forEach(function(label) { - self.render('board',{outlet: label, into: 'index', controller: label}); - }); - }, - - _setupPositionHolders: function() { - var self = this; - App.Boards.forEach(function(type) { - self.get('store').find('position', type).then( - function(position){ - // set issues into appropriate controller through position model - self.controllerFor(type).set('position', position); - }, - function(position){ - // create position documents (as a part of first time initialization) - if (position.status === 404){ - self.get('store').createRecord('position', { id: type }).save().then(function(position){ - self.controllerFor(type).set('position', position); - }); - } - } - ); - }); - }, - - _position: function(){ - // create a CouchDB `/_change` listener which serves an position documents - params = { include_docs: true, filter: 'issues/only_positions'}; - position = EmberCouchDBKit.ChangesFeed.create({ db: 'boards', host: App.Host, content: params }); - - // all upcoming changes are passed to `_handlePositionChanges` callback through `fromTail` strategy - var self = this; - position.fromTail(function(){ - position.longpoll(self._handlePositionChanges, self); - }); - }, - - _handlePositionChanges: function(data) { - var self = this; - data.forEach(function(obj){ - var position = self.controllerFor(obj.doc._id).get('position'); - // we should reload particular postion model in case of update is received from another user - if (position.get('_data.rev') != obj.doc._rev) - position.reload(); - }); - }, - - _issue: function() { - // create a CouchDB `/_change` issue listener which serves an issues - var params = { include_docs: true, filter: 'issues/issue'}; - var issue = EmberCouchDBKit.ChangesFeed.create({ db: 'boards', host: App.Host, content: params }); - - // all upcoming changes are passed to `_handleIssueChanges` callback through `fromTail` strategy - var self = this; - issue.fromTail(function(){ - issue.longpoll(self._handleIssueChanges, self); - }); - }, - - _handleIssueChanges: function(data) { - var self = this; - // apply received updates - data.forEach(function(obj){ - issue = self.get('store').all('issue').toArray().find(function(i) { - return i.get('id') === obj.doc._id; - }); - if(issue != undefined && issue.get('_data.rev') != obj.doc._rev){ - issue.reload(); - } - }); - } -}); diff --git a/example/src/views.js b/example/src/views.js deleted file mode 100644 index 951c412..0000000 --- a/example/src/views.js +++ /dev/null @@ -1,133 +0,0 @@ - -// Views - -App.IssueView = Ember.View.extend({ - tagName: "form", - edit: false, - attributeBindings: ['draggable'], - draggable: 'true', - - submit: function(event) { - event.preventDefault(); - if (this.get('edit')){ - this.get('controller').send("saveIssue", this.get('context') ); - } - this.toggleProperty('edit'); - }, - - dragStart: function(event) { - event.dataTransfer.setData('id', this.get('elementId')); - }, - - dragEnter: function(event) { - event.preventDefault(); - event.target.style.opacity = '0.4'; - }, - - dragOver: function(event) { - event.preventDefault(); - }, - - dragLeave: function(event) { - event.preventDefault(); - event.target.style.opacity = '1'; - }, - - drop: function(event) { - var view = Ember.View.views[event.dataTransfer.getData('id')]; - if((this.draggable === 'true') || (view.draggable === 'true')){ - this.get('controller').send("dropIssue", view.get('controller'), view.get('context'), this.get('context')); - } - event.preventDefault(); - event.target.style.opacity = '1'; - } -}); - -App.NewIssueView = Ember.View.extend({ - tagName: "form", - create: false, - attributeBindings: ["style"], - style: "display:inline", - - submit: function(event){ - this._save(event); - }, - - keyDown: function(event){ - if(event.keyCode == 13){ - this._save(event); - } - }, - - _save: function(event) { - event.preventDefault(); - if (this.get('create')){ - text = this.get("TextArea.value"); - if(!Ember.isEmpty(text)){ - this.get('controller').send("createIssue", text); - } - } - this.toggleProperty('create'); - } -}); - -App.CancelView = Ember.View.extend({ - tagName: "span", - - click: function(event){ - event.preventDefault(); - this.set('parentView.create',false); - } -}); - -App.DeleteIssueView = Ember.View.extend({ - tagName: "button", - classNames: ['btn', 'btn-xs', 'btn-danger'], - - click: function(event){ - event.preventDefault(); - this.get('controller').send('deleteIssue', this.get('context')); - } -}); - -App.DeleteAttachmentView = Ember.View.extend({ - tagName: "span", - classNames: ['badge'], - - click: function(event){ - event.preventDefault(); - this.get('controller').send('deleteAttachment', this.get('context')); - } -}); - -App.AttachmentView = Ember.View.extend({ - tagName: "input", - attributeBindings: ["style", "type", "multiple"], - style: "display:none", - type: 'file', - multiple: true, - - actions: { - browseFile: function(e){ - this.$().click(); - } - }, - - change: function(event) { - this.get('controller').send('addAttachment', event.target.files, this.get('context')); - } -}); - -App.FocusedTextArea = Ember.TextArea.extend({ - elementDidChange: function() { - this.$().focus(); - }.observes('element') -}); - -Ember.Handlebars.helper('linkToAttachment', function(attachment) { - aTagTemplate= "%@" - url = "%@/%@/%@".fmt(App.Host, attachment.get('_data.db'), attachment.get('id')); - return new Handlebars.SafeString( - aTagTemplate.fmt(url, attachment.get('file_name')) - ); -}); diff --git a/package.json b/package.json deleted file mode 100644 index f07a650..0000000 --- a/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "ember-couchdb-kit", - "scripts": { - "postinstall": "bower install", - "test": "grunt test" - }, - "dependencies": {}, - "devDependencies": { - "grunt-es6-module-transpiler": "~0.6.0", - "grunt": "~0.4.2", - "grunt-bower-install": "~1.0.0", - "grunt-contrib-clean": "~0.5.0", - "grunt-contrib-coffee": "~0.7.0", - "grunt-contrib-concat": "~0.4.0", - "grunt-contrib-copy": "~0.4.1", - "grunt-contrib-cssmin": "~0.6.0", - "grunt-contrib-jshint": "~0.8.0", - "grunt-contrib-uglify": "~0.3.0", - "grunt-contrib-qunit": "~0.3.0", - "grunt-contrib-connect": "~0.6.0", - "grunt-contrib-watch": "~0.5.3", - "load-grunt-config": "~0.7.0", - "grunt-mkdir": "*", - "load-grunt-tasks": "~0.4.0", - "matchdep": "~0.3.0", - "bower": "~1.2" - }, - "peerDependencies": { - "grunt-cli": "~0.1.13" - } -} diff --git a/spec/attachment-adapter_spec.coffee b/spec/attachment-adapter_spec.coffee deleted file mode 100644 index f6e4d98..0000000 --- a/spec/attachment-adapter_spec.coffee +++ /dev/null @@ -1,66 +0,0 @@ -Ember.ENV.TESTING = true - -module 'EmberCouchDBKit.AttachmentAdapter', - setup: -> - unless window.testing - window.subject = new TestEnv() - window.testing = true - @subject = window.subject - @async = window.async - @user = -> - window.subject.create.call @, 'user', id: Math.floor Math.random() * 1000 - -test 'create', 1, -> - user = @user() - user.save().then @async => - rev = user.get '_data.rev' - params = - model_name: 'user' - doc_id: user.get 'id' - id: "%@/%@".fmt user.get('id'), 'test_image.jpeg' - file: window.TestImage - rev: user.get '_data.rev' - content_type: 'image/jpeg' - length: 4056 - file_name: 'test_image.jpeg' - attachment = @subject.create.call @, 'attachment', params - attachment.save().then @async -> - notEqual user.get('_data.rev'), rev, 'attachment created' - -test 'delete', 2, -> - rev = @subject.createDocument({id: "user0", name: "UserZero"}) - rev = @subject.createAttachment('user0', rev, {id: "user/image3"}) - @subject.createAttachment('user0', rev, {id: "user/image4"}) - @subject.find('user', 'user0').then @async (user) => - user.get('attachments').then @async => - equal user.get('attachments.length'), 2, 'two attachments exist' - attachment = user.get('attachments.firstObject') - attachment.deleteRecord() - attachment.save().then @async => - user.get('attachments').then @async -> - equal user.get('attachments.length'), 1, 'one attachment gone' - -test 'find', 1, -> - user = @user() - user.save().then @async => - params = - model_name: 'user' - doc_id: user.get 'id' - id: "%@/%@".fmt user.get('id'), 'test_image.jpeg' - file: window.TestImage - rev: user.get '_data.rev' - content_type: 'image/jpeg' - length: 4056 - file_name: 'test_image.jpeg' - attachment = @subject.create.call @, 'attachment', params - attachment.save().then @async => - @subject.find('attachment', attachment.id).then @async (rec) -> - ok rec?, 'finds ok' - -test 'hasMany', 1, -> - rev = @subject.createDocument({id: "user1", name: "User"}) - rev = @subject.createAttachment('user1', rev, {id: "user/image1"}) - @subject.createAttachment('user1', rev, {id: "user/image2"}) - @subject.find('user', 'user1').then @async (user) => - user.get('attachments').then @async -> - equal user.get('attachments.length'), 2, 'okok' diff --git a/spec/document-adapter_spec.coffee b/spec/document-adapter_spec.coffee deleted file mode 100644 index 8ed1116..0000000 --- a/spec/document-adapter_spec.coffee +++ /dev/null @@ -1,69 +0,0 @@ -Ember.ENV.TESTING = true - -module 'EmberCouchDBKit.DocumentAdapter', - setup: -> - unless window.testing - window.subject = new TestEnv() - window.testing = true - @subject = window.subject - @async = window.async - -test 'create record with given id', 1, -> - person = @subject.create.call @, 'user', id: 'john@example.com' - equal person.id, 'john@example.com', 'Id is correct' - -test 'create record with correct attributes', 2, -> - person = @subject.create.call @, 'user', a: 'a', b: 'b' - equal person.get('a'), 'a', 'attr is correct' - equal person.get('b'), 'b', 'attr is correct' - -test 'retrieve raw json', 1, -> - person = @subject.create.call @, 'user', name: 'john', id: Math.floor Math.random() * 10000 - person.save().then @async -> - equal person.get('_data').name, 'john', 'Retrieve raw json ok' - -test 'belongsTo relation', 1, -> - person = @subject.create.call @, 'user', id: Math.floor(Math.random() * 10000), name: 'john' - article = @subject.create.call @, 'article', label: 'lbl', user: person - equal article.get('user.name'), 'john', 'Retrieves relations attrs ok' - -test 'retrieve belongsTo field as raw json', 1, -> - person = @subject.create.call @, 'user', id: Math.floor(Math.random() * 10000), name: 'john' - message = @subject.create.call @, 'message', user: person - message.save().then @async -> - equal message.get('_data.user'), person.get('name'), 'Retrieves raw belongsTo json ok' - -test 'hasMany relation', 1, -> - article = @subject.create.call @, 'article', label: 'Label' - article.save().then @async => - comment = @subject.create.call @, 'comment', text: 'text', article: article - article.save().then @async -> - equal article.get('comments.firstObject.id'), comment.id, 'Handles hasMany relation ok' - -test 'update', 1, -> - person = @subject.create.call @, 'user', id: Math.floor(Math.random() * 10000), name: 'john' - person.save().then @async => - rev = person.get '_data.rev' - person.set 'name', 'paul' - person.save().then @async -> - ok person.get('_data.rev') isnt rev, 'Increments rev on update' - -test 'update belongsTo relation', 1, -> - person1 = @subject.create.call @, 'user', id: Math.floor(Math.random() * 10000), name: 'john' - person2 = @subject.create.call @, 'user', id: Math.floor(Math.random() * 10000), name: 'paul' - article = @subject.create.call @, 'article', label: 'lbl', user: person1 - article.set 'user', person2 - article.save().then @async -> - equal article.get('user.id'), person2.id, 'Updates parent relation' - -test 'delete', 1, -> - person = @subject.create.call @, 'user', id: Math.floor(Math.random() * 10000), name: 'john' - person.save().then @async => - person.deleteRecord() - person.save().then @async -> - ok person.get('isDeleted'), 'Marks record deleted' - -test 'find by id', 1, -> - person = @subject.create.call @, 'user', id: Math.floor(Math.random() * 10000), name: 'john' - @subject.find('user', person.id).then @async (user) -> - equal user.get('name'), 'john', 'Finds record by id' diff --git a/spec/env.coffee b/spec/env.coffee deleted file mode 100644 index 4e1fe68..0000000 --- a/spec/env.coffee +++ /dev/null @@ -1,153 +0,0 @@ -class @DatabaseCleaner - @reset: -> - @destroy() - @create() - - @create: -> - @_ajax 'PUT' - - @destroy: -> - @_ajax 'DELETE' - - @_ajax: (type) -> - jQuery.ajax - url: 'http://localhost:5984/doc' - type: type - dataType: 'json' - contentType: 'application/json' - cache: true - async: false - -class @TestEnv - - constructor: -> - DatabaseCleaner.reset() - - window.async = (cb) -> - stop() - -> - start() - args = arguments - Em.run -> - cb.apply @, args - - unless window.Fixture - @models() - mapping = user: User, article: Article, attachment: Attachment, comment: Comment, message: Message, history: History - window.Fixture = window.setupStore mapping - @ - - models: -> - window.User = DS.Model.extend - name: DS.attr 'string' - history: DS.belongsTo 'history', inverse: null - attachments: DS.hasMany 'attachment', async: true - - window.Comment = DS.Model.extend - text: DS.attr 'string' - - window.Article = DS.Model.extend - label: DS.attr 'string' - user: DS.belongsTo 'user', inverse: null - comments: DS.hasMany 'comment', async: true, inverse: null - - window.Message = DS.Model.extend - user: DS.belongsTo 'user', attribute: 'name' - - window.Attachment = DS.Model.extend - content_type: DS.attr 'string' - length: DS.attr 'number' - file_name: DS.attr 'string' - db: DS.attr 'string' - - window.History = DS.Model.extend - user: DS.belongsTo 'user', inverse: 'history' - users: DS.hasMany 'user', async: true, inverse: null - - create: (type, params) -> - window.Fixture.store.createRecord type, params - - createDocument: (params, deleteID=true) -> - @rev = undefined - id = params.id || params._id - rev = params.rev - url = "http://localhost:5984/doc/#{id}" - if rev - url += "?rev=#{rev}" - delete params.rev - delete params.id if deleteID - - jQuery.ajax - url: url - type: 'PUT' - dataType: 'json' - contentType: 'application/json' - data: JSON.stringify params - success: (data) => - @rev = data.rev - cache: true - async: false - - @rev - - find: (type, id) -> - window.Fixture.store.find(type, id) - - createView: (viewName) -> - switch viewName - when "byComment" - doc = - _id: "_design/comments" - language: "javascript" - views: { - all: {map: "function(doc) { if (doc.type == \"comment\") emit(null, {_id: doc._id}) }"} - } - @createDocument(doc, false) - - createAttachment: (id, rev, fileName) -> - @createDocument id: "%@/%@".fmt(id, fileName), rev: rev - - findQuery: (type, params) -> - models = window.Fixture.store.find type, params - models - -window.setupStore = (options) -> - env = {} - - options = options or {} - registry = new Ember.Registry(options) - container = env.container = registry.container() - adapter = env.adapter = EmberCouchDBKit.DocumentAdapter.extend db:'doc', host: 'http://localhost:5984' - - delete options.adapter - - for prop of options - registry.register "model:" + prop, options[prop] - - registry.register "store:main", DS.Store.extend adapter: adapter - - registry.register "serializer:_default", EmberCouchDBKit.DocumentSerializer - registry.register "serializer:history", EmberCouchDBKit.RevSerializer - registry.register "serializer:attachment", EmberCouchDBKit.AttachmentSerializer - - registry.register "adapter:_rest", DS.RESTAdapter - registry.register "adapter:history", EmberCouchDBKit.RevAdapter.extend db:'doc', host: 'http://localhost:5984' - registry.register "adapter:attachment", EmberCouchDBKit.AttachmentAdapter.extend db:'doc', host: 'http://localhost:5984' - - registry.register 'transform:boolean', DS.BooleanTransform - registry.register 'transform:date', DS.DateTransform - registry.register 'transform:number', DS.NumberTransform - registry.register 'transform:string', DS.StringTransform - - registry.injection "serializer", "store", "store:main" - - env.serializer = container.lookup("serializer:_default") - env.restSerializer = container.lookup("serializer:_rest") - env.store = container.lookup("store:main") - env.adapter = env.store.get("defaultAdapter") - - env - -window.TestImage = """ - data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wAARCAMAAgADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDgqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbb/j6h/66L/Ooqltv+PqH/rov86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbb/j6h/66L/Ooqltv+PqH/rov86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbb/j6h/66L/Ooqltv+PqH/rov86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbb/j6h/66L/Ooqltv+PqH/rov86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbb/j6h/66L/Ooqltv+PqH/rov86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbb/j6h/66L/Ooqltv+PqH/rov86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiloASipIIJbiURQRvLIeioMmugXwt9ht1u9euVsoT0iX5pX9gO1AHN0VJP5Xnv5BYxbjs39cds1HQAUUUUAT2cUM90kdxOLeJj80pXO38K6LT/CdnqblLLXbeVhzt2ENj6GuWrS8OyNF4gsGQ4PnKPwJoEdM3w3uv4dRh/GM0w/Dq6xxqVuf+AmvRn+430rw26nlN1MRNJje38R9aBnVj4c3p6X9t+Rpp+HOo9ry2P51yQubgdJ5R/wADNPjv7yJt0d3Op9RIaAOjuvAOo21vJM1zbMsaljgnPH4VyleieF/EFxq+kahZXjeZPFAzK56suMc/SvO6ACiiigCdrS5SFZmt5RE4yrlDg/Q1BWtpniPUtMKLFcM8C9YZPmUj0x2r006LpGr2cVxNp8WZkD5A2kZGeooA8cor0i88AaVK5W2u5LeT+4WDj8jzWHf+ANVtgWtniulHZTtb8jQBydFTXVrcWcxhuYXhkH8LjBqGgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqW2/4+of8Arov86iqW2/4+of8Arov86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKWrulaRe6xOYbKLeV5ZicBR7musb4etFpM8r3JkvVTciIMJkdvegDhaVcbhnpnmgggkEYI4IpDxQB7bpGm2VhaRizt44gygkgcnjua4j4mQSjULSckmJoyo9AQa7TTr1BY6cjnDTwjb7kKDioPFmlf2toc0Sj99H+8j+o7fjQB45RSkEHBGCOtJQAUUUUAFX9C/5Dth/13T+dUK0fD3/IwWH/AF3X+dAj2mT/AFbfQ14TP/r5f98/zr3ab/Uv/umvB5D+9f8A3j/OgY2iitLw/p0mp6zbW8akrvDOeyqOTQBFpmoz6XcPLAFLPG0bBhxg1TrX8WgL4nv1UAAScAfQVj0AFFFFAElvC1xcRQqMtI4Ufia90giENvHEvSNQo/AYryTwXafbPE1qCMrETIfw/wDr16rqVyLPTbm4JwI42b9KAPI/FF4bzxFezBjtEm1cHsOP6VPofirUNInUGZ57bPzRO2ePb0rEdi7s7dWJJptAHtFxZ6f4j0qNpY1lhmTcj4+Zc9wexrynX9Hm0TUntZfmQ/NG/wDeWvRPh9I7+GIw2cJI6r9M1U+JVokmjwXOBvhl2g+xHSgDzSnpFI6M6RsyJ94gZA+tNVS7BVGWJwAO5r1bQrSw8M6GkeoTQxSz/NL5hHzH0x3xQB5TSV6RfeFdF15GuNGuYope4jOUP1HauJ1bQdQ0d8XkBCHpIvKH8aAM2iiigAooooAKKKKACiiigAooooAKKKKACiiigAqW2/4+of8Arov86iqW2/4+of8Arov86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoopaAPQvhqFh0rULlh0cZ+gXP9a7Kzuor20iuIG3RyqGU1xPw+ffoOqwj7wJOPqv8A9an/AA41bfHNpcrcx5eL6dxQBheOdHOmayZo1xb3WXXHZu4rmq9j8VaQNY0WWFQDMnzxH/aHb8a8dKlSVYYIOCPSgDvdVu5LfwdoV/ETvgdG/QjFdtY3Ud9ZQ3UJzHKgYfjXA3eZvhhat/zzkA/8eIq98N9V8y3m0yRvmi/eR59D1H50Ac3410r+y9dkKLiG4/eJ+PUfnXP16x460r+0tEaWNczW37xfUjuPyryegAooooAK0/DahvEVgD/z2WsytXwuN3iSwA/56igD2WVgsTMRkAEketeenxZ4fywfQF644VDmvQLo7bWUnoEP8q8Jbl2PuaAO1HifwznnQMD/AHFrpPDmu6JfsYNOjW2lxkxGMIT+XWvJKu6NdfYdYtLkttWOUFj6Dv8ApQBa8V8+Jr//AK6/0rIrR8QXMV5rt5cQMHikkJVh3FZ1ABRRQaAO6+GNpunvbwjhQIwfrya3fH939m8NSRg4ad1jH06n+VHgG0+zeGonIw07GQ/yH8qh8Xano6XMOnaxbvLGy7xIh5jPSgDy6nIjSOqIpZ2OAB1JrsovCGkak2/S9bUqf4JACw/lXTaF4Q0/RpBOSbi4HSR+i/QUAXvDenHStDtrV/8AWKu5/wDePJrnfiZeqlha2QI3yP5hHoB/9c1va14k07R4mM0yyTY+WFDliff0ryjV9Tn1fUJLu5PzNwqjoo7AUAP0S9g07UUu7iAz+UCY1zxv7E+1QX99cajdvc3Uhkkc9T29h7VWooAlt7ia1mWa3leKRejIcGvRvBmvX2urNa38Mc0UafNKR19iOhrzVQWYKoJJOABXsfhbSF0bRooCB5z/ADyn/aPb8KAMbWvAVnd7pdNYWsvXYeUP+FcHqmjX+ky7L23ZB2ccq30Ndl488RT2t3BY2E7RSRESSMh5z2FQaV46iuYvsmvWySI3BlVcg/Vf8KAOFor0PVPA1nfRC70SdYw43KhOY2+h7Vwt/Y3Gm3b2t3H5cqdRnP40AVqKKKACiiigAooooAKKKKACiiigAqW2/wCPqH/rov8AOoqltv8Aj6h/66L/ADoAiooooAKKKKACiiigAooooAKKKKACilpKACiuh1Hw00Wg2mrWReWGSMNMpHKH1+lc9QAUUUUAFLSUUAdl8NbhV1K7tGPE0WR+B/wNYEdxLofiIypw9tOQR6jPI/Kjw5ff2dr1pcE4UPtb6Hg1o+PbH7L4iklUfu7lRID79D/KgD1K1uI7u2juIW3RyKGU+xrzDx5o/wDZ2sG5iXEF1lhgdG7j+tb3w41fzbWXTJW+eL54s91PUfhXQeJtJGsaNNb4HmqN8R9GH+cUAcfaZm+GFyvXy5T/AOhA1zGiag+latb3iE4jb5h6qeorp9ADN4D1qB1w0TnIPY4H+FcV2oA94ieO4gWRCGjkXI9wRXjvibSzpGtT24H7snfH/umu4+Huq/bNJaykbMtqcDPdD0/LpSfETSftelpfRLmW2PzY7oev5GgDzKiiigArX8Kf8jNYf9dayK3fBcLzeJ7MopIQl29gBQB6ze/8eU//AFzb+VeFHqfrXut4hksp0X7zRsB+VeGSRPFI0boyspwQRyKAGUUUqqzHCqSfQDNAABngDJrQ1bSJNJFss8imaeLzGjA5jB6A103g7wlK0yajqkRjij+aOJxyx9SPSub8R6gdT1y6uc5QvtT/AHRwKAMynIhkdUXqxAFJWt4VszfeI7KLblQ+9vovNAHrmnW4tNPt7cDHlxqv6V5T4zu/tnia7IOViIjH4V63czLb20szdI0LH8BXhc8rT3EkzHLSMWP4nNADOhyOtSfarjbt+0S7fTecVFRQAd896KKKACiinIjSSLGilnY4AHc0AdR4B0f+0NW+1yrmC1+bnoz9h/WvRtVv4tL06a8l+7Eucep7Cq/h3Sl0bR4bUAeZjdIfVj1rjPiLrHn3SaXC2Uh+eXHduw/D+tAHIXdzLeXUtzO26SVizGoaKKAPQfh9qfk6Lfi5Y+RakOCewIOR+lcRqd7JqWoz3kp+aVy2PQdhVmLVjB4fl0yKMq08oeWTP3lHRazKACirOnwwXF7FFdT/AGeFzhpMZ21Nq+k3Oj3Xk3K5U8xyL91x6g0AUKKWkoAKKKKACiiigAooooAKltv+PqH/AK6L/Ooqltv+PqH/AK6L/OgCKiiigAooooAKKKKACiiigAopa6rwd4VOquL29UrZqflX/nqf8KAMzRPDWo62d1vGEh7yycL+HrXRf8I34a0ghdX1Qyzd0U7R+QyaTxV4s8ndpeikQxRjY8sfH4L6fWuHYliWYkk9Se9AHt2mPZXWlxfYQrWZXYgxxgcYwa868Z+GP7Im+2WgJs5GwV/55t6fStfwD4ht0tBpd1IsToxMLMcBge31rd8ZXNrH4bu1ndCZE2oueS3bFAHkNFFFABRRRQAV2+qf8VB4Htr9fmubH5ZfXA4P6YNcRXV+AL5U1KbTZ+YLxCu09NwH+GaAMHR9Qk0rVILyP/lm3zD1XuK9rt547m3jnibdHIoZT6g14jqVo1hqNxat1ikK/wCFd98OtX+0WUmmytl4PmjyeqHt+BoA1LrSRaW+uPGB5N3EZMDs+05/oa8jHSvdrxA9nOn96Nh+leFEYJHoaANjwpqTaZr9tJn93IwjkHsa9gmjSaJopFDI4KsD3BryfRtNsbUW+qavexJCCHjgjO6STB7gdK0tX+IF3cbo9MiFsnTzH5f8ugoATVvBVvYTvLNq0FvaE5USLlwPQDvXL6itkl2V0+SWSAAYaUAEnvx6VFc3M93KZbmZ5ZD1Z2JNRUAWtOube1ufMubNLtNpHluxUZ9ciuk0/wAawaapWz0O3hB67ZDk/jiuRooA7v8A4WVJ/wBApf8Av+f/AImmt8QYXOZNFjZj1JkB/wDZa4aigDtx47sf4tBh/Bl/+JqSPx/ZRsCmiqnurKD/ACrhKKAO61D4hJc2E8EFlJFJIhUOZAdua4XtRRQBpaWmjyRumpy3UMhb5HhUMoHuK7nwnF4b05mks9SWa4kGC0xCED0ArzSigD27WIXvdFu4bchnliZUweCcV4pNDJbytFNG0cinBVhgip7XUb2yObW7nhP+w5A/Krl54j1C/smtrxopw2MSNEN4+hoAyaKKKACiiloAK674e6P9s1JtQmX9zbfcyOr/AP1q5SCF7idIYlLSSMFUDuTXtGh6ZHpGlQ2iAZQZc+rHqaAF1vUo9J0qe8kx8i/KP7zHoK8WnmkuJ5JpWLSSMWYnuTXWfEPWPteoLp0LZit+Xx3f/wCtXH0AFFFFABRRRQAter6Voov/AAja2WroWfZlSfvRjtg+oFc/4I8K+eyanqEf7oHMMbD7x/vH2ru7++t9OtHubqQRxIOSf5D3oA8j8Q+H7rQrrZKN8DH93MBw319DWRXc3fjqz1B3tb3TN9g/Gd3zj3rldYtLS0uwLG7W5t5F3ow+8oPZvegChRRRQAUUUUAFFFFABUtt/wAfUP8A10X+dRVLbf8AH1D/ANdF/nQBFRRRQAUUUUAFFFFABRRRQBf0XTm1XVrezQ4EjfMR2UdTXpXiu8TQfDJitAIywEEQH8Ixyfyrmfhnbh9VupyOY4gB+J/+tWj8T1f7DYMM7BIwb64GP5GgDzykoooAKczu+Nzs2OmTmm0UAFFFXNJtoLzU4La5lMUcrbS4HQnp+tAFOirmqadcaVfSWlyuHQ8Hsw7EVToAv6bouo6qrtZWryqnVhwM+mTU2lQ3Wn+JbKOWJ4p0nQFGGD1r0TwG8LeGLcRYypYPjruzWne6ZYzX0GpXKgSWgJVjwB7n6UAeaeOkVPFN1t/iCsfris3Q9SfSdWt7xDwjYceqngineIL8anrd1dL9x3wn+6OBWfQB65qni7SLGDmcTyOuRHFyefX0rySQhpGYDALEgU2igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArZ0vSbPVLZUj1FIL/J/dTDCMO2G9axqKAPSvCfg19Lu/tuoPG8yj90ichffPrXQeINTGkaPPd8b1GEB7seleaaL4v1PSdsZf7Tbj/lnIc4Hse1dLqep2PjLRvstpOLe9Vg6wzHG4jsD0NAHnskjyyNJIxZ3JZie5NNqe7tLixuGguoXilXqrDFQUAFFFFABXWeDPC51SZb69QizQ/Kp/5an/CqvhLw1Jrd15swKWUR+dv75/uivVoo4reFIo1WONAAqjgAUANnmhsrVpZWWKGJckngACvJvFHiKbXbzC5S0jOI09fc+9bXxGutS+0R27xmOw6qynIkb3+npXEUAFFFFABRRRQAUUUUAFFFFABUtt/x9Q/9dF/nUVS23/H1D/10X+dAEVFFFABRRRQAUUUUAFFFFAHbfDGUDUL2I9WiDD8D/wDXrrPF+mHVNAniQZlj/eJ9R/8AWzXm3hPUhpniC3mc4ic+W/sD3/OvYutAHgvTjpSV1PjjQDpmoG7t0/0S4bPHRG7j/CuWoAKKKKAClVirBlOGByD6UlFAHqWsaOnijw7a3UYAvBEHjb145U15hLG8MjRyKUdThlPUGvULnWU8LeGdOSaIy3DRhVjzjtk5rh/Euq2OszRXlvbPb3TDE4JBU+hHvQBDoev32hys1oysj/fjcZVv/r1a1nxfqerwmBykEDfejiBG76msCigAooooAKKKKACiiigAoopyI0jqkalnY4AAySaAEqzZadeahJss7aWY99i8D8a7bw54DUKtzrHLHkW4PA/3j/Su4gt4raIRQRpFGOioMCgDyyHwJrkoy0UUX+/IP6VZX4easRzNbA/7x/wr06loA8wb4easBxNbH23H/CqF74N1uzUubXzlHeE7v06167RigDwV0eNyjqyMOoYYIpte16rodhq8RS8t1ZscSAYZfxrzTxL4VutDcyoTPZk8SAcr7NQBz9FFFABRRRQAUUUUAFFFFABSjg5HBpKKAJZ55rlw88rysAFBc5OBUVFX9HuLK3u/+JjbfaLd12Ng4ZM/xD3oAoVs+GtAm129CDKW6HMsnoPQe9btv4CjvZI7iy1OOTT35DbfnA9PSu703TrbS7NLW0QJGv5k+p96AH2ltBYWqW9ugjhjGAPQV57408VG9law0+Qi2Q/vJFP+sI9ParfjfxVu36Xp0nHSaVT/AOOj+tcFQB3XhjX49YgOia4BMJBtikbqfYn19DXP+JfD0+hXm05e2kP7qT+h96x43aKRZEJV1OQR2NewiCHxL4ZhFwoP2iENn+6+Oo/GgDxyipru2ezu5raXh4nKn8KhoAKKKKACiiigAooooAKltv8Aj6h/66L/ADqKpbb/AI+of+ui/wA6AIqKKKACiiigAooooAKKKKACvUPA/iJdRs1sbl/9LhXCk/8ALRR3+ory+pba4ltbhJ4HMcsZyrDqDQB7heWcF/aSW1zGJIpBhlNeU+JfC9zocxkQNLZsfllA+77N6V2/hnxdbavGsF0VgvAMFScB/cf4V0kiJLGUkUMjDBVhkGgDwWivTNW8AWN2zS2EptHPO3G5Py7Vir8OdS34N5ahfUbj+mKAOOrrPBvhpryZdSvl8uyhO5Q/HmEd/oK3tP8ABOlaSn2vU7gXHl8kyfLGPw71heK/Fx1BDYabmOzHDN0Mnt7CgDM8Waz/AGzrDyRn/R4hsi+nr+NYlFFABRRRQAUUUUAFFFFABRRRQAoBJwBknoK9O8GeF002Bb68QNeSDKgj/VD/ABrlfAmlLqOtiWVcw2oEhB7t2r1egApaSloAKKKKACiiigAqOeGOeF4pkDxuMMrDIIqSkoA8f8V6E2h6ltjBNtLlomPb2/CsSvW/G+nrfeHZ2xmS3/eofp1/SvI6ACiiigAooooAKKKKACiiigAooooA2fDniK60K4yhMlsx/eQk8H3Hoa9B1TUZ9W8MS3OgSh3YfNj74HcD0avJq0dE1q70S8E9s2VP34z91xQBnHOTuznPOetFeiNo2ieMIje2MptLo8yqADg+6/1FUR8N7rzMHUYdnqIzn8qAOPsrSa+u47a3QvLIcACvbNOtFsNPt7RTkQxhM+uB1rO0Dw1ZaEhMIMk7DDTP1+g9BV7VdSt9KsZLu5YBEHA7sewFAHlnjQKPFN7txjcM49cCsKrF9dyX19NdS/flcsfbNV6ACiiigAooooAKKKKACpbb/j6h/wCui/zqKpbb/j6h/wCui/zoAiooooAKKKKACiiigAooooAKKKKAFBwcjg+1b+l+MtX05BH5wuIh0WYZx+PWufooA72L4knb+9075v8AZk/+tUV18R7hlItbCND/AHnYn9K4eloAv6prWoas+69uWkAPCDhR9BWfRRQAUUUUAFFFFABRRRQAUUUUAFFFFAHofwwQfZr9++9R+ldzXD/DD/jyvv8Arov8q7igBaKSjNAC0UlFAC0UUlAC0UlFAEF/H5tjcRkZ3RsP0rwsjBI9DXvFwcW8mf7h/lXhD/6xvqaAG0UUUAFFFFABRRRQAUUUUAFFFFABRRRQBPaXlxY3Cz2kzxSr0ZTiuy074izRxhdQtBKR/HEdpP4Vw1LQB6Bc/EeLYfsunuX7GRxj9K4/WNbvtanEl5LkL92NeFX6Cs6igAooooAKKKKACiiigAooooAKltv+PqH/AK6L/Ooqltv+PqH/AK6L/OgCKiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigDv/AIYTrsv4M/NlXx+ld6a8i8ETyw+J7VYiQJMo49RivXaAPJ/FXiK+vNWuIYriSG2hcoqRsVzjucdawftdyGDC4mz6+Ya6Hxr4fn0/Upr2JC1pOxfcOdjHqDXM0Adf4V8X3kF7DZ38zT20jBAz8shPTnuK9MFeF6fC9xqFvDECXeRQPzr3NflUD0FAHM+NPEU2iW8UVoo8+fOHYZCgf1rzW51O+u5TJPeTux7lzXq/inQ01zTDEMLcRndE3v6fQ15DNDJbzPDMpSRCVZT2NAGlpfiTVNLlVorqR0B5jkYspH49K9a0nUI9U02C8jGFlXOPQ9xXh9er/D8sfC8O4EASPj3GaANnWJhb6TdzMcBImP6V4fXpXxH1RrbTYrGM4NySXP8Asj/69ea0AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFS23/H1D/10X+dRVLbf8fUP/XRf50ARUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAdL8P4lk8Txk/wDLONmH16f1r1evDdNv59Mvoru2YCSM556Edwa9P0TxlpupqqTOLW4PVJDgE+xoA6J0V0KOoZSMEEZBrnb/AMEaNesXWFrdz3hbA/LpXRAgjIOQaWgDC0Twnp2jTefCHlnxgSSHJX6VmfETVXs9PgtIHKyzvuJB5AH/ANeuwNedeM9N1LWvEphs7aSVIYlXd0UZ5PJoA7bRNRTVdKgu0IJdRuHo3cVleJPCNtrbefE/2e7xjfjIf6j+tc74QGo6D4iTTb9HhjuVOEJyC3YivRqAPPLH4cz/AGgG/u4/JB+7DklvxPSu9tbaKztY7e3QJFGu1VHYVNkVm6zrdno9q0tzKu7HyRg/M59hQBwPxGuRNr6Qg5EMQH4nn/CuTqxf3kmoX013MfnlYsfb2qvQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVLbf8fUP/XRf51FUtt/x9Q/9dF/nQBFRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFaWgaY2r6vBaAMY2bMhX+FR1oAowwTTkiGJ5COyKTSSRvE5SRGRh1Vhg17jZWNtYW6wWkKRRqOij+dYvi7w4mtWRlhULeRDKN/eH900AeS10fgnSbPWNTngvkLIsO5QDjnOM1zzo0bsjqVdTgg9Qa6T4f3Ah8TRqTgSxsn9f6UAa+p6drHhIfa9KvJJ7BT80UnzbB7j09xXS+GfEMOvWhcKI7iPiSPP6j2rVuYEubaWCQApIpUg+4ryzwkv2PxhHBJK0W12Q843EdAaAPWO1Jisaz137Vr1/pwVPLtUDCQHqe4NMt/Ekc2gTamY1Uxlh5XmDJwcdaANpokZ1dkUuv3WI5H0p5rI1DXYrG/062fZi8JyxfGwY4rlvGPi24S5n0uwKBMANMjZY5HIFAEd3oV/4h8R3rQ3jLZRyY80sSM91Ud8Vl+MPD8OhNaCKeWZpVO5pD6Y6V6RoNotlo1pAE2lYwWHueTXEfE2bdqVnCP4Iix/E/wD1qAOKopaSgAopQCTgDJPQVrWHhnWNQwYLKQIf45PkH60AZFFdXJ8PtXSAur27uB/qwxz+dcxPDLbTPDOjRyIcMrDBBoAjooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKltv+PqH/rov86iqW2/4+of+ui/zoAiooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKWkooA0/D+jS65qSWsZ2IPmkfH3Vr1vStIstItxDZwhP7z/AMTfU1x3wweLdfoceadpHrt5rv6ACg1m391dWd9E620s9oyFX8ldzI2eDjuKfHrOnu4Q3Kxuf4JQYz+TYoA57xf4QGpFr7TwFuurx9BJ/wDXrz62luNJ1KOUo0c9u4O1hg8dq9wVlZQVIIPcGsvWfD2n60n+lQgSj7sycMPx70AT6Rqlvq9gl1bNkMPmXPKH0Ncz4m8FyahqQvdOlSNpWHmq3GP9of4VnroOu+Fbs3Wln7ZbfxovVh7r/UV1ej+JbDVE2iQQXI4eCU7WB/HrQBw8Xg3Uxqd5bLM0axR7lnwQJfaorPwbd3Ohyag0/lsoYiEoctivVRgjIpaAPJ9H8KX+s2U1y8rQ+UNqJIpJY46ewrT8MeCbo3kd3qqCKKM7liJyzHtn0FeiEgdcCqGp61YaVA0t3cIuOiA5Zj6AUAW7m4itIHnndY4kGWY9AK8d8S6qNZ1ma7UER8LGD12itXUdQ1jxnd+RZW7i1U8IDhR7selb+i+ALS22y6m/2mTr5Y4Qf40AcHp2k3+qPtsrZ5fVgMKPqa7DS/h0TtfU7rH/AEyh/wDijXeQwxwRiOGNY0XgKowBUV1cSQACG3knkboq8D8SeBQBV0/QdM0wD7LaRq399hub8zV9ZUMrRhhvUAle4BrKktdZvD+9vo7GM/wW67m/76P+FT6dpNrpjvMrySTyjDzTSFmb86ANGvOPiXaRxX9pcoAHmQq3vjGD+teis6KpZmAUdyeK8r8daxFqmrJHbOHht1Khh0LHrigDmaKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbb/j6h/66L/Ooqltv+PqH/rov86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigCxY3tzp9ytxaStFKvQiuntviJqkYAnt7ab3wVP6VyFFAHoUHxJhJxcac6+6SA/wAxWnD4x8P6ioS5bZn+GePI/rXlVFAHuGnNYNEf7OeExnnETAirleDRSywOHhkeNh0ZGINb+neNtZssK8wukHaYZP59aAPWqzNV8P6bqw/0q3XzO0qfK4/GsTTPH+m3RCXqPaOf4j8yfmOldTb3UF1EJLeZJUP8SNkUAcFqnhPW7AF9K1C4uIRz5fmFXH9DXKz6lq0UrRz3l2ki8FWdgRXsOoalaadEHuZQhPCoOWY+gHU1z99o8/imWOS7tlsbVDldwBnkHv8A3R7UAeeW7anqM6wQSXU8jfwh2NdnongEfLPrMpduvkIePxP+FdfpulWWlQCGygWJe5HVvqe9XKAIra2htYVht4kijXoqDAqWs/U9a0/SY917cKh7IOWP0FcTq/xCuZt0emQiBenmSct+XQUAegXV3b2cRluZo4UH8TtiuW1L4gadbbks43u3Hf7q/nXnN3eXN9KZbueSZz3ds1BQB0t/451m7yIpEtU9Il5/M1hXF9d3Lbp7maQ/7Tk1XooAla6uHTY08rL6FyRUVFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVLbf8AH1D/ANdF/nUVS23/AB9Q/wDXRf50ARUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRS0lABRT0ikf7kbt9FJqddNvn+7ZXBz6RGgCrRWpB4c1mcjy9OuOe7LgfrWzY/D/Vbgg3Tw2qd8nc35D/GgDkq6nw14b1yaRbi3kk0+InPmMSCR7L3rs9H8H6ZpRWQR/aJx/wAtJecfQdBXQdKAM2w0a2s5PPcvc3R+9cTHcx+noPpWlUF5eW1lAZrqZIox/ExxXDa58QGbdDpEe0dPPkHP4D/GgDtNQ1S001AbmXDNwkajc7n0AHJqpGdT1P5nB0+1PRRzM49z0X+deeaD4pbTb17i8tVvJJD80zN+8A9AT29q9D0nxNperAC3uQsp/wCWUnyt/wDXoA5zXvATXDvcadcu0jcmOds5+jf41xF/pt7psvl3ttJCexYcH6Gvcc1HNDHPGY5o0kQ9VYZBoA8Hor1m+8D6LdkssLW7HvE2B+VZMvw2tyf3Woyr/vRg0AeeUtd7/wAK1/6if/kH/wCvTh8No/4tSfOO0Q/xoA8/orT17Q7nQr3yLjDI3McgHDj/ABrMoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbb/AI+of+ui/wA6iqW2/wCPqH/rov8AOgCKiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAUAsQB1JwK9a0Hwpp2mWsbSwJPclQXkkGcH0A7V5KCQwI6g5FetaH4s03UbVBLOlvcKoDpIcc+xoA1LK6tLiWaG1XmA7XxGVAPoDjmrtMiljmjWSJ1dG5DKcg1Gby23Ov2iLcn3hvGV+vpQBPVR71ftIghRpnzhyn3Y/qf6VWaebU38u0JjtB9+46F/ZP8AH8qyPEXie18Pw/YrBEe6x90dI/c+poA6G91Gz09N95cxQA9N7YJ+g71yGs/EKKMNHpMXmt/z1kGFH0HeuDvby4v7lri7laWVupY1DQBZ1DUrzU5zLezvK3bJ4H0Haq1JRQAUoOCCDgjpSUUAdBpHjDVdM2oZftMI/wCWcvOPoetdtpPjfS7/AGxzubSY9pPu/wDfX+NeVUlAHvasGAKkEHkEHrS149oPie/0VwqOZrbPMLnj8PSvTtF12y1q38y1k+cD5om+8v4UAaeailWO5ilgZuCNrbWwRn+VZ+pajf2Uh8nS5LuMr8rxOMg+hB/nR4ehuYtN8y+UrdTu0sinsSeB+AxQBm+LNHjbwnJGHkke0XzEklbc3HXn6V5VXZ+NPFNxNd3Gl2jKtqvySMOS578+lcZQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFS23/H1D/vr/Ooqltv+PqH/fX+dAEVFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAdb4L8ULpTGxvmItHOVfr5Z/wrtbiPw7PIL+4+wOwGfNZl5+vrXjtGKAO/8R+OUWNrXRTk4wZ8YA/3R/WuCd2kcu7FmY5JJyTTaKACiiigAooooAKKKKACiiigAqW3uJrWZZreV4pF5DIcEVFRQB2em/EK9gQJfW6XIH8ana349qXVfiDPc2zQ2Ft9nZxgyM2SPpXF0UAKSWJZiSScknvSUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUtt/x9Q/76/wA6iqW2/wCPqH/rov8AOgCKiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqW2/wCPqH/rov8AOoqltv8Aj6h/31/nQBFRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVLbf8fUP++v86iqW2/4+of8AfX+dAEVFFFABRRRQAUVpaLpsGq3K2z3ot5nbagaMkN+Pata+8KWWn3Agu9etoZTg7WQ5H19KAOXorc1fwvd6baLexyxXdm3PnQnIH1rGhiknlSKFC8jnaqgck0AMorpZfDunaXGi61qhhuWGfIgTeVHvSHwqJYJb2yvo7mwSJn80cMpA4UrQBd8K+F7PV9Dubq5Z/N3Mke042YHX3rjyMEj0rtvDul3Muj3TaXrqx2rgidWhwV45+nFYul6FZarfG0t9VAkOdm6EgP8ATmgDCorR17SJdE1JrSVxJhQyuBgEGs6gAoro9L8Jvf6K2qS3iW0K7j8yE8DvWDcJEkzLBKZYx0crtz+FAEVFFdNonhWHW4S1rqsfmKPnjaMhl/xHvQBzNFaeu6XFpF59lW7FxKv+sCoQF/HvWhp/hm2vtKfURq0cUUXEu+MjafT3oA5yiumtvCtvPpy30msQQW7sVRpV27sH61OPBdv9i+2/27a/Zs483Hy5+uaAOSoro7/wvFbaRNqNtqtvdxxEAiIZ6mqejaDJqUMl1PMlpYxffnk6fQDuaAMiiumg8P6TqRaHSdXZ7sAlY549gf6GueuLeW1uHgnQxyxnDKeoNAEVFS2yQyTKtxMYYz1cLux+FdJe+EYLHTU1CfV4/s7gbGWIktnpgUActRV3TrW2u7oQT3n2cMdqOYywJ9/StbXfDEWhRKbnUlaWQExxrEctj8eBQBzlFFaT6TInh+PVSTteYx4x2A6/nmgDNopaBgsMnAzyaAEorqbTwjb3mmtqEWsRfZkzvZoiNuPUVW/sLSf+hktf+/bUAc/RXVXHhC3ttMGovrERtT911iJ3fSmaN4Ug1qJ3s9VQmP76tEQVoA5iirt/a2trdiGG989AcPIIyAvPYHrW7beEILrS21GLV4/sqglmaIjbjrkUAcrRXQaP4etNZuXt7TVR5qgkB4SNw9RzVz/hC45Lh7W21qzlukzmHoaAOTorbtfD5bVTpl/cizuiwVFZCyvnpgipNf8ADsOhYjm1FZLhhuWJYz09z2oAwKK3NA0CDXG8qPUViucFjE0ZPHse9LeaHY2WpCxm1dRIDtciE7UPoTQBhUV0mveFU0K1Es+oo8j/AOrjWM5b/Cm+H/DCa9AzQagkcyffiaM5X0Oe9AHO0Vfu7K0ttT+yi+DxK215ljOFPfjvW1qPhGHTdPW+uNWj8l8bNsRJfPIxQBy1FdTpfhGHVbJrq11aPy0+/uiIK8Z5rAuLeJbwQWc/2kEhVYIVyfTBoAq0V11/4GmsdEkvXu1aWJdzxheB7Z9ay9J0A3lm9/e3C2VghwZnGSx9FHegDForpovDunanG66LqhnuUBPkTx7C49q5yWN4ZWjlUo6HaynqDQAyinxRSTzJFEheRztVR1Jrqn8ENbac95eanDCsY/eAKW2n0470AVvBWh2ut3863m4xwx7tqnGSTisjWbJdO1e6s0YskMhVSfSuh8J6e0t7I2ja0I7hVO5XgPzLnrg9axNetY7XUJU+3fbLjefObYQA3170AZlFFFABUtt/x9Q/9dF/nUVS23/H1D/10X+dAEVFFFABRRRQBoaB/wAh+w/67r/Otb4g/wDI0Sf9ck/lWToH/IfsP+u6/wA667xh4em1HXTPHeWcQZFXZLLtYY9qAG/D+Q3ml6np0/zwbcgHoMgg/wAqreA7GOA3+rzqGW0UrHn1wST+X861EtP+EN8L3Lpuubq56yRrlV4wOfQZqDSR5HwyvJBw0gkJPrk4oA4a9upL28luZmLSSsWJNanhi/a2nurUtiK6t3Qg9NwUkGsSl6dOKAO18Fk/8ItrozwImP8A44awvCBK+JrEjsx/ka3fBf8AyLGvf9cm/wDQDWD4R/5GWy/3j/I0AdF4/iS+07TtXiHysNjEeh5H9a4ZVLsEUZZjgAetd7pH/E58D6jp/wB6W2Zig/HcP6iuY8LW63GuQtKP3VvmeTPoozQB3N4I4PBWoWMWMWcAiYju2AT+pry8V3mn3LXngjXrlzlpZHc/jiuDoAWtLw9qj6RrEF0CdgbbIPVT1rMooA7H4haZt1GDULcbo7sAcf3u35is7XWGmadbaHE3zp++uiO8h6D8BXV+FLtdX8MeXNEJ57BsoG7kDK/4V5zdTS3F1LNOSZXcs+fWgCe8vPtFnZW6lttvGQQem4kkn+VdSv8AySx/+u3/ALPXFV2q/wDJLG/67f8As9AHN6VO224sV3H7aqxgDpu3DB/nXR+OmXTbHTtEtvlijTe4H8R6DP6msLwnGJPE1gp6CTP5CtH4iOW8SlT0WJRQBzcE0ltPHPExWSNgykdiK7Hx3ax3VjYa5CoBnQLJjuSMj+oriq7/AFD978L7dm6qFx+DYoA4A9K7fxH/AMk+0j/eX+RriD0r0S90ybVfA2lQwSQxsu1syvtHQ0Aef2//AB8Rf74/nXYfEv8A4/dP/wCuJ/nVCLwffLMjG7sMBgeLgetaHxMGL6wH/TE/zoA4sAk4HU16ZPZQz+DLnSohmeyiVmH+3jdXCeHbUXuuWkTf6sPvf2VeT/Kuo8G6t9q8U6gkhyl6GIB9jx+lAHC0tWtUtDY6pc2pGPKkKj6Z4qpQB2/h7/kn2rf7zfyFcR2rt/D3/JPtW/3m/kK4jtQB22r/APJNtO/31/rTfhwT5mpjP/LEH+dO1f8A5Jtpv++v9ab8OP8AW6n/ANcB/WgDjX5kf13H+dehR2R074c3kLNmVk3yL/cLY4/LFclodtG95Ne3Qza2QMrg/wARz8q/ia6W0uJLv4fatcTHMksrMx+pFAGT8Pf+Rnj/AOuT/wAqz9UuJLTxVc3ETFXjuiwI+taHw9/5GeP/AK5P/Kpbjwxqd/4pnzayJbvOXMzD5dufWgDe8XRI+paBfAYkadVJ9sg1gfEb/kZP+2K/1rT8U6nDceJNJ0+2YOtrMm8g8Bsjis/x9DJceLI4Yl3SSRoqj1JJoAl+HNhu1Nr922qgMcYP8bEc/kKwvEf/ACM19/13NdP4dmSPxZaaZbtmCyhdSR0eQj5m/Pj8K5jxH/yMt9/13NAHRfEj/mF/9cj/AEqP4aZGp3v/AFw/rUnxI/5hf/XI/wBKj+Gn/ITvf+uH9aAOQuCTcSk9S7fzrs/FhJ8F6Lk/3f8A0GuLn/18v++f512fiv8A5ErRP+A/+g0AL4HY/wDCO64M9F/9lNYHhSJX1uOeQZjtUad/+AjI/XFdD4AiM+i6zEpAZwFBY4HKmqWpaZJ4X8PvG37y5vzseVPuIg52g+poAyLK5v8AVL37AtzKI72bMibjg5OTxW18QJ1gns9Itxst7aIHaPU//q/WqHgVA/im13D7oZh9cUeOWLeKrrPYKP0oAx9Pu5LC+huoiQ8ThuO/tXU/ELT40uLXVIAAl2vz4/vYyD+X8q449K9B8UATfD7TZTyV8rGf93FAHP8Ah/bpmmXetuAZU/cWwP8AfI5P4CtfzHf4Xyu7FmeUliep+esfxF/oel6Tpo4KQ+fIP9pz/hWun/JLH/66/wDs9AFP4b/8jE//AFwb+Yrn9X/5C97n/nu/8zXQfDj/AJGNv+uDfzFc/q//ACF7z/ru/wDOgCpRRRQAVLbf8fUP++v86iqW2/4+of8AfX+dAEVFFFABRRRQBoaB/wAh+w/67r/Otb4hf8jPJ/1zT+VZvhqGSfxBYiNC2JlJwOgHJrd8d6Xf3HiBp4LOaWJo1AdEJGaALfw7v5LkXWlXR82DZuRW5wOhH0q/bW6jwdrOnRfMbaSZAPYHIqr4Q01vDtld6tqw+z7kwqN97HXp6n0rO8I+IETXLuO9YLBqLknPRWJ4z9c4oA48UfSt/wAQ+GL3S76TyYJJrVzmORF3cHscVHZ2MmlWranfRmNsFbWJx8zuf4seg/nQBt+C/wDkV9e/65N/6AawfCP/ACMtl/vH+RrpfBdrO3hjWcRtumRlQEfeOw9PxNc/4Otpn8TWoET/ACElsqeMA9aANHwDe+R4imtXPyXKsuPcHI/rRd2X9g2muSY2tNMLaH/dPzHH4Yrn4JptL1pJ2Vkkgn3EEY6Hmuk+I2orc3lpbQnKLH5px3LdP0FAEmg/8k71f6t/IVxNd5oFpMfh5qSiJ98u8quOSBjpWNoPhOXWdKubtZ/LeNiscZX7xAzz6daAOcpyqzsFUEsTgAdzSHgkHqOK6fwro86rNrU1uzQ2iM8SEf6xwOOPQUAaml6nH4d1Ww0ZSNhH+lt/00bp+XFYvjjS/wCzdekdFxDc/vF9Ae4/OsGaaWa4eaRiZXYsx75zXo2q2M/iPwVa3JjP2yKMOARy2OD+YGaAPNa7Vf8Akljf9dv/AGeuMKOrFWUhhwVI5ruhZzj4YFPKbeW8zbjnG6gDlvDc4tvENjKxwolAP48Vs/EeIp4gSTtJCP0zXKAsjAjKspyPY12+r/8AFV+G7e9tRvvrMbZ4h94juQP1oA4eu91hxafDWyhb702wAfiWrlNH0W71W/S3jhdUDfvHYEBB3zWn401aK9vIrKzbNpZLsUjoW6E/pQBzR6V2/iL/AJJ9pH+8v8jXEgFuFBJ9BXeeJLSdfAOmJ5bboyhcY5Xg0AcNb/8AHxF/vj+ddj8S/wDj90//AK4n+dcjZRSTXkMcSM7mRQAB712PxLglN1YSCNigiK5AzznpQBl+FPs1pbajqV75nkJGIB5f3svxx+FWtLvvC9jqUFxbR6gkqsMFmGOeOfaq2q2Nxp3hDT4miZftErTS8dOMKD+Fc1gnpnNAHV/ESz8jXVuVHy3MYbPuOD/SuUr0PxPZXGqeDbC88pjcQIrOuOcEYP8AQ157QB23h7/kn2rf7zfyFcR2rv8Aw5Zzt4B1JPKfdKXKDHLcCuC2Nu2bW3emOaAO01f/AJJtpv8Avr/Wm/DjJl1MDr5I/rVrWbKdfh1Yx+U++MqzrjkDmmfDWCZG1C48tgNgVSRgE8mgDA1rGm2MOkof3rHz7o/7R+6v4D+dbek/8k01H/fb+YrkdQa4kv53ulYTNIS24c5zXbaNZTt8OL6Pyn3ybmRcckcf4UAY3w9/5GeP/rk/8qo63fXcetX8cd1Mkfnt8okIHWtL4ewSnxIJPLYIkTbiR0rJ8SwSQ+IL4SKRmYkEjrmgCDRif7bscnnz0/8AQq7bxZjT9ZuNXcfOkKw2wPeQ5yfwFcd4et5LjXbFY0ZsToTgdAD1roPiU1y2rQK4b7OsWUOOMk8/yFAFP4fkt4qjJOSY3JP4VneI/wDkZb7/AK7n+danw8glbxIsoRjGkT7mxwO1UfEdpcf8JTdx+S5aSbKgKfmB9KANz4kf8wv/AK5H+lR/DT/kJ3v/AFw/rV34gadeXQ077PbSTbEKtsXODxUfw8068tb+7e5tpYVaHaC6kZOaAOGn/wBfL/vn+ddn4r/5ErRP+A/+g1zlzoeqC5lH9n3J+c8iM8811Pi21nTwZpKmJt0O0SDH3fl70AQ+B/8AkXtc/wBwf+gmpvC06+IPDd1od02ZYlzCx6gdvyP86XwNZzf8I7qx8th5w2pkfewp6fnXKaHfy6LrUFywZQjbZFIxlTwRQBd8Is1j4tto5htYO0TZ7HBFSePoTF4omYjiRFYflj+laPjfSpLbUYtbsFLRS4dmQZ2sOQfoad4lj/4SXR7XWbBd80K7LiNeWX8Pr/OgDiTXofiVSvhfQ9Ox+8neIEfRef51ynh/Q59Vv0VkZLWM7ppGGFVR1rqTM+ueJDqEMTvpuloTEQv+sYdMevP8qAOY8YTCXxJdKv3YdsS+20AfzzW6n/JLH/66f+z1zc+latcXEkz6fdbpGLH92e9dd/Z91F8M5YJYHSYEvsI5xuz0+lAGT8OP+Rib/rg38xXP6v8A8he8/wCu7/zrpPhvBJ/bsspRgiQkEkcZJFc/rkMkOtXqyoynzmPIxkZoAoUUUUAFS23/AB9Q/wC+v86iqW2/4+of+ui/zoAiooooAKKKKALdnqd9YKy2d3LAGOSEbGTVn/hI9a/6Cl1/38NZdFAFm6v7u9IN1cyzY6b3JqvSUUAbFp4p1qygEMN8/lgYAcBsfTNZ95fXV/OZ7ud5pP7zHp9Kr0UAaa+IdYUALqdyAOwc0i6/q6MzLqNwGbkkP1rNooAsXl9dX7q95cSTsowC5zio2nleVZHkZnXAVieRjpUdFAGoPEWsgYGp3IHp5hrS8Maxqf8AaTM95J9mXM9yWPUAdz78CuZqwLydbI2avthZtzBRjcfc9xQBDI++V36bmJrRXxDrCIEXUrkKBgASHAFZlFAEjTSNMZmcmQtuLd8+taA8R60BgandAf8AXQ1l0UAWHvbmS7+1vO7XGc+YT82auf8ACRazjH9p3WPTzDWXRQA+WR5pWklYu7HLMepNS2V7c2E4ntJ3hkH8SnH/AOuq9FAGvd+JtYvIDDNet5bfeCKE3fXArIoooAlt7ia1mE1vI0Ui9GU4Iq8fEWskYOp3RHp5hrMooAs2moXdlI0lrcyQu/3mRsE1abxDrDDDalcke71mUUAaE+u6rcQtFPqFxJG4wys+QRVGOR4pFkjYq6nKsOoNNooA1P8AhI9a/wCgndf9/DWcZHaQyFiXJ3Fu+fWmUUAag8RayBgandAe0hqodQvDefbDcSfaf+eufmqtRQBqHxFrJGDqd0R/10NIviHWEXampXKgdg5rMooAtXOo3l3Mk1zcyyyR/dZmyRVr/hI9ZAwNTugP+uhrLooA0l8QaumdupXK5OThzzUF5qd9fqq3l1LOFOQHbODVSigC3Z6le2AYWd1LBu5OxsZqS51rU7uFobm+nljbqrvkGqFFAF+21nU7OEQ219PDGvRUfAFPOv6uzq7ajcFl6Eucis2igDU/4SPWv+gpdf8Afw0f8JHrX/QUuv8Av4ay6KANT/hI9a/6Cl1/38NI3iHWHUq2pXJB4IMhrMooA018Q6wihV1O5AHAAkNUrq7uL2Yy3UzzSEY3OcnFQ0UAaCa5qscAgTULgRBdoQOcAelQ2Go3mmzebZXDwv32ng/Ud6q0UAat/wCI9W1CAw3N4xiPVEAQH64610k3iGTRvBumQWZVbqeMndjOxQTz9a4arF1dNcpbqwwIIhGPzJ/rQBcPiTWiSf7Uuuf+mhra8L+Lr+LU4rfULlri2mbYTJyVJ6HNclSqxVgwOCDkUAb+t61qdprt/DbX08MSTuFRHwBzWReaje3+z7Zcyz7M7d7Zxmm310b2+muWXa0rliPTNV6ACiiigAqW2/4+of8Arov86iqW2/4+of8AfX+dAEVFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUtJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRS0lABRRRQAVLbf8fUP++v86iqW2/4+of99f50ARUUUUAFFFFABRRRQAUUUUAFFLSUAFFWU0+9kj8xLOdk/vCMkVXIIJBBBHY0AJRRS0AJRS0lABRS8+lJQAUUv4UfhQAlFLRQAlFLRz6UAJRRS0AJRRRQAUUc+lH4UAFFFL+FACUUdKKAOg8J+G/+EglmMkpighAyVGSSe1Zutaa+karNZSNv8s8Nj7wPINbfgnxFb6HJcR3iv5MwDBlXJBFZPiLUxrGtT3iKVjbCoD12jpmgDMoopaAEopaPwoASij8KKACiil/CgBKKKX8KAEoopVBZgqglj0A5NACUVZlsLyGPzJbSdE/vNGQKrUAFFFKqlmCqCSegAyTQAlFWJbC8hj8yW0njT+80ZAqEIzKzKpKr1IHAoAbRS/gaPwNACVe0bTX1bVIbKNtpkPLY+6B1NUq0vD2pDSNZgvGUsiEhwOu08GgC94s8Nf2BJA0UzSwTAgFhggjtXPV1PjXxHb621vFZq5hhyxZhgkn2rlqACiiigAqW2/4+of8Arov86iqW2/4+of8Arov86AIqKKKACiiigAooooAfFG80qRRqWdyFVR3Jr0fw9Zafo+ow6QY0nv5YjJcSEZCYHCiua8HQRwvd6zcLmKwj3KD3c9Kn8D3Mt54yNxOxaSVJGYn3oAl8davNHqc2mW6xRW6oofbGuWJGev5U/QdItNH0Vtf1eISNjNvC3TnocepqDXbH+0viI1ofuySR7v8AdCgn9Kv/ABLudgsbBMLGoMhUfkP60AY8njnW2ufMSaOOMHiIRjbj09a2Lm0tfGOiPqFpCsOpwcSIvG/2/wAK4Out+HF20OuyW2fkuIjx7jkf1oAo+FdXuLHU7azIje3lmCvG8YPU469RXW+MdbfQbi2jtrO1cSozHfH6Gue1zT10/wAewCNcRzXEcqj0ywz+tXfif/x/WH/XN/5igA0jxsJr+KC/0+1WKRgu+Ncbc96ZYa1/Zniy6sbiOFrOW5YcoMoSeCD6dK4tSVII6g5FS3dzJd3UlzJgSSHcdvrQB6N49uptN0+D7FHFGszlJHEYJ6dOleZ16dcf8VN4DEg+adEDcf306/n/AFrzGgD0HwtrQXwzeXN/DFItlhY22AFuOB0qHSfESw6LfarfwQyyvOI4o1QAdOnTpWRfn7B4Jsbbo97M07/7o4FYZu2OnJZ7QFWQybs9SQB/SgDuvD3iptY1mKyk060jRwxyq8jAzTvEvidtF1hrOLTrWRFRW3MvPNc74E/5Gq2/3X/lUnxB/wCRok/65J/KgDV1XxAl74ai1GwhjtriGcJINinaSPccg1u2GopN4POqyW8BmWFmOEABYcV5hHdtHYT2m0FJnVyfQrn/ABrvNM/5JjP/ANc3/nQBwmoajc6lP510ylgMDagUAfQVWpKWgDf8H6Aut6i3n5+ywANJjjcewroNe8R2WhXR0/StNtWaLiRmQYB9PerHwzVf7Ku2H3zNz+VcDqbtJqd2zn5jM2c/WgD07SNUj1DwzNqktnbxvGrnaqAj5RXmWo6lc6nKJLplYgEKFQKAPwruvDX/ACTy8/3Zf5V50OlAFqw1C502Yy2rqrEYOVDAj8a9NvtTFv4RTVo7S3MzRo21kGMmvKD0r0fVf+SZQ/8AXKP+dAGLaeN3MoXUNOs5YGPzBYwCBVvxZ4csn0xda0dQsRAZ0XoVPcDtXD16Z4WJm8ASrNygSVRn05oAq+AtVl1OW4tL1IpfKQMjeWoIHTHSsHxjq88+q3dgojjtYn2hFQDOO+etX/hj/wAha7/64D+dYHif/kZdR/67tQAug6zcaXdIsfltC8g8xHQNnt36V1HiPxTPpWsy2lvZWbRoFILx88jNcLD/AK+P/fH863PG/wDyM0/+4n/oIoA7Twfqp122upbq0tkMLADy4+2M1xV54p1FtQeWJoURXOyMQrgDPTpXS/DP/kHaj/vr/wCgmvPpf9dJ/vH+dAHq1/qKQ+DxqsdvAJnhVh8gwGNeXXl3NfXDT3DBpG6kKF/QV32qf8kxg/65p/OvOqALum6nc6XKZLZkG7G4MgYMB25FekeJNRFj4XS/toIUmmCBSYwdu4Zryo9K9F8X/wDIiWP/AGy/9BoA4KS9nkvftjuDPuDbto6j26V6T4I1BtX0ud72KGSWCTbvEYG4YzXl1eifDP8A5Beof9dR/wCg0AcjqupXmtX/AJJVceaViijQDHOB0rprhbbwPpcWyOOfV7gZ3uMhB3x7fzql4C09brxHPcuMrbbmH+8Tgf1rP8bXjXfia5ycrDiJfw/+vmgCxZ+OdXiuA13IlzAx+eNkA49sVY8WaHbPYx67pKgWswDSRqOFz3Hp7iuQr0bwC66j4cvNNn+ZFYrg/wB1h/jQB57DFJPMkMSlpHYKoHc13cyWfgfS4isSXGrTj7zDIX1/AfrVLwLpH/FS3LTLn7DlRn+9nA/rWV40vWvfEt1zlIT5S/h1/XNAE0fjfWxMWlmjljPWJoxtI9K3LqLSrvwjd6jptuIZJ5IvNjH8LBxwB2HNcBW3ol+YNL1S1ZhteNZUUnqysD/KgDp9e8XQaZfGys9Pt5jEAHd14z6DFa2m6mt54Vl1Z7K3WVFchAvHFeW3dw11dy3DjDSuWIHvXoWgf8k3uP8ArnLQBn6d46jku40vdNtkhcgF415X35p8erS2vjmSwCwvZyzhdhjXjIHIOK4StvSrxr7xZY3DqFZpYwcHPQAf0oA6jx7qkumSW9rZJFF5iFnYRqSR0x0+teeV2fxN/wCQvaf9cT/OuMoAKKKKACpbb/j6h/31/nUVS23/AB9Q/wC+v86AIqKKKACiiigAoopyqXZVHVjigDpr/wD4l3gWxthxJfymZ/dR0/pSfDz/AJGhP+uL/wAhR46YR39lZLwtraouPc0fDz/kaE/65P8A0oA3raMP8Urkn+CPI/74FY3xHbPiFF/uwqP51s2sgT4pXQP8cWB/3wKxviOm3xCjf3oV/maAOUra8GOV8U2OO7Efoaxa2fBwLeKbHH98n9DQB1XjWMDxPocvdpFX8nFTeOdA1HWbq0ewhWRY0YNlwuCT71D41lB8UaHF/dkVvzcVX+JVzPBe2AhmkjBRjhWI5yKAOZv/AA3q+nRmS5snEY6upDAflWTXoPgXxFcXs76ZqEhmyhaN35OB1U+vFc34x0tNJ16WKEbYZQJUHoD1H5g0Abvw01HE1zpsh4ceag9xwR/Kud1/SXs/Ek1jGvEkgMXuGPFVdFv20zV7a7U4Ebjd7qeD+lem6xpUV3q2navwY7YM8h9VAyv60AcJ4zlX+10s4z+7soVhGPUDmufqe9uWu72e5c5Mrl/zNQ0AdB4E/wCRqtv91/5VL8Qf+Rok/wCuSfyqLwJ/yNVt/uv/ACqX4g/8jRJ/1yT+VAHM16XoFtLe/Dx7aBQ0squqgnGTmvNK9H0h2j+GkzoxVhHJgg4I5oA5eXwZr0KbjZbsdkkVj/OsSaGSCVopo2jkXgqwwRWroviXUNKukcXEksGRvidsgjv9DXXeP9OhvNHi1aFQJE2ksB95G9f0oAw/AWuRaZfyW10+yG5xhj0Vh0z9ak8X+F7uLUZL6xhae2nO8+WMlT349K5FVZ2CqMsxwB616fczt4T8GRp5hN0V2qWOfnPJ/KgCv4fhkg+H94k0bRttlO1hg9K83HSvTtIvrrUfAl7cXkpllKSjccdMV5iOlAAeleoTWNxqXw9trW0TfM0KELkDOD715eelel380tv8N4JIJGjcRJhlOD1oA5e28Ea5PMEe2WFc8u7jA/Kul8SX1t4c8NLo1s+64ePZjuAerH681xVp4g1a0mEsV/OSOzsWB+oNehJHaeM/DQlkiVbjBUMOqOPQ+lAHO/DL/kLXf/XAfzrA8Uf8jLqP/Xdq6H4ao0WtX0bjDLFtI9w1c94o/wCRl1H/AK7tQBnQ/wCvj/3x/Otvxqc+J7j2VP8A0EVj2q77uBcZzIox+NavjJt3ii89io/ICgDqPhn/AMg7Uf8AfX/0E159L/rpP94/zr0H4Z/8g7Uf+ui/+gmvPpf9dJ/vH+dAHpj2Fzqfw9tbW0QPM8SEAsB0PvXHXHg7XbdC7WJYD/nm4Y/oa6rUJHi+GkDxOyN5acqcHrXN+GvFF7p1/FHNO81pIwV0c5xnuPSgDnpEaNmR1KspwQRgivS/ENjdaj4LsYLOFppMRNtXrjbWf8SNKiWOHVIVAdm2SkfxccGr3ia6uLPwTYy20zwyYiG5Dg420AcZ/wAIrrv/AEDZv0/xrt/Aem3mmadepfW7wM7gqG7jFcD/AG/q/wD0Ern/AL+Gu7+H17dXum3zXdxJMyyAAu2SBtoAj+HEYEGoydzPj8q4TWmLa1ek9TO/867v4cSAwajH3E+a4XWlKa1eqeonf+dAFGu6+GD/AOkX6Z6qp/WuFrufhgmbu+f0RR+tAHT6DbLBqetSL1e5H/oIP9a8p1J/N1O7kJyWmc/qa9Y0GdZr7WAp5W7wf++QP6V5NqCeXqN0ndZXH6mgCtRRRQAV6RoH/JN7j/rnLXm9ekaB/wAk3uP+uctAHm4rT8N/8jFp/wD13X+dZgrT8N/8jFp//Xdf50AdB8Tf+Qtaf9cT/OuMrs/ib/yFrT/rif51xlABRRRQAVLbf8fUP++v86iqW2/4+of99f50ARUUUUAFFFFABWl4etDe69ZQAZBlBb6Dk/oKzgCxAHJPAr0Hwt4XvtPsrm9eNVv5IikEbt9zPcmgDkvE94L7xDezKcr5m1foOK0vh5/yNCf9cn/kKc3gPWCxLSWuScnMv/1q3fCHhS80nVjeXcsJAjKqqNkkmgDI1q9/s74jG6bhEkTd/ulQDV74mWu4WN8nKYMZI/Mf1qp4+0W9/tWbUo4We1ZFLOP4SOOasaHqlr4g0JtB1OUR3CgCCRv4sdPxFAHC11Xw7tDP4hM2MpBEWJ9zwP61Ul8Ga4l0YVs94zgSKw2ketbs1xb+DNDeyglSbVbn/WMn8H/6u1AGdrl+uoePYDGcxw3EcSn6MM/rmrnxQ/4/rD/rm/8AMVk+FNFvNQ1W2vBGfs0cwZ5WIAyDn8TXV+NfD95rlxayWTwkRKysHfHU0Acr4CRn8VWxUZCo5PsNpH9avfEt1bW7dRjcsHP4k1t6Hp1h4QtJbvUbuE3TrghTkgf3VHU1wet6k+r6rPeOMbz8q+ijoKAKFd7/AG4//Ct/nyJifsoJ/iHqPwp+l+DNN1Tw5azpI8dzIu5pVbIz3GKwvF13bI9tpGnsDa2K4LD+Jz1NAHOYooooA6HwJ/yNVt/uv/KpfiD/AMjRJ/1yT+VW/AWj3a6tHqM0RjtlQlXYgbs8cVJ4+0e8l1RtRgiMtv5Q3spHyY65oA4qvRdL/wCSYz/9c5P51wNnaXF9cLBaxGWVuiivUbPSJovBR0otGty8RBBYYDE5xmgDyevUtd/0f4e7JfvfZ4159eKwtH8ESQXa3GsT26W8RDbFkB3Y9T2FWvE99L4mnTSNF2zRRHfNIGAXPQc+goAx/Aek/wBoayLiRcw2vznPduwpfHurf2hrP2aNsw2vyjHQt3P9K6oJB4O8JybJFe4IPzD+KQ9PwH9K80gguL658uFGmnkJOB1J70Ad94a/5J5ef7sv8q86HQV6voOky23hBtPuNqTyo+V3Dgt0BrzPUNNu9MlEV5CYmP3c9GHqKAKh6V6Pqv8AyTKH/rlH/OuBsdPutRmMVnC0rgZIHYepr1G80aS58IR6Ss0azLGilmPGRQB5LXpXwzVho90xB2mf5c/QZrBh8A6kZQJ7i1iizy+/PH0rd1PWdO8M6D/ZmmTJLc7So2nOCerEjvQBU8DusnizV3Q5VtxH/fdcr4n/AORl1H/ru1df8PtIu7Ca5vLyMxCRAqBiMtznNVrjQmTxx9pv4Vk0+eYsHJBUkjgH8aAOV0C3N1rllCBndMp/AHNO8RTCfxDqEg6GdgPwOP6V32pado/hh7jWI18udkKww5+UMf7orzNVkuZwqgvLK3AHUk0Ad/8ADP8A5B2o/wDXRf8A0E159L/rpP8AeP8AOvUvA+lT6VpUy3oEcs0m7YWGQMY5riLzwpq6ag8MdoXVnOxww2keuc0AdRqn/JMYP+uafzrz2JS8yKvUsAPzr1XUdGmuPB8elQvF56xoOWwMjrWHoHhAabdrf61cW6JCdyIJBjPqTQBp+PmEfhSON/vl0A+oFVfF/wDyIlj/ANsv/QazfE15P4s1FLPSI2nt7Y/M44BJ789q6TxNpM174VSxtdrzQhCF3AbtoxxQB5RXonwz/wCQXqH/AF1H/oNcG9lcR3n2R4WW43BfLPXJ7V6V4F0q40vSpxeqIpJ5NwQkZAxjmgDnvh/fi28Q3Nq5wLkNt/3gc/yzWb43tDaeJrk4ws2JV98j/EVX1PTtQ0HUvPdDHtlLRSg5Dc5GK6m+S38caTFNaOkeqW4w0THGR3H09DQB5/Xofw+RbDQr7Up/ljLE5Poo/wAa56y8F6xcXIS4t/s0QPzyyMMAe3PNXfFWtW0GnxaDpL7reIASyL0Yjt788mgC18P9VMut30UrYN3mVfqD/gawvGVi1j4kugVwkx81D6g9f1zWVY3ctheRXUDYkibcK9GuoNP8c6SkkEqxXsQ4B6oe4I9KAPMqt2Vi12lxIXEcVvEZHcj8APqTW0fAutCbayQrGOspkG3H86razPa2NmukadKJlVt9zOOkrjoB7CgDDr0jQP8AknFx/wBc5a88tbaa7nWC2jaWRuir1Nep6NpM1v4ObTpiqzyRvldw4LdBQB5PWl4b/wCRi0//AK7r/Oq9/p13pswivIWicjIB7j1FbPg7Rry51e0vPKK2scm8yk4HHYUAaHxN/wCQvaf9cT/OuMr0T4gaNd6hLbXdnH5wjQq6qRkd8155QAlFFFABUtt/x9Q/9dF/nUVS23/H1D/10X+dAEVFFFABRRRQA+H/AF8f+8P513/jrxFNaeXptlIY3ZA0rqeQD0A9K4CIhZUY9AwJ/OretXg1DV7q6BJSSQ7Sf7vQfpQBUaSR2LNI7E9yxp0NzPBIJIZpI3HQqxFRUUAegR64+s+AtRFwc3MCBXP94ZGDXn/0rR0y+W1tNRgdiBcwbAMfxBgRWdQBeXWNSSLylv7kJ6eYapMxZizEknqTyTSUUAKGYdGI+hpd7/32/Om0UAKSSckk/WkoooAsw6heW8DQQ3U0cTdUVyAarUUUAFFFFAChmHRmH40bmP8AE350lFACgkHIJH0pd7/32/Om0UAKWYjBYke5oBI6Ej6GkooAUsx6sT9TQCRyCQaSigB29/77fnSEk9ST9TSUUAKCR0JH0NLub++3502igB25v7zfnTaKKAHbm/vN+dG5v7zfnTaKAJJZpZiDNK8hAwN7E4/OmUlFADt7/wB9vzo3P/eb86bRQA7e/wDfb86Qlj1JP1NJRQAoJHQkfQ0u9/77fnTaKAFyc5yc+tLvf++3502igBSWPUk/U06OR4XDxOyOOjKcEUyigC5Pq2o3MXlTX1w6f3WkOKp0UUAFPimlgkEkMjxuP4kYg0yigC5PquoXMflz3s8if3Wc4qnRRQAoJHQ4pdzf32/Om0UAKST1JP1NAZh0Yj6GkooAdub+83502iigAooooAKltv8Aj6h/31/nUVS23/H1D/vr/OgCKiiigAooooAKKKKACiiigApaSigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbb/j6h/31/nUVS23/H1D/wBdF/nQBFRRRQAUUUUAFFFFABRT4opJpFjiRndjgKoyTVz+xdU/6B9z/wB+zQBQoq//AGLqn/QPuf8Av2aP7F1QDJ0+5/79mgChRV5dG1NlDLYXBU8giM80HRtTUEnT7kAf9MzQBRoqTyZfNEXluJCcBCOc+mKt/wBi6p/0D7n/AL9mgChRVi5srqz2/areWHd93epGaZb2091J5dtC8r4ztQZOKAIqKvPo+pIjO9hcKqjJJjOAKo0AFFXl0fUnUMthclSMgiM81WuLea1lMVxE8Ug52uMGgCKir+naNqOqZNlaySqOrdF/M1LqHh3VtNjMt1ZusY6uuGA+uOlAGXRT4opJpFjiQu7HAVRkmrn9i6p/0D7n/v2aAKFFXjo2qAZOn3P/AH7NU5I3iYpIjIw6hhg0ANooqa2tp7uYQ20LyyHoqDJoAhorck8Ia7HD5hsHIAzgMCfyzWK6NG5SRSrLwQRgigBtFXY9I1KWNZI7G4dGGVYRnBFV54JraUxXETxSDkq4waAIqKWigBKKvDRdUIyNPuef+mZoGjamRkWFyR/1zNAFGirk2lahBE0s1lPHGvVmQgCqigswVQSScADuaAEoq/8A2Jqn/QPuf+/ZqnLHJDK0cqMjqcMrDBBoAZRS9TgVe/sXVP8AoH3P/fs0AUKKv/2Lqn/QPuf+/Zo/sTVP+gfc/wDfs0AUKKvDRtTIyNPuSP8ArmaSTSNRijaSSxuERRlmaMgAUAUqKK0dO0LU9TXfZ2kkif3+i/maAM6itHUtC1PS13Xlo8af3xyv5is6gAoopVVnYKilmJwABkmgBKKv/wBi6p/0D7n/AL9mj+xdU/6B9z/37NAFCirc2l6hbxNLNZTxxr1ZkIAqqoLMFUEknAA70AJRV/8AsXVP+gfc/wDfs1TlieGRo5UZHU4ZWGCDQAyiiigAooooAKltv+PqH/rov86iqW2/4+of99f50ARUUUUAFFFFABRRRQBr+FCF8S2LMcKrlifYA1qX/j3VpLuVrR44oNxCLsBOO2Sa5m2uJLWYSxHDgEA/UY/rUR6UAer65q97Z+EYNQgkC3LiMlioI568VgeHPGl/c6klpqTJJFP8gYKFKnt07VoeJ/8Akn1r/uxfyrzuCV4JkmjOHjYMp9xQB2vgC+uTrt3ZPO7xbWIVmyAQe1N8Q+L9W07xFcW8EsfkQsAEMYORgdT1qr8PHMnieWRvvNC5P1JFZnjH/kab/wD3x/IUAdrqNnbeK/DaalbRiO8VN6MvDBh1Un8Kd4D1yXU7KW1u5C9xbkfM3VlPr9Kj+G7M2gTK33FmOPyFcn4b1EaZ4t3E4illaJvTBPH64oAp+JpbttdvI7yV5GjlYKGPAGeMD6VSsri4tbuOS0keOYMApU4PWur+JGneTqMN+g+Wddrf7w/+tWH4Vsxe+IbSNh8iN5j/AEXmgDovH+u3AuE0uCVkVUDT7TgsT2+lZnhqxtrWym17UkDQQHbBEf8AlpJWTqU76vrs8ictcTYQfjgVreMp1t3tdEtziGxjG7H8TkcmgDsL7V7n/hBTqQfy7iSIEFf4STjiuG8OaVP4j1kLcSSPEg3TSMSTj0z711F//wAkui/65J/6FUvgqBdN8I3F+Rh5Q8mfZRgfyoAwvFXiGSK5OlaQ/wBls7b5D5XG8jrz6VW8OeKbuxvEhvZnuLKU7JElO7aD3Ga513aR2djlmJJPuabQB1HjPQxo1/HeWWUtpzuTb/A3oP6V0/grULm48LzvLKzyW5cKzHJwBkU24j/tr4do7fNIkAcH3X/9VV/AP/Irah/vP/6DQBy6+MNeWbzPt7Nz90qu38sV18C2njfw+7ywpHfRcblHKt2/A+leaV3nwvD7tQb/AJZ4QfjzQBxC2sz3gtVQmYyeWF/2s4ruNTePwVokVrZbf7SuhmSbGSB3P9BUXhuyjuvHt/OBmO3d3X6k4H9ayfHlybjxNOpPywqsY/LP9aAM621/Vba6FxHfTl85O5ywb6g11WtWUHifw8muWcQS8jX98q/xY6j6964Ou++GVxvjv7N+V+WQA+/BoApfDzUrkawbJ5na3eJjsY5CkenpU3xMsdl3a3yjiRfLY+45FVtFs/7P8bXtqvAjjm2/TbkVvaqP7e8AJcD5pY0D++V4NAHmdafhyy/tDXrS3IypcM30HJrMrpvCn+g6fqmrHgxReTEf9tqAOs+IN7PZ6HGLaRozLKEZlODjBNc74F8QXEOqJYXU7vBPwu852t2x9a1viH/yLlhnr5i/+gmvPIZWgmSaM4eNgyn3FAHa/EqW7S9tk81xayRHCA4UsDzn17VxAJByOCO4r0rxVEuveDodQhGXjUTDHp0Yf59K81UFmCqOScCgDvP+EhvbDwJbStMxu7hmjjkY5YKD1rhZJHlkaSRmd2OSzHJJroPGDC3k0/TF+7Z2yhh/tNya5ygArsPDOtX6aDrC/aZC8EIkiZjuKHODjNchW/4c/wCQPr//AF6j/wBCoAZH4u14yIDqMhBYD7q/4V2Xj/ULmz0S2FvK0bTyBWZTg4xmvM4v9cn+8P516D8Sv+QPp/8A11/9loAyPAOpXf8AwkC273EjxSo2VZiRkc5pfiFqFydcNoJnWCOJfkDYBJ659apeBf8AkarX6N/KpPiB/wAjRN/1zT+VAEfg7QBrWoF7gf6JB80n+0ey1L4m8TXFxdtZ6dKbaxgOxFiO3djvxXTaTH/Y3w9luBxJJC0pPu3A/pXmf1oA6vwr4knF2unapIbmyuP3ZEvzbSenJ7VS8XaF/Ymp7Ygfs03zRH09V/CsIMUYMpwQcivTPFUQ1XwTFe4zJGiTA/oaAPMq67R4ofDmirrN1Gr31ydtpG38I/vVz+iWB1PVra0HSRxu9lHJq94r1EX+uskR/wBHtsQxAdABwaAO18c6jc2nh2BreZo5JnUMynBxjJrnvA3iG4h1VLG6neSC4OF3nO1+351rfEP/AJFyx/66L/6DXnkMrwTJLGcOjBgfcUAdr8S5LpL21TzXFq8R+QHClgec/pXEAkHI4I716X4qiXXvB0OoQjLxKJhj06MP8+leaKCzBQMknAoA7z/hIb2w8B20rzM13cM0cUh5IUHrXCSSPK7PIxd2OSzHJNdD4wYW76fpa/ds7ZQw/wBpuTXOUAFFFFABRRRQAVLbf8fUP++v86iqW2/4+of99f50ARUUUUAFFFFABRRRQAUHpRQelAHpHif/AJJ9a/7sX8q83r0jxP8A8k+tf92L+Veb0AdX8OP+Rjf/AK4N/MU3xTomqXPia6kgsJ5EmcbHVMqeB37U74cf8jG//XBv5im+Jta1Oy8S30VrfTRIH4UNwOBQB1CeV4P8H7JnX7Sykhc/ekbsPYf0ry8sxbdk7s5z71Nd3tzfSeZdzyTP6u2ahoA9NvQPE3gMTKMzogf/AIGvX8+fzrlPDf8AoekavqZ4ZIhBGf8Aaatj4a6iN91psh4YeagP5Efyqt4wto9E0mLS4mH+kXL3DY9OgFAGZ4ItVuPEMckgzHbI0zfgOP1NZGoXLXmoXFyxyZZGb8zXQeFv9F0LXb7oRCIlPua5ftQB6Lf/APJLof8Arkn/AKFV1MQ/Db5en2I/r/8Arqlf/wDJLov+uSf+hVdsz9q+G+F5P2Nlx7jP+FAHloooFFAHqfg4eb4JCN0Kyr/Oqnw9j8zw5exAgbpWXJ7ZWrHhV/s3gMyscAJK386qeAufC2oH/af/ANBoAx08B3Tz7BqViRns5J/KulmSLwX4bdbaOSeZ85kCcbsdT6AV5cCVbcpIIPUda9H8Ba1LqdvPp183nNEuVL8lk6EH1xQBX+GeZH1GZzlmK5Pvya5PxMxbxHqBP/PYiu88L2SaV4k1ixj+4QksY/2TXDeKozF4lv1PeUn86AMiuw+Gjka1cL2aD+orj67H4aITrNy/ZYP5mgDRnQL8RbrH8Vox/wDHKd8ObtbjT73TZeQrFgD/AHW4NQtMJfiLe4/gtnT8krB8E332LxLCCcJPmJvx6frQBkajatY6hcWr9YpCtbup/wDEu8IaZZdJLtzcyDvjt/Sr3jXR2l8WWvlrxfbR+IOD+lZPjG6WfxA8UZ/dWqrAgHTjr+tAHUfET/kXLD/rov8A6Ca85r0b4if8i5Yf9dF/9BNec0AeifDu9W80u60qf5gnKg90bg/r/Oub0zR2j8Zpp8o+WCYsx/2V5z+VQeEtR/szxBbSlsRyHy3+h/8Ar13fiW1i037frikCR7XyVH+0TjP5UAec61eG/wBYu7kniSQ4+nb9Ko0UUAFdB4d/5A2v/wDXqP8A0KufrodCITw3rznvHGn5mgDBi/1yf7w/nXoPxK/5A+n/APXX/wBlrz6H/Wx/7w/nXoPxK/5A+n/9df8A2WgDnPAn/I1Wv0b+VdB4r8K3uq61JdQT2qIyKMSSYPA+lc/4F/5Gq1+jfyqT4gEjxPMAT/q07+1AHYeKYvsfgaSAY+SKNDjp1FeVV6lrLfa/h4ZF5zbRt+WK8toADXqlkvnfDkK3ezb9M15Wa9Rik+y/DUMxwfshH5//AK6AOV8GAWsOqaqw/wCPW3Kof9pq5pSWkBPJLZNdNB/ofw7nbo15dBfqB/8AqrmU++v1FAHonxD/AORcsf8Arov/AKDXnNejfEP/AJFyx/66L/6DXnNAHonw7vVu9LutKn+YJkqD3RuCPz/nXN6ZozR+M00+UfLBMWYn+4vOfyxUHhPUf7M8QW0rHEbny3+hru/ElrFppv8AXFIEjWvkKP8AaJxn8qAPOdavDf6xd3RORJKSPp0H6VRoooAKKKKACiiigAqW2/4+of8Arov86iqW2/4+of8Arov86AIqKKKACiiigAooooAKD0oq5ptgNQmMZure3VQCWmfaMe3rQB3nif8A5J9a/wC7F/KvN69Q1p9Kv/DY0yHVbVXRFCM0gwStea3dubW4eEyRy7f4o23KfoaAOl+HH/Ixv/1wb+YrO8Yf8jTf/wC+P5Ct/wAFW9npNw97eanZhpIwqIJMkZ55qh4xsra41CfUrPULSWNwC0Yk+fPTgd6AOVoorY8KizPiC2GobDASfv8A3c44zQBH4Znng8Q2L26lnMoXaO4PB/Srnje++3eJJ9pykAES/h1/Umut1dND8NtLqltHGL6RCsMatkZP8QHauR8NeH5fEd3JJJKEhRwZT/Ec88UATz/8S/wFDEeJNQuN+P8AYX/Irma9I8UeHrS6uIDcatDZQxRiOGFgOAPxrD/4RbSP+hktvyH+NAG1fAn4XRY7RIf/AB6k+Hl6l1pN1pch+ZMlR6q3X9a2W061v/CraXY3UcqCIRrIGBGR64rzofbvCWvrlkM0WCwU5VlPagDNvrZ7K9ntpBhonKn86hALEKoyScAe9dzqdhp/i7bf6ZdxW96RiWCU4yf8ai07QLHw9KNQ128gZovmjt423Et2+tAGh4hmGieB7bTs4nmjCY/VqPAX/Irah/vP/wCg1yWsapN4k1lZJHSFSdkQdsLGvua7Xw02l6Tob2U+q2hklLFykgwMjHFAHmddh8NIXbWbiYD5Eh2k+5Ix/Ko5PC+jCXK+I7cR57gZ/nWumu6F4Y0trfSZPtdw3O4c7m9WPp7CgBLjWI7X4kYLARNGtu57AkZH6msj4i2Jt9cW5C4S4Qc/7Q4P9K5ieeS4uXuJWJldixb3rs7TVrHxRoy6Xq86295F/qZ24DH1/wAaAOIrvvh9EthpF/qtz8sfQE+i8n9azIvA8scu++1G0itFOTIsnJHtTPE/iC2kso9H0f5bGEYZ/wDnoR/SgBnha5e88VXNy/3pYpnP4iucjkaGdZUOGRtwPuDXX+DbOzsbk317qdogeEqsYkG4bvWue1jTlsJiY7y3uY3Y7TE+Tj3HagD1G6ltbnTbXW5Mf6NEZ0PuVxivIJJWmnaVzlnfcfqTXSf28P8AhBP7N3jz/O8vb32df/rVk6PpqahN+9vLe2jRhuMr4JHt60Adr8RP+RcsP+ui/wDoJrzmvTfFTaZrOjpbW+qWiyxMGTdIMHAxXm8sQhuWiZ0cI2CyHIP0NAEWe4PIrtPGGqTTeHNJtZsrNNGJZR9Bgfn1rfm0rwzPp1teyrAkMKhgyMBu9j61594g1RtX1aW6xtj+7Ev91R0oAzaKWkoAXBwTg49e1dDbobXwJdysMG7uURfcLya3/BU+kT+G5bG+a3Vt5MiysF3A9DWF4v1W0upINO0wKLG0BC7ejN60Ac9F/ro/94fzr0H4k/8AIH0//rr/AOy1yGhabFfXCyXF9bW0Ubjd5r4JHsK7jxY2ma5pkcFvqtoksT7k3ycHjGKAOT8CAnxVbf7rfyqT4gf8jRN/1zT+VXvCFja6Xqn2681WyARWVVWUEknijxra2mo3rajZalZuBEA8fmfMSPT1oA1vB1wureErjTHOZIlaPHsehrzmSNoZXjcYZCVI9CK0NA1ibRNSW6iG5SNsif3lrpNU0Sx8SynUdEvYEll5lglbac+vtQBxkML3E8cMYy8jBQPc13/ju6XT/D9npEZ+dwu4D+6o/wAaqabpen+FH/tDVruKa6QfubeI7iD6/Wn6JpU/ivUzruouv2YSYWEcn5ei/SgDK8T/AOhaLo+l9HSIzSD0LdP61zaffX6iu98ReHbS71aa5vdeggkc8RsB8oHQdaz4vC+jLIpfxHblQckADJ/WgDY+If8AyLlj/wBdF/8AQa84r1zxJpP/AAkGhpBYzRkowaNt2VOBjGRXlNxbta3klvNjdFIUfHTg4NAEOfQ9K7Txhqs03hvSLWYbZpoxLKvsBgfn1rfm0rwxPp1teyrAkMKhgyMBu9j61594g1Q6vq0t1jbH92Jf7qjpQBnUlFFABRRRQAUUUUAFS23/AB9Q/wC+v86iqW2/4+of99f50ARUUUUAFFFFABRRRQAUUUUAGKKKKACiiigAooooAK7PwVqK6VomsXjc+Xt2j1bnArjKuRXpj0qeyAP76VXJ7YAPH60AQ3dzNe3L3FzIZJZDlmNQ0UUAafh7VpdH1WK4Rj5ZO2VezLV/x4wbxPKynIaNCD68VztXNTvjqFxHKVIKwpGc9yoxmgCnS0lFABRRRQAUUUUAFFFFAC0lFFABRRRQAUUUUAFFFFABS0lFABRRRQAUUUUAFGKKKACiiigApaSigArt9L1htH+H7SRHE8s7xxn0Jxk/lXEVcmvfN0u1sgpAhd3J9S2P8KAKrs0jl3YszHJJOSabiiigDpfA2syafrEdszn7NcnYy9g3Y1k69/yHtQ/6+JP/AEI1ThkMM0cq9UYMPwNS6hcC81C4uQpUTSM+D2yc0AV6WkooAKKKKACiiigAooooAKltv+PqH/fX+dRVLbf8fUP++v8AOgCKiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqW1/wCPqH/fX+dRVLa/8fUP++v86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbX/j6h/wB9f51FUtr/AMfUP++v86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbX/j6h/31/nUVS2v/AB9Q/wC+v86AIqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACpbX/j6h/31/nUVS2v/H1D/vr/ADoAiooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKltf8Aj6h/31/nUVS2v/H1D/vr/OgCKiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqW1/4+of8AfX+dRVLa/wDH1D/vr/OgCKitb/hGNb/6Bs/5Uf8ACMa3/wBAyf8AKgDJorW/4RjW/wDoGT/lR/wjGt/9Ayf8qAMmitb/AIRjW/8AoGT/AJUf8Ixrf/QMn/KgDJorW/4RjW/+gZP+VH/CMa3/ANAyf8qAMmitb/hGNb/6Bk/5Uf8ACMa3/wBAyf8AKgDJorW/4RjW/wDoGT/lR/wjGt/9Ayf8qAMmitb/AIRjW/8AoGz/AJUf8Ixrf/QMn/KgDJorW/4RjW/+gZP+VH/CMa3/ANAyf8qAMmitb/hGNb/6Bk/5Uf8ACMa3/wBA2f8AKgDJorW/4RjW/wDoGT/lR/wjGt/9Ayf8qAMmitb/AIRjW/8AoGT/AJUf8Ixrf/QNn/KgDJorW/4RjW/+gbP+VH/CMa3/ANA2f8qAMmitb/hGNb/6Bs/5Uf8ACMa3/wBA2f8AKgDJorW/4RjW/wDoGz/lR/wjGt/9A2f8qAMmitb/AIRjW/8AoGz/AJUf8Ixrf/QNn/KgDJorW/4RjW/+gbP+VH/CMa3/ANA2f8qAMmitb/hGNb/6Bs/5Uf8ACMa3/wBA2f8AKgDJorW/4RjW/wDoGz/lR/wjGt/9A2f8qAMmitb/AIRjW/8AoGz/AJUf8Ixrf/QNn/KgDJorW/4RjW/+gbP+VH/CMa3/ANA2f8qAMmitb/hGNb/6Bs/5Uf8ACMa3/wBA2f8AKgDJorW/4RjW/wDoGz/lR/wjGt/9A2f8qAMmitb/AIRjW/8AoGz/AJUf8Ixrf/QNn/KgDJorW/4RjW/+gbP+VH/CMa3/ANA2f8qAMmitb/hGNb/6Bs/5Uf8ACMa3/wBA2f8AKgDJorW/4RjW/wDoGz/lR/wjGt/9A2f8qAMmitb/AIRjW/8AoGz/AJUf8Ixrf/QNn/KgDJorW/4RjW/+gbP+VH/CMa3/ANA2f8qAMmitb/hGNb/6Bs/5Uf8ACMa3/wBA2f8AKgDJorW/4RjW/wDoGz/lR/wjGt/9A2f8qAMmitb/AIRjW/8AoGz/AJUf8Ixrf/QNn/KgDJqW1/4+of8AfX+daP8AwjGt/wDQNn/KpLfwzrS3ETNps4AcEnHvQB//2Q== - """ diff --git a/spec/index.html b/spec/index.html deleted file mode 100644 index d43b322..0000000 --- a/spec/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - Ember Data - - - - - - - - - - - - - - -
-
- - diff --git a/spec/revs-adapter_spec.coffee b/spec/revs-adapter_spec.coffee deleted file mode 100644 index b5e4d5c..0000000 --- a/spec/revs-adapter_spec.coffee +++ /dev/null @@ -1,42 +0,0 @@ -Ember.ENV.TESTING = true -module 'EmberCouchDBKit.RevsAdapter', - setup: -> - unless window.testing - window.subject = new TestEnv() - window.testing = true - @subject = window.subject - @async = window.async - -test 'belongsTo relation', 1, -> - person = @subject.create.call @, 'user', name: 'name' - person.save().then @async (saved) => - saved.set 'name', 'updated' - saved.save().then @async (updated) => - stop() - history = updated.get 'history' - setTimeout => - history.reload() - setTimeout => - start() - equal history.get('user.id').split('/')[0], person.id, 'history belongsTo user' - , 1000 - , 1000 - -test 'hasMany relation', 1, -> - person = @subject.create.call @, 'user', name: 'john' - person.save().then @async (saved) => - saved.set 'name', 'updatedJohn' - saved.save().then @async (updated) => - stop() - history = updated.get 'history' - setTimeout => - history.reload() - setTimeout => - history.get('users') - setTimeout => - start() - console.log(length) - equal history.get('users.length'), 2, 'history hasMany users' - , 1000 - , 1000 - , 1000 diff --git a/src/attachment-adapter.coffee b/src/attachment-adapter.coffee deleted file mode 100644 index a258b85..0000000 --- a/src/attachment-adapter.coffee +++ /dev/null @@ -1,118 +0,0 @@ -### -@namespace EmberCouchDBKit -@class AttachmentSerializer -@extends DS.RESTSerializer -### -EmberCouchDBKit.AttachmentSerializer = DS.RESTSerializer.extend - - primaryKey: 'id' - - normalize: (type, hash) -> - self = this - rev = (hash._rev || hash.rev) - @store.find(hash.model_name, hash.doc_id).then (document) -> - unless document.get('_data.rev') == rev - if self.getIntRevision(document.get('_data.rev')) < self.getIntRevision(rev) - document.set('_data.rev', rev) - @_super(type, hash) - - getIntRevision: (revision) -> - parseInt(revision.split("-")[0]) - - normalizeId: (hash) -> - hash.id = (hash["_id"] || hash["id"]) - -### - An `AttachmentAdapter` is an object which manages document's attachements and used - as a main adapter for `Attachment` enabled models. - - Let's consider an usual use case: - ```coffee - App.Task = DS.Model.extend - title: DS.attr('string') - attachments: DS.hasMany('attachment', {async: true}) - - App.Attachment = DS.Model.extend - content_type: DS.attr('string') - length: DS.attr('number') - file_name: DS.attr('string') - db: DS.attr('string') - - task = @get('store').find('task', id) - attachments = task.get('attachments') - ``` - - For getting more details check `spec/coffeescripts/attachment-adapter_spec.coffee` file. - -@namespace EmberCouchDBKit -@class AttachmentAdapter -@extends DS.Adapter -### - -EmberCouchDBKit.AttachmentAdapter = DS.Adapter.extend - - find: (store, type, id) -> - return new Ember.RSVP.Promise((resolve, reject) -> - Ember.run null, resolve, {attachment: EmberCouchDBKit.sharedStore.get('attachment', id)} - ) - - findMany: (store, type, ids) -> - docs = ids.map (item) => - item = EmberCouchDBKit.sharedStore.get('attachment', item) - item.db = @get('db') - item - - return new Ember.RSVP.Promise((resolve, reject) -> - Ember.run(null, resolve, {attachments: docs}) - ) - - createRecord: (store, type, record) -> - url = "%@/%@?rev=%@".fmt(@buildURL(), record.get('id'), record.get('rev')) - adapter = this - - return new Ember.RSVP.Promise((resolve, reject) -> - data = {} - data.context = adapter - request = new XMLHttpRequest() - request.open('PUT', url, true) - request.setRequestHeader('Content-Type', record.get('content_type')) - adapter._updateUploadState(record, request) - - request.onreadystatechange = => - if request.readyState == 4 && (request.status == 201 || request.status == 200) - data = JSON.parse(request.response) - data.model_name = record.get('model_name') - data.doc_id = record.get('doc_id') - json = adapter.serialize(record, includeId: true) - delete data.id - Ember.run(null, resolve, {attachment: $.extend(json, data)}) - request.send(record.get('file')) - ) - - updateRecord: (store, type, record) -> - # just for stubbing purpose which should be defined by default - - deleteRecord: (store, type, record) -> - return new Ember.RSVP.Promise((resolve, reject) -> - Ember.run(null, resolve, {}) - ) - - _updateUploadState: (record, request) -> - view = record.get('view') - if view - view.startUpload() - request.onprogress = (oEvent) => - if oEvent.lengthComputable - percentComplete = (oEvent.loaded / oEvent.total) * 100 - view.updateUpload(percentComplete) - - buildURL: -> - host = Ember.get(this, "host") - namespace = Ember.get(this, "namespace") - url = [] - url.push host if host - url.push namespace if namespace - url.push @get('db') - url = url.join("/") - url = "/" + url unless host - url diff --git a/src/changes-feed.coffee b/src/changes-feed.coffee deleted file mode 100644 index 77adb10..0000000 --- a/src/changes-feed.coffee +++ /dev/null @@ -1,88 +0,0 @@ -### - - This module provides convinience for working with CouchDB's `/changes` feeds - - For instance: - - ```coffee - # Create feed with custom parameters - feed = EmberCouchDBKit.ChangesFeed.create({ db: 'docs', content: params }) - feed.longpoll(callback) - - # Start listening from the last sequence - self = @ - feed.fromTail((=> feed.longpoll(self.filter, self))) - - # Destroy feed listening - feed.stop().destroy() - ``` - -@namespace EmberCouchDBKit -@class ChangesFeed -@extends Ember.ObjectProxy -### -EmberCouchDBKit.ChangesFeed = Ember.ObjectProxy.extend - - content: {} # by default - - longpoll: -> - @feed = 'longpoll' - @_ajax.apply(@, arguments) - - normal: -> - @feed = 'normal' - @_ajax.apply(@, arguments) - - continuous: -> - @feed = 'continuous' - @_ajax.apply(@, arguments) - - - fromTail: (callback)-> - $.ajax({ - url: "%@%@/_changes?descending=true&limit=1".fmt(@_buildUrl(), @get('db')), - dataType: 'json', - success: (data) => - @set('since', data.last_seq) - callback.call(@) if callback - }) - - stop: -> - @set('stopTracking', true) - @ - - start: (callback) -> - @set('stopTracking', false) - @fromTail(callback) - - _ajax: (callback, self) -> - $.ajax({ - type: "GET", - url: @_makeRequestPath(), - dataType: 'json', - - success: (data) => - unless @get('stopTracking') - callback.call(self, data.results) if data?.results?.length && callback - @set('since', data.last_seq) - complete: => - unless @get('stopTracking') - setTimeout((=> @_ajax(callback, self)), 1000) - }) - - _buildUrl: -> - url = @get('host') || "/" - url +="/" unless url.substring(url.length-1) == "/" - url - - _makeRequestPath: -> - feed = @feed || 'longpool' - params = @_makeFeedParams() - - "%@%@/_changes?feed=%@%@".fmt(@_buildUrl(), @get('db'), feed, params) - - _makeFeedParams: -> - path = '' - ["include_docs", "limit", "descending", "heartbeat", "timeout", "filter", "filter_param", "style", "since"].forEach (param) => - path += "&%@=%@".fmt(param, @get(param)) if @get(param) - path diff --git a/src/document-adapter.coffee b/src/document-adapter.coffee deleted file mode 100644 index 7ae8ecd..0000000 --- a/src/document-adapter.coffee +++ /dev/null @@ -1,331 +0,0 @@ -### -@namespace EmberCouchDBKit -@class DocumentSerializer -@extends DS.RESTSerializer -### -EmberCouchDBKit.DocumentSerializer = DS.RESTSerializer.extend - - primaryKey: '_id' - - normalize: (type, hash, prop) -> - @normalizeId(hash) - @normalizeAttachments(hash["_attachments"], type.typeKey, hash) - @addHistoryId(hash) - @normalizeUsingDeclaredMapping(type, hash) - @normalizeAttributes(type, hash) - @normalizeRelationships(type, hash) - - return @normalizeHash[prop](hash) if @normalizeHash and @normalizeHash[prop] - return hash if !hash - - @applyTransforms(type, hash) - - hash - - extractSingle: (store, type, payload, id, requestType) -> - @_super(store, type, payload, id, requestType) - - serialize: (record, options) -> - @_super(record, options) - - addHistoryId: (hash) -> - hash.history = "%@/history".fmt(hash.id) - - normalizeAttachments: (attachments, type, hash) -> - _attachments = [] - for k, v of attachments - key = "#{hash._id}/#{k}" - attachment = - id: key - content_type: v.content_type - digest: v.digest - length: v.length - stub: v.stub - doc_id: hash._id - rev: hash.rev - file_name: k - model_name: type - revpos: v.revpos - db: v.db - - EmberCouchDBKit.sharedStore.add('attachment', key, attachment) - _attachments.push(key) - hash.attachments = _attachments - - normalizeId: (hash) -> - hash.id = (hash["_id"] || hash["id"]) - - normalizeRelationships: (type, hash) -> - payloadKey = undefined - key = undefined - if @keyForRelationship - type.eachRelationship ((key, relationship) -> - payloadKey = @keyForRelationship(key, relationship.kind) - return if key is payloadKey - hash[key] = hash[payloadKey] - delete hash[payloadKey] - ), this - - serializeBelongsTo: (record, json, relationship) -> - attribute = (relationship.options.attribute || "id") - key = relationship.key - belongsTo = record.belongsTo(key) - return if Ember.isNone(belongsTo) - json[key] = if (attribute == "id") then belongsTo.id else belongsTo.attr(attribute) - json[key + "_type"] = belongsTo.typeKey if relationship.options.polymorphic - - serializeHasMany: (record, json, relationship) -> - attribute = (relationship.options.attribute || "id") - key = relationship.key - relationshipType = record.type.determineRelationshipType(relationship) - switch relationshipType - when "manyToNone", "manyToMany", "manyToOne" - json[key] = record.hasMany(key).mapBy(attribute) -### - - A `DocumentAdapter` should be used as a main adapter for working with models as a CouchDB documents. - - Let's consider: - - ```coffee - EmberApp.DocumentAdapter = EmberCouchDBKit.DocumentAdapter.extend({db: db, host: host}) - EmberApp.DocumentSerializer = EmberCouchDBKit.DocumentSerializer.extend() - - EmberApp.Document = DS.Model.extend - title: DS.attr('title') - type: DS.attr('string', {defaultValue: 'document'}) - ``` - - The following available operations: - - ```coffee - # GET /my_couchdb/:id - @get('store').find('document', id) - - # POST /my_couchdb - @get('store').createRecord('document', {title: "title"}).save() - - # update PUT /my_couchdb/:id - @get('store').find('document', id).then((document) -> - document.set('title', title) - document.save() - ) - - # DELETE /my_couchdb/:id - @get('store').find('document', id).deleteRecord().save() - ``` - - For more advanced tips and tricks, you should check available specs - -@namespace EmberCouchDBKit -@class DocumentAdapter -@extends DS.Adapter -### -EmberCouchDBKit.DocumentAdapter = DS.Adapter.extend - defaultSerializer: '_default' - customTypeLookup: false - typeViewName: "all" - - buildURL: -> - host = Ember.get(this, "host") - namespace = Ember.get(this, "namespace") - url = [] - url.push host if host - url.push namespace if namespace - url.push @get('db') - url = url.join("/") - url = "/" + url unless host - url - - ajax: (url, type, normalizeResponce, hash) -> - @_ajax('%@/%@'.fmt(@buildURL(), url || ''), type, normalizeResponce, hash) - - _ajax: (url, type, normalizeResponce, hash={}) -> - adapter = this - return new Ember.RSVP.Promise((resolve, reject) -> - if url.split("/").pop() == "" then url = url.substr(0, url.length - 1) - hash.url = url - hash.type = type - hash.dataType = 'json' - hash.contentType = 'application/json; charset=utf-8' - - hash.context = adapter - - if hash.data && type != 'GET' - hash.data = JSON.stringify(hash.data) - - if adapter.headers - headers = adapter.headers - hash.beforeSend = (xhr) -> - Ember.keys(headers).forEach (key) -> - xhr.setRequestHeader key, headers[key] - - unless hash.success - hash.success = (json) -> - _modelJson = normalizeResponce.call(adapter, json) - Ember.run(null, resolve, _modelJson) - - hash.error = (jqXHR, textStatus, errorThrown) -> - if (jqXHR) - jqXHR.then = null - Ember.run(null, reject, jqXHR) - - Ember.$.ajax(hash) - ) - - _normalizeRevision: (json) -> - if json && json._rev - json.rev = json._rev - delete json._rev - json - - - shouldCommit: (record, relationships) -> - @_super.apply(arguments) - - find: (store, type, id) -> - if @_checkForRevision(id) - @findWithRev(store, type, id) - else - normalizeResponce = (data) -> - @_normalizeRevision(data) - _modelJson = {} - _modelJson[type.typeKey] = data - _modelJson - - @ajax(id, 'GET', normalizeResponce) - - findWithRev: (store, type, id, hash) -> - [_id, _rev] = id.split("/")[0..1] - url = "%@?rev=%@".fmt(_id, _rev) - normalizeResponce = (data) -> - @_normalizeRevision(data) - _modelJson = {} - data._id = id - _modelJson[type.typeKey] = data - _modelJson - @ajax(url, 'GET', normalizeResponce, hash) - - findManyWithRev: (store, type, ids) -> - key = Ember.String.pluralize(type.typeKey) - self = @ - docs = {} - docs[key] = [] - hash = {async: false} - ids.forEach (id) => - [_id, _rev] = id.split("/")[0..1] - url = "%@?rev=%@".fmt(_id, _rev) - url = '%@/%@'.fmt(@buildURL(), url) - hash.url = url - hash.type = 'GET' - hash.dataType = 'json' - hash.contentType = 'application/json; charset=utf-8' - hash.success = (json) -> - json._id = id - self._normalizeRevision(json) - docs[key].push(json) - Ember.$.ajax(hash) - docs - - findMany: (store, type, ids) -> - if @_checkForRevision(ids[0]) - @findManyWithRev(store, type, ids) - else - data = - include_docs: true - keys: ids - - normalizeResponce = (data) -> - json = {} - json[Ember.String.pluralize(type.typeKey)] = data.rows.getEach('doc').map((doc) => @_normalizeRevision(doc)) - json - - @ajax('_all_docs?include_docs=true', 'POST', normalizeResponce, { - data: data - }) - - findQuery: (store, type, query, modelArray) -> - designDoc = (query.designDoc || @get('designDoc')) - query.options = {} unless query.options - query.options.include_docs = true - - normalizeResponce = (data) -> - json = {} - json[designDoc] = data.rows.getEach('doc').map((doc) => @_normalizeRevision(doc)) - json - - @ajax('_design/%@/_view/%@'.fmt(designDoc, query.viewName), 'GET', normalizeResponce, { - context: this - data: query.options - }) - - findAll: (store, type) -> - - typeString = Ember.String.singularize(type.typeKey) - - designDoc = @get('designDoc') || typeString - - typeViewName = @get('typeViewName') - - normalizeResponce = (data) -> - json = {} - json[[Ember.String.pluralize(type.typeKey)]] = data.rows.getEach('doc').map((doc) => @_normalizeRevision(doc)) - json - - data = - include_docs: true - key: '"' + typeString + '"' - - @ajax('_design/%@/_view/%@'.fmt(designDoc, typeViewName), 'GET', normalizeResponce, { - data: data - }) - - createRecord: (store, type, record) -> - json = store.serializerFor(type.typeKey).serialize(record._createSnapshot()) - @_push(store, type, record, json) - - updateRecord: (store, type, record) -> - json = @serialize(record, {associations: true, includeId: true }) - @_updateAttachmnets(record, json) if record.get('attachments') - @_push(store, type, record, json) - - deleteRecord: (store, type, record) -> - @ajax("%@?rev=%@".fmt(record.get('id'), record.get('_data.rev')), 'DELETE', (->), { - }) - - _updateAttachmnets: (record, json) -> - _attachments = {} - - record.get('attachments').forEach (item) -> - attachment = EmberCouchDBKit.sharedStore.get('attachment', item.get('id')) - _attachments[item.get('file_name')] = - content_type: attachment.content_type - digest: attachment.digest - length: attachment.length - stub: attachment.stub - revpos: attachment.revpos - - json._attachments = _attachments - delete json.attachments - delete json.history - - _checkForRevision: (id) -> - id.split("/").length > 1 - - _push: (store, type, record, json) -> - id = record.get('id') || '' - method = if record.get('id') then 'PUT' else 'POST' - - if record.get('_data.rev') - json._rev = record.get('_data.rev') - - normalizeResponce = (data) -> - _data = json || {} - @_normalizeRevision(data) - _modelJson = {} - _modelJson[type.typeKey] = $.extend(_data, data) - _modelJson - - @ajax(id, method, normalizeResponce, { - data: json - }) diff --git a/src/ember-couchdb-kit.coffee b/src/ember-couchdb-kit.coffee deleted file mode 100644 index 244bd63..0000000 --- a/src/ember-couchdb-kit.coffee +++ /dev/null @@ -1,25 +0,0 @@ -#= require_self -# -#= require registry -#= require document-adapter -#= require attachment-adapter -#= require revs-adapter -#= require changes-feed - -window.EmberCouchDBKit = Ember.Namespace.create - VERSION: '1.0.x' - -EmberCouchDBKit.sharedStore = do -> - _data = {} - - add: (type, key, value) -> - _data[type + ':' + key] = value - get: (type, key) -> - _data[type + ':' + key] - remove: (type, key) -> - delete _data[type + ':' + key] - mapRevIds: (type, key)-> - @get(type, key)._revs_info.map (_rev) => "%@/%@".fmt(@get(type, key)._id, _rev.rev) - stopAll: -> - for k,v of _data - v.stop() if k.indexOf('changes_worker') is 0 diff --git a/src/revs-adapter.coffee b/src/revs-adapter.coffee deleted file mode 100644 index 43b04f4..0000000 --- a/src/revs-adapter.coffee +++ /dev/null @@ -1,94 +0,0 @@ -### -@namespace EmberCouchDBKit -@class RevSerializer -@extends DS.RESTSerializer -### -EmberCouchDBKit.RevSerializer = DS.RESTSerializer.extend - - primaryKey: 'id' - - normalize: (type, hash, prop) -> - @normalizeRelationships(type, hash) - @_super(type, hash, prop) - - extractId: (type, hash) -> - hash._id || hash.id - - normalizeRelationships: (type, hash) -> - type.eachRelationship ((key, relationship) -> - - if relationship.kind == "belongsTo" - hash[key] = EmberCouchDBKit.sharedStore.mapRevIds('revs', @extractId(type, hash))[1] - - if relationship.kind == "hasMany" - hash[key] = EmberCouchDBKit.sharedStore.mapRevIds('revs', @extractId(type, hash)) - - ), this - -### - `RevAdapter` is an adapter which gets revisions info by distinct document and used - as a main adapter for history enabled models. - - Let's consider `belongsTo` relation which returns previous version of document: - ```coffee - App.Task = DS.Model.extend - title: DS.attr('string') - history: DS.belongsTo('history') - - - App.History = DS.Model.extend - # previous version of task entry - task: DS.belongsTo('task', {inverse: null}) - # list of all available versions of task entry - tasks: DS.hasMany('task', {inverse: null, async: true}) - ``` - - For getting more details check `spec/coffeescripts/revs-adapter_spec.coffee` file. - -@namespace EmberCouchDBKit -@class RevAdapter -@extends DS.Adapter -### -EmberCouchDBKit.RevAdapter = DS.Adapter.extend - - find: (store, type, id) -> - @ajax("%@?revs_info=true".fmt(id.split("/")[0]), 'GET', {context: this}, id) - - updateRecord: (store, type, record) -> - # just for stubbing purpose which should be defined by default - - deleteRecord: (store, type, record) -> - # just for stubbing purpose which should be defined by default - - ajax: (url, type, hash, id) -> - @_ajax('%@/%@'.fmt(@buildURL(), url || ''), type, hash, id) - - _ajax: (url, type, hash, id) -> - - hash.url = url - hash.type = type - hash.dataType = 'json' - hash.contentType = 'application/json; charset=utf-8' - hash.context = this - - hash.data = JSON.stringify(hash.data) if (hash.data && type != 'GET') - - return new Ember.RSVP.Promise((resolve, reject) -> - hash.success = (data) -> - EmberCouchDBKit.sharedStore.add('revs', id, data) - Ember.run(null, resolve, {history: {id: id} }) - - Ember.$.ajax(hash) - ) - - - buildURL: -> - host = Ember.get(this, "host") - namespace = Ember.get(this, "namespace") - url = [] - url.push host if host - url.push namespace if namespace - url.push @get('db') - url = url.join("/") - url = "/" + url unless host - url From 64fa5e7a7f02b85fa74c8353a8816652af58106d Mon Sep 17 00:00:00 2001 From: Zatvor Date: Wed, 17 Jun 2015 15:10:57 +0300 Subject: [PATCH 02/48] Initial Ember CLI addon structure --- .bowerrc | 4 ++ .ember-cli | 9 ++++ .gitignore | 20 ++++++-- .jshintrc | 32 ++++++++++++ .npmignore | 14 +++++ .watchmanconfig | 3 ++ Brocfile.js | 16 ++++++ addon/.gitkeep | 0 app/.gitkeep | 0 bower.json | 20 ++++++++ config/ember-try.js | 35 +++++++++++++ config/environment.js | 5 ++ index.js | 6 +++ package.json | 49 ++++++++++++++++++ testem.json | 12 +++++ tests/.jshintrc | 51 +++++++++++++++++++ tests/dummy/app/app.js | 18 +++++++ tests/dummy/app/components/.gitkeep | 0 tests/dummy/app/controllers/.gitkeep | 0 tests/dummy/app/helpers/.gitkeep | 0 tests/dummy/app/index.html | 25 +++++++++ tests/dummy/app/models/.gitkeep | 0 tests/dummy/app/router.js | 11 ++++ tests/dummy/app/routes/.gitkeep | 0 tests/dummy/app/styles/app.css | 0 tests/dummy/app/templates/application.hbs | 3 ++ tests/dummy/app/templates/components/.gitkeep | 0 tests/dummy/config/environment.js | 47 +++++++++++++++++ tests/dummy/public/crossdomain.xml | 15 ++++++ tests/dummy/public/robots.txt | 3 ++ tests/helpers/resolver.js | 11 ++++ tests/helpers/start-app.js | 19 +++++++ tests/index.html | 33 ++++++++++++ tests/test-helper.js | 6 +++ tests/unit/.gitkeep | 0 35 files changed, 464 insertions(+), 3 deletions(-) create mode 100644 .bowerrc create mode 100644 .ember-cli create mode 100644 .jshintrc create mode 100644 .npmignore create mode 100644 .watchmanconfig create mode 100644 Brocfile.js create mode 100644 addon/.gitkeep create mode 100644 app/.gitkeep create mode 100644 bower.json create mode 100644 config/ember-try.js create mode 100644 config/environment.js create mode 100644 index.js create mode 100644 package.json create mode 100644 testem.json create mode 100644 tests/.jshintrc create mode 100644 tests/dummy/app/app.js create mode 100644 tests/dummy/app/components/.gitkeep create mode 100644 tests/dummy/app/controllers/.gitkeep create mode 100644 tests/dummy/app/helpers/.gitkeep create mode 100644 tests/dummy/app/index.html create mode 100644 tests/dummy/app/models/.gitkeep create mode 100644 tests/dummy/app/router.js create mode 100644 tests/dummy/app/routes/.gitkeep create mode 100644 tests/dummy/app/styles/app.css create mode 100644 tests/dummy/app/templates/application.hbs create mode 100644 tests/dummy/app/templates/components/.gitkeep create mode 100644 tests/dummy/config/environment.js create mode 100644 tests/dummy/public/crossdomain.xml create mode 100644 tests/dummy/public/robots.txt create mode 100644 tests/helpers/resolver.js create mode 100644 tests/helpers/start-app.js create mode 100644 tests/index.html create mode 100644 tests/test-helper.js create mode 100644 tests/unit/.gitkeep diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..959e169 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,4 @@ +{ + "directory": "bower_components", + "analytics": false +} diff --git a/.ember-cli b/.ember-cli new file mode 100644 index 0000000..ee64cfe --- /dev/null +++ b/.ember-cli @@ -0,0 +1,9 @@ +{ + /** + Ember CLI sends analytics information by default. The data is completely + anonymous, but there are times when you might want to disable this behavior. + + Setting `disableAnalytics` to true will prevent any data from being sent. + */ + "disableAnalytics": false +} diff --git a/.gitignore b/.gitignore index 62c15b5..86fceae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,17 @@ -node_modules/ -bower_components/ -tmp/ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp + +# dependencies +/node_modules +/bower_components + +# misc +/.sass-cache +/connect.lock +/coverage/* +/libpeerconnection.log +npm-debug.log +testem.log diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..08096ef --- /dev/null +++ b/.jshintrc @@ -0,0 +1,32 @@ +{ + "predef": [ + "document", + "window", + "-Promise" + ], + "browser": true, + "boss": true, + "curly": true, + "debug": false, + "devel": true, + "eqeqeq": true, + "evil": true, + "forin": false, + "immed": false, + "laxbreak": false, + "newcap": true, + "noarg": true, + "noempty": false, + "nonew": false, + "nomen": false, + "onevar": false, + "plusplus": false, + "regexp": false, + "undef": true, + "sub": true, + "strict": false, + "white": false, + "eqnull": true, + "esnext": true, + "unused": true +} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..49996f5 --- /dev/null +++ b/.npmignore @@ -0,0 +1,14 @@ +bower_components/ +tests/ +tmp/ +dist/ + +.bowerrc +.editorconfig +.ember-cli +.travis.yml +.npmignore +**/.gitkeep +bower.json +Brocfile.js +testem.json diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 0000000..5e9462c --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1,3 @@ +{ + "ignore_dirs": ["tmp"] +} diff --git a/Brocfile.js b/Brocfile.js new file mode 100644 index 0000000..2a682c0 --- /dev/null +++ b/Brocfile.js @@ -0,0 +1,16 @@ +/* jshint node: true */ +/* global require, module */ + +var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); + +/* + This Brocfile specifes the options for the dummy test app of this + addon, located in `/tests/dummy` + + This Brocfile does *not* influence how the addon or the app using it + behave. You most likely want to be modifying `./index.js` or app's Brocfile +*/ + +var app = new EmberAddon(); + +module.exports = app.toTree(); diff --git a/addon/.gitkeep b/addon/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/.gitkeep b/app/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..f0fa840 --- /dev/null +++ b/bower.json @@ -0,0 +1,20 @@ +{ + "name": "ember-couchdb-kit", + "private": true, + "dependencies": { + "ember": "components/ember#canary", + "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", + "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", + "ember-data": "1.0.0-beta.18", + "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.4", + "ember-qunit": "0.3.3", + "ember-qunit-notifications": "0.0.7", + "ember-resolver": "~0.1.15", + "jquery": "^1.11.1", + "loader.js": "ember-cli/loader.js#3.2.0", + "qunit": "~1.17.1" + }, + "resolutions": { + "ember": "canary" + } +} diff --git a/config/ember-try.js b/config/ember-try.js new file mode 100644 index 0000000..83dab0f --- /dev/null +++ b/config/ember-try.js @@ -0,0 +1,35 @@ +module.exports = { + scenarios: [ + { + name: 'default', + dependencies: { } + }, + { + name: 'ember-release', + dependencies: { + 'ember': 'components/ember#release' + }, + resolutions: { + 'ember': 'release' + } + }, + { + name: 'ember-beta', + dependencies: { + 'ember': 'components/ember#beta' + }, + resolutions: { + 'ember': 'beta' + } + }, + { + name: 'ember-canary', + dependencies: { + 'ember': 'components/ember#canary' + }, + resolutions: { + 'ember': 'canary' + } + } + ] +}; diff --git a/config/environment.js b/config/environment.js new file mode 100644 index 0000000..0dfaed4 --- /dev/null +++ b/config/environment.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function(/* environment, appConfig */) { + return { }; +}; diff --git a/index.js b/index.js new file mode 100644 index 0000000..a8e5fa9 --- /dev/null +++ b/index.js @@ -0,0 +1,6 @@ +/* jshint node: true */ +'use strict'; + +module.exports = { + name: 'ember-couchdb-kit' +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..a9dd392 --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "ember-couchdb-kit", + "version": "2.0.0-alpha", + "description": "An Ember.js adapter for Apache CouchDB", + "directories": { + "doc": "doc", + "test": "tests" + }, + "scripts": { + "start": "ember server", + "build": "ember build", + "test": "ember try:testall" + }, + "repository": { + "type": "git", + "url": "https://github.com/Zatvobor/ember-couchdb-kit.git" + }, + "engines": { + "node": ">= 0.10.0" + }, + "author": "Aleksey Zatvobor", + "license": "MIT", + "devDependencies": { + "broccoli-asset-rev": "^2.0.2", + "ember-cli": "0.2.7", + "ember-cli-app-version": "0.3.3", + "ember-cli-content-security-policy": "0.4.0", + "ember-cli-dependency-checker": "^1.0.0", + "ember-cli-htmlbars": "0.7.6", + "ember-cli-ic-ajax": "0.1.1", + "ember-cli-inject-live-reload": "^1.3.0", + "ember-cli-qunit": "0.3.13", + "ember-cli-uglify": "^1.0.1", + "ember-data": "1.0.0-beta.18", + "ember-disable-proxy-controllers": "^1.0.0", + "ember-export-application-global": "^1.0.2", + "ember-disable-prototype-extensions": "^1.0.0", + "ember-try": "0.0.6" + }, + "keywords": [ + "ember-addon", "couch db" + ], + "dependencies": { + "ember-cli-babel": "^5.0.0" + }, + "ember-addon": { + "configPath": "tests/dummy/config" + } +} diff --git a/testem.json b/testem.json new file mode 100644 index 0000000..0f35392 --- /dev/null +++ b/testem.json @@ -0,0 +1,12 @@ +{ + "framework": "qunit", + "test_page": "tests/index.html?hidepassed", + "disable_watching": true, + "launch_in_ci": [ + "PhantomJS" + ], + "launch_in_dev": [ + "PhantomJS", + "Chrome" + ] +} diff --git a/tests/.jshintrc b/tests/.jshintrc new file mode 100644 index 0000000..ea8b88f --- /dev/null +++ b/tests/.jshintrc @@ -0,0 +1,51 @@ +{ + "predef": [ + "document", + "window", + "location", + "setTimeout", + "$", + "-Promise", + "define", + "console", + "visit", + "exists", + "fillIn", + "click", + "keyEvent", + "triggerEvent", + "find", + "findWithAssert", + "wait", + "DS", + "andThen", + "currentURL", + "currentPath", + "currentRouteName" + ], + "node": false, + "browser": false, + "boss": true, + "curly": false, + "debug": false, + "devel": false, + "eqeqeq": true, + "evil": true, + "forin": false, + "immed": false, + "laxbreak": false, + "newcap": true, + "noarg": true, + "noempty": false, + "nonew": false, + "nomen": false, + "onevar": false, + "plusplus": false, + "regexp": false, + "undef": true, + "sub": true, + "strict": false, + "white": false, + "eqnull": true, + "esnext": true +} diff --git a/tests/dummy/app/app.js b/tests/dummy/app/app.js new file mode 100644 index 0000000..8d66b95 --- /dev/null +++ b/tests/dummy/app/app.js @@ -0,0 +1,18 @@ +import Ember from 'ember'; +import Resolver from 'ember/resolver'; +import loadInitializers from 'ember/load-initializers'; +import config from './config/environment'; + +var App; + +Ember.MODEL_FACTORY_INJECTIONS = true; + +App = Ember.Application.extend({ + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix, + Resolver: Resolver +}); + +loadInitializers(App, config.modulePrefix); + +export default App; diff --git a/tests/dummy/app/components/.gitkeep b/tests/dummy/app/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/controllers/.gitkeep b/tests/dummy/app/controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/helpers/.gitkeep b/tests/dummy/app/helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/index.html b/tests/dummy/app/index.html new file mode 100644 index 0000000..1c49d36 --- /dev/null +++ b/tests/dummy/app/index.html @@ -0,0 +1,25 @@ + + + + + + Dummy + + + + {{content-for 'head'}} + + + + + {{content-for 'head-footer'}} + + + {{content-for 'body'}} + + + + + {{content-for 'body-footer'}} + + diff --git a/tests/dummy/app/models/.gitkeep b/tests/dummy/app/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js new file mode 100644 index 0000000..cef554b --- /dev/null +++ b/tests/dummy/app/router.js @@ -0,0 +1,11 @@ +import Ember from 'ember'; +import config from './config/environment'; + +var Router = Ember.Router.extend({ + location: config.locationType +}); + +Router.map(function() { +}); + +export default Router; diff --git a/tests/dummy/app/routes/.gitkeep b/tests/dummy/app/routes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/styles/app.css b/tests/dummy/app/styles/app.css new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs new file mode 100644 index 0000000..05eb936 --- /dev/null +++ b/tests/dummy/app/templates/application.hbs @@ -0,0 +1,3 @@ +

Welcome to Ember.js

+ +{{outlet}} diff --git a/tests/dummy/app/templates/components/.gitkeep b/tests/dummy/app/templates/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js new file mode 100644 index 0000000..c59bcd5 --- /dev/null +++ b/tests/dummy/config/environment.js @@ -0,0 +1,47 @@ +/* jshint node: true */ + +module.exports = function(environment) { + var ENV = { + modulePrefix: 'dummy', + environment: environment, + baseURL: '/', + locationType: 'auto', + EmberENV: { + FEATURES: { + // Here you can enable experimental features on an ember canary build + // e.g. 'with-controller': true + } + }, + + APP: { + // Here you can pass flags/options to your application instance + // when it is created + } + }; + + if (environment === 'development') { + // ENV.APP.LOG_RESOLVER = true; + // ENV.APP.LOG_ACTIVE_GENERATION = true; + // ENV.APP.LOG_TRANSITIONS = true; + // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; + // ENV.APP.LOG_VIEW_LOOKUPS = true; + } + + if (environment === 'test') { + // Testem prefers this... + ENV.baseURL = '/'; + ENV.locationType = 'none'; + + // keep test console output quieter + ENV.APP.LOG_ACTIVE_GENERATION = false; + ENV.APP.LOG_VIEW_LOOKUPS = false; + + ENV.APP.rootElement = '#ember-testing'; + } + + if (environment === 'production') { + + } + + return ENV; +}; diff --git a/tests/dummy/public/crossdomain.xml b/tests/dummy/public/crossdomain.xml new file mode 100644 index 0000000..0c16a7a --- /dev/null +++ b/tests/dummy/public/crossdomain.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/tests/dummy/public/robots.txt b/tests/dummy/public/robots.txt new file mode 100644 index 0000000..f591645 --- /dev/null +++ b/tests/dummy/public/robots.txt @@ -0,0 +1,3 @@ +# http://www.robotstxt.org +User-agent: * +Disallow: diff --git a/tests/helpers/resolver.js b/tests/helpers/resolver.js new file mode 100644 index 0000000..28f4ece --- /dev/null +++ b/tests/helpers/resolver.js @@ -0,0 +1,11 @@ +import Resolver from 'ember/resolver'; +import config from '../../config/environment'; + +var resolver = Resolver.create(); + +resolver.namespace = { + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix +}; + +export default resolver; diff --git a/tests/helpers/start-app.js b/tests/helpers/start-app.js new file mode 100644 index 0000000..16cc7c3 --- /dev/null +++ b/tests/helpers/start-app.js @@ -0,0 +1,19 @@ +import Ember from 'ember'; +import Application from '../../app'; +import Router from '../../router'; +import config from '../../config/environment'; + +export default function startApp(attrs) { + var application; + + var attributes = Ember.merge({}, config.APP); + attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; + + Ember.run(function() { + application = Application.create(attributes); + application.setupForTesting(); + application.injectTestHelpers(); + }); + + return application; +} diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 0000000..8fea6fe --- /dev/null +++ b/tests/index.html @@ -0,0 +1,33 @@ + + + + + + Dummy Tests + + + + {{content-for 'head'}} + {{content-for 'test-head'}} + + + + + + {{content-for 'head-footer'}} + {{content-for 'test-head-footer'}} + + + + {{content-for 'body'}} + {{content-for 'test-body'}} + + + + + + + {{content-for 'body-footer'}} + {{content-for 'test-body-footer'}} + + diff --git a/tests/test-helper.js b/tests/test-helper.js new file mode 100644 index 0000000..e6cfb70 --- /dev/null +++ b/tests/test-helper.js @@ -0,0 +1,6 @@ +import resolver from './helpers/resolver'; +import { + setResolver +} from 'ember-qunit'; + +setResolver(resolver); diff --git a/tests/unit/.gitkeep b/tests/unit/.gitkeep new file mode 100644 index 0000000..e69de29 From a4a61fd7f192535ca94e5a90afebc6a277384968 Mon Sep 17 00:00:00 2001 From: Zatvor Date: Wed, 17 Jun 2015 17:22:56 +0300 Subject: [PATCH 03/48] Main JS --- addon/index.js | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 addon/index.js diff --git a/addon/index.js b/addon/index.js new file mode 100644 index 0000000..f55ea60 --- /dev/null +++ b/addon/index.js @@ -0,0 +1,4 @@ +/* global Ember*/ + +let version = '2.0.0-alpha'; +Ember.libraries.register('Ember CouchDB Kit', version); From d52340164eb6ae0c9e66107938a78f867ee4065e Mon Sep 17 00:00:00 2001 From: Zatvor Date: Wed, 17 Jun 2015 17:29:46 +0300 Subject: [PATCH 04/48] Turn ON Travis --- .travis.yml | 26 ++++++++++++++++++++++++++ README.md | 4 ++-- 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ae07ee3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +--- +language: node_js +node_js: + - "0.12" + +sudo: false + +cache: + directories: + - node_modules + +before_install: + - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH + - "npm config set spin false" + - "npm install -g npm@^2" + +install: + - npm install -g bower + - npm install + - bower install + +script: + - ember test + +notifications: + email: false diff --git a/README.md b/README.md index e02b643..0af0836 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![License](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE) +[![Build Status](https://travis-ci.org/zatvobor/ember-coucdb-kit.svg?branch=2.0.0-alpha)](https://travis-ci.org/zatvobor/ember-coucdb-kit)[![License](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE) #### ember-couchdb-kit @@ -15,7 +15,7 @@ Versions: #### Contribution - + See [CONTRIBUTING.md](CONTRIBUTING.md) From ae0d775234af9e8dc83dcc44f3b194ff0e3eda28 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Mon, 27 Jul 2015 14:40:00 -0400 Subject: [PATCH 05/48] Upgrading to Ember CLI 1.13.1 --- .editorconfig | 34 +++++++++++++++++++++++ .travis.yml | 16 ++++++++--- Brocfile.js | 16 ----------- LICENSE.md | 9 ++++++ bower.json | 10 +++---- ember-cli-build.js | 17 ++++++++++++ package.json | 16 ++++++----- tests/.jshintrc | 5 ++-- tests/dummy/app/templates/application.hbs | 2 +- tests/helpers/start-app.js | 1 - 10 files changed, 90 insertions(+), 36 deletions(-) create mode 100644 .editorconfig delete mode 100644 Brocfile.js create mode 100644 LICENSE.md create mode 100644 ember-cli-build.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..47c5438 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,34 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.js] +indent_style = space +indent_size = 2 + +[*.hbs] +insert_final_newline = false +indent_style = space +indent_size = 2 + +[*.css] +indent_style = space +indent_size = 2 + +[*.html] +indent_style = space +indent_size = 2 + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/.travis.yml b/.travis.yml index ae07ee3..8197d31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,17 @@ cache: directories: - node_modules +env: + - EMBER_TRY_SCENARIO=default + - EMBER_TRY_SCENARIO=ember-release + - EMBER_TRY_SCENARIO=ember-beta + - EMBER_TRY_SCENARIO=ember-canary + +matrix: + fast_finish: true + allow_failures: + - env: EMBER_TRY_SCENARIO=ember-canary + before_install: - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH - "npm config set spin false" @@ -20,7 +31,4 @@ install: - bower install script: - - ember test - -notifications: - email: false + - ember try $EMBER_TRY_SCENARIO test diff --git a/Brocfile.js b/Brocfile.js deleted file mode 100644 index 2a682c0..0000000 --- a/Brocfile.js +++ /dev/null @@ -1,16 +0,0 @@ -/* jshint node: true */ -/* global require, module */ - -var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); - -/* - This Brocfile specifes the options for the dummy test app of this - addon, located in `/tests/dummy` - - This Brocfile does *not* influence how the addon or the app using it - behave. You most likely want to be modifying `./index.js` or app's Brocfile -*/ - -var app = new EmberAddon(); - -module.exports = app.toTree(); diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..00e9fbb --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2015 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/bower.json b/bower.json index f0fa840..57079dc 100644 --- a/bower.json +++ b/bower.json @@ -5,11 +5,11 @@ "ember": "components/ember#canary", "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", - "ember-data": "1.0.0-beta.18", - "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.4", - "ember-qunit": "0.3.3", + "ember-data": "1.13.6", + "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", + "ember-qunit": "0.4.1", "ember-qunit-notifications": "0.0.7", - "ember-resolver": "~0.1.15", + "ember-resolver": "~0.1.18", "jquery": "^1.11.1", "loader.js": "ember-cli/loader.js#3.2.0", "qunit": "~1.17.1" @@ -17,4 +17,4 @@ "resolutions": { "ember": "canary" } -} +} \ No newline at end of file diff --git a/ember-cli-build.js b/ember-cli-build.js new file mode 100644 index 0000000..d37d64c --- /dev/null +++ b/ember-cli-build.js @@ -0,0 +1,17 @@ +/* global require, module */ +var EmberApp = require('ember-cli/lib/broccoli/ember-addon'); + +module.exports = function(defaults) { + var app = new EmberApp(defaults, { + // Add options here + }); + + /* + This build file specifes the options for the dummy test app of this + addon, located in `/tests/dummy` + This build file does *not* influence how the addon or the app using it + behave. You most likely want to be modifying `./index.js` or app's build file + */ + + return app.toTree(); +}; diff --git a/package.json b/package.json index a9dd392..eb25316 100644 --- a/package.json +++ b/package.json @@ -22,16 +22,18 @@ "license": "MIT", "devDependencies": { "broccoli-asset-rev": "^2.0.2", - "ember-cli": "0.2.7", - "ember-cli-app-version": "0.3.3", + "ember-cli": "1.13.1", + "ember-cli-app-version": "0.4.0", "ember-cli-content-security-policy": "0.4.0", "ember-cli-dependency-checker": "^1.0.0", - "ember-cli-htmlbars": "0.7.6", - "ember-cli-ic-ajax": "0.1.1", + "ember-cli-htmlbars": "0.7.9", + "ember-cli-htmlbars-inline-precompile": "^0.1.1", + "ember-cli-ic-ajax": "0.2.1", "ember-cli-inject-live-reload": "^1.3.0", - "ember-cli-qunit": "0.3.13", + "ember-cli-qunit": "0.3.15", + "ember-cli-release": "0.2.3", "ember-cli-uglify": "^1.0.1", - "ember-data": "1.0.0-beta.18", + "ember-data": "1.13.6", "ember-disable-proxy-controllers": "^1.0.0", "ember-export-application-global": "^1.0.2", "ember-disable-prototype-extensions": "^1.0.0", @@ -46,4 +48,4 @@ "ember-addon": { "configPath": "tests/dummy/config" } -} +} \ No newline at end of file diff --git a/tests/.jshintrc b/tests/.jshintrc index ea8b88f..6ec0b7c 100644 --- a/tests/.jshintrc +++ b/tests/.jshintrc @@ -26,7 +26,7 @@ "node": false, "browser": false, "boss": true, - "curly": false, + "curly": true, "debug": false, "devel": false, "eqeqeq": true, @@ -47,5 +47,6 @@ "strict": false, "white": false, "eqnull": true, - "esnext": true + "esnext": true, + "unused": true } diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index 05eb936..94df915 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -1,3 +1,3 @@

Welcome to Ember.js

-{{outlet}} +{{outlet}} \ No newline at end of file diff --git a/tests/helpers/start-app.js b/tests/helpers/start-app.js index 16cc7c3..0f7aab1 100644 --- a/tests/helpers/start-app.js +++ b/tests/helpers/start-app.js @@ -1,6 +1,5 @@ import Ember from 'ember'; import Application from '../../app'; -import Router from '../../router'; import config from '../../config/environment'; export default function startApp(attrs) { From 86bca07f5517233fc64b9423df507f1646de8d88 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Tue, 28 Jul 2015 10:17:30 -0400 Subject: [PATCH 06/48] Changed README to reflect that we've forked. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0af0836..df27d4d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![Build Status](https://travis-ci.org/zatvobor/ember-coucdb-kit.svg?branch=2.0.0-alpha)](https://travis-ci.org/zatvobor/ember-coucdb-kit)[![License](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE) +[![Build Status](https://travis-ci.org/validusa/ember-couch.svg?branch=2.0.0-alpha)](https://travis-ci.org/zatvobor/ember-coucdb-kit)[![License](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE) -#### ember-couchdb-kit +#### ember-couch An `ember-data` kit for Apache CouchDB. A collection of adapters to work with CouchDB documents, attachments, revisions, changes feed. @@ -21,4 +21,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) #### License -`ember-couchdb-kit` source code is released under MIT-License. Check [MIT-LICENSE](MIT-LICENSE) for more details. +`ember-couch` source code is released under MIT-License. Check [MIT-LICENSE](MIT-LICENSE) for more details. From 21c94def0e66bfddcd2fdb153eafe6010a4ca7ca Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Tue, 28 Jul 2015 10:21:57 -0400 Subject: [PATCH 07/48] Left out a spot that needed changed in the README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df27d4d..14c1558 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/validusa/ember-couch.svg?branch=2.0.0-alpha)](https://travis-ci.org/zatvobor/ember-coucdb-kit)[![License](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE) +[![Build Status](https://travis-ci.org/validusa/ember-couch.svg?branch=2.0.0-alpha)](https://travis-ci.org/validusa/ember-couch)[![License](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE) #### ember-couch From 4b9d3caaff3bc8de4251a7c4a23a5af7def8e377 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Tue, 28 Jul 2015 14:52:08 -0400 Subject: [PATCH 08/48] First commit as Ember Couch --- .editorconfig | 10 +- .jscsrc | 51 ++++ .jshintrc | 89 ++++-- addon/adapters/attachment.js | 99 +++++++ addon/adapters/document.js | 275 ++++++++++++++++++ addon/adapters/rev.js | 56 ++++ addon/changes-feed.js | 87 ++++++ addon/index.js | 21 +- addon/serializers/attachment.js | 25 ++ addon/serializers/document.js | 114 ++++++++ addon/serializers/rev.js | 26 ++ addon/services/shared-store.js | 39 +++ bower.json | 9 +- config/ember-try.js | 116 +++++--- config/environment.js | 4 +- ember-cli-build.js | 23 +- index.js | 2 +- package.json | 16 +- tests/.jshintrc | 109 +++---- tests/dummy/app/adapters/application.js | 7 + tests/dummy/app/adapters/attachment.js | 7 + tests/dummy/app/app.js | 16 +- tests/dummy/app/components/eck-attachment.js | 19 ++ tests/dummy/app/components/eck-cancel.js | 10 + .../app/components/eck-delete-attachment.js | 11 + .../dummy/app/components/eck-delete-issue.js | 11 + .../app/components/eck-focused-text-area.js | 7 + tests/dummy/app/components/eck-issue.js | 43 +++ tests/dummy/app/components/eck-new-issue.js | 30 ++ tests/dummy/app/controllers/advanced.js | 5 + tests/dummy/app/controllers/common.js | 5 + tests/dummy/app/controllers/index.js | 92 ++++++ tests/dummy/app/controllers/intermediate.js | 5 + tests/dummy/app/helpers/link-to-attachment.js | 10 + tests/dummy/app/index.html | 34 +-- tests/dummy/app/models/attachment.js | 10 + tests/dummy/app/models/issue.js | 9 + tests/dummy/app/models/position.js | 8 + tests/dummy/app/router.js | 8 +- tests/dummy/app/routes/index.js | 109 +++++++ tests/dummy/app/serializers/application.js | 3 + tests/dummy/app/serializers/attachment.js | 3 + tests/dummy/app/templates/_create-issue.hbs | 14 + tests/dummy/app/templates/_issue-list.hbs | 45 +++ tests/dummy/app/templates/board.hbs | 9 + tests/dummy/app/templates/index.hbs | 25 ++ tests/dummy/config/environment.js | 74 ++--- tests/dummy/public/crossdomain.xml | 18 +- tests/helpers/resolver.js | 8 +- tests/helpers/start-app.js | 29 +- tests/index.html | 50 ++-- tests/test-helper.js | 6 +- 52 files changed, 1645 insertions(+), 266 deletions(-) create mode 100644 .jscsrc create mode 100644 addon/adapters/attachment.js create mode 100644 addon/adapters/document.js create mode 100644 addon/adapters/rev.js create mode 100644 addon/changes-feed.js create mode 100644 addon/serializers/attachment.js create mode 100644 addon/serializers/document.js create mode 100644 addon/serializers/rev.js create mode 100644 addon/services/shared-store.js create mode 100644 tests/dummy/app/adapters/application.js create mode 100644 tests/dummy/app/adapters/attachment.js create mode 100644 tests/dummy/app/components/eck-attachment.js create mode 100644 tests/dummy/app/components/eck-cancel.js create mode 100644 tests/dummy/app/components/eck-delete-attachment.js create mode 100644 tests/dummy/app/components/eck-delete-issue.js create mode 100644 tests/dummy/app/components/eck-focused-text-area.js create mode 100644 tests/dummy/app/components/eck-issue.js create mode 100644 tests/dummy/app/components/eck-new-issue.js create mode 100644 tests/dummy/app/controllers/advanced.js create mode 100644 tests/dummy/app/controllers/common.js create mode 100644 tests/dummy/app/controllers/index.js create mode 100644 tests/dummy/app/controllers/intermediate.js create mode 100644 tests/dummy/app/helpers/link-to-attachment.js create mode 100644 tests/dummy/app/models/attachment.js create mode 100644 tests/dummy/app/models/issue.js create mode 100644 tests/dummy/app/models/position.js create mode 100644 tests/dummy/app/routes/index.js create mode 100644 tests/dummy/app/serializers/application.js create mode 100644 tests/dummy/app/serializers/attachment.js create mode 100644 tests/dummy/app/templates/_create-issue.hbs create mode 100644 tests/dummy/app/templates/_issue-list.hbs create mode 100644 tests/dummy/app/templates/board.hbs create mode 100644 tests/dummy/app/templates/index.hbs diff --git a/.editorconfig b/.editorconfig index 47c5438..3ef6636 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,24 +11,24 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space -indent_size = 2 +indent_size = 4 [*.js] indent_style = space -indent_size = 2 +indent_size = 4 [*.hbs] insert_final_newline = false indent_style = space -indent_size = 2 +indent_size = 4 [*.css] indent_style = space -indent_size = 2 +indent_size = 4 [*.html] indent_style = space -indent_size = 2 +indent_size = 4 [*.{diff,md}] trim_trailing_whitespace = false diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 0000000..076d6db --- /dev/null +++ b/.jscsrc @@ -0,0 +1,51 @@ +{ + "disallowFunctionDeclarations": true, + "disallowEmptyBlocks": true, + "disallowImplicitTypeConversion": ["numeric", "boolean", "binary", "string"], + "disallowKeywordsOnNewLine": ["else", "catch", "while", "until"], + "disallowKeywords": ["const", "let", "super", "with", "yield"], + "disallowMixedSpacesAndTabs": true, + "disallowMultipleLineBreaks": true, + "disallowMultipleLineStrings": true, + "disallowNewlineBeforeBlockStatements": true, + "disallowOperatorBeforeLineBreak": ["+", "."], + "disallowPaddingNewlinesInBlocks": true, + "disallowQuotedKeysInObjects": "allButReserved", + "disallowSpacesInsideParentheses": true, + "disallowYodaConditions": true, + "disallowTrailingComma": true, + "disallowTrailingWhitespace": true, + "esnext": true, + "requireBlocksOnNewline": true, + "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", + "requireCommaBeforeLineBreak": true, + "requireDotNotation": true, + "requireLineBreakAfterVariableAssignment": true, + "requireMultipleVarDecl": "onevar", + "requireOperatorBeforeLineBreak": true, + "requirePaddingNewLinesInObjects": true, + "requireParenthesesAroundIIFE": true, + "requireSpaceAfterBinaryOperators": true, + "requireSpaceAfterKeywords": true, + "requireSpaceAfterLineComment": true, + "requireSpaceBeforeBinaryOperators": true, + "requireSpaceBeforeBlockStatements": true, + "requireSpacesInConditionalExpression": { + "afterTest": true, + "beforeConsequent": true, + "afterConsequent": true, + "beforeAlternate": true + }, + "requireSpacesInFunction": { + "beforeOpeningRoundBrace": true, + "beforeOpeningCurlyBrace": true + }, + "requireSpaceBeforeKeywords": ["else", "while", "until", "catch"], + "requireSpaceBeforeObjectValues": true, + "requireSpaceBetweenArguments": true, + "validateIndentation": 4, + "validateQuoteMarks": { + "mark": "\"", + "escape": true + } +} diff --git a/.jshintrc b/.jshintrc index 08096ef..62ef27e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,32 +1,61 @@ { - "predef": [ - "document", - "window", - "-Promise" - ], - "browser": true, - "boss": true, - "curly": true, - "debug": false, - "devel": true, - "eqeqeq": true, - "evil": true, - "forin": false, - "immed": false, - "laxbreak": false, - "newcap": true, - "noarg": true, - "noempty": false, - "nonew": false, - "nomen": false, - "onevar": false, - "plusplus": false, - "regexp": false, - "undef": true, - "sub": true, - "strict": false, - "white": false, - "eqnull": true, - "esnext": true, - "unused": true + "bitwise": true, + "curly": true, + "eqeqeq": true, + "es3": false, + "es5": false, + "forin": true, + "freeze": true, + "funcscope": true, + "futurehostile": true, + "globals": { + "$": false, + "visit": false, + "click": false, + "andThen": false, + "currentURL": false, + "test": false, + "find": false, + "fillIn": false, + "module": false + }, + "globalstrict": false, + "iterator": false, + "latedef": true, + "maxcomplexity": 8, + "maxdepth": 6, + "maxerr": 5, + "maxparams": 5, + "maxstatements": 100, + "noarg": true, + "nocomma": true, + "nonbsp": true, + "nonew": true, + "notypeof": false, + "plusplus": false, + "shadow": false, + "singleGroups": true, + "undef": true, + + "asi": false, + "boss": false, + "debug": false, + "elision": false, + "eqnull": false, + "esnext": true, + "evil": false, + "expr": false, + "lastsemic": false, + "loopfunc": false, + "moz": false, + "noyield": false, + "phantom": [], + "proto": false, + "scripturl": false, + "supernew": false, + "validthis": false, + "withstmt": false, + "browser": true, + "jquery": true, + "unused": "params" } diff --git a/addon/adapters/attachment.js b/addon/adapters/attachment.js new file mode 100644 index 0000000..f3e4774 --- /dev/null +++ b/addon/adapters/attachment.js @@ -0,0 +1,99 @@ +import Ember from "ember"; +import DS from "ember-data"; + +export default DS.Adapter.extend({ + sharedStore: Ember.inject.service(), + find: function (store, type, id) { + var sharedStore = this.get("sharedStore"); + return new Ember.RSVP.Promise(function (resolve, reject) { + return Ember.run(null, resolve, { + attachment: sharedStore.get("attachment", id) + }); + }); + }, + findMany: function (store, type, ids) { + var docs, + _this = this; + docs = ids.map(function (item) { + item = _this.get("sharedStore").get("attachment", item); + item.db = _this.get("db"); + return item; + }); + return new Ember.RSVP.Promise(function (resolve, reject) { + return Ember.run(null, resolve, { + attachments: docs + }); + }); + }, + createRecord: function (store, type, record) { + var adapter, url; + url = "%@/%@?rev=%@".fmt(this.buildURL(), record.get("id"), record.get("rev")); + adapter = this; + return new Ember.RSVP.Promise(function (resolve, reject) { + var data, request, + _this = this; + data = {}; + data.context = adapter; + request = new window.XMLHttpRequest(); + request.open("PUT", url, true); + request.setRequestHeader("Content-Type", record.get("content_type")); + adapter._updateUploadState(record, request); + request.onreadystatechange = function () { + var json; + if (request.readyState === 4 && (request.status === 201 || request.status === 200)) { + data = JSON.parse(request.response); + data.model_name = record.get("model_name"); + data.doc_id = record.get("doc_id"); + json = adapter.serialize(record, { + includeId: true + }); + delete data.id; + return Ember.run(null, resolve, { + attachment: Ember.$.extend(json, data) + }); + } + }; + return request.send(record.get("file")); + }); + }, + updateRecord: function (store, type, record) {}, + deleteRecord: function (store, type, record) { + return new Ember.RSVP.Promise(function (resolve, reject) { + return Ember.run(null, resolve, {}); + }); + }, + _updateUploadState: function (record, request) { + var view, + _this = this; + view = record.get("view"); + if (view) { + view.startUpload(); + request.onprogress = function (oEvent) { + var percentComplete; + if (oEvent.lengthComputable) { + percentComplete = oEvent.loaded / oEvent.total * 100; + return view.updateUpload(percentComplete); + } + }; + return request.onprogress; + } + }, + buildURL: function () { + var host, namespace, url; + host = Ember.get(this, "host"); + namespace = Ember.get(this, "namespace"); + url = []; + if (host) { + url.push(host); + } + if (namespace) { + url.push(namespace); + } + url.push(this.get("db")); + url = url.join("/"); + if (!host) { + url = "/" + url; + } + return url; + } +}); diff --git a/addon/adapters/document.js b/addon/adapters/document.js new file mode 100644 index 0000000..00f97b9 --- /dev/null +++ b/addon/adapters/document.js @@ -0,0 +1,275 @@ +import Ember from "ember"; +import DS from "ember-data"; + +export default DS.Adapter.extend({ + sharedStore: Ember.inject.service(), + defaultSerializer: "_default", + customTypeLookup: false, + typeViewName: "all", + buildURL: function () { + var host, namespace, url; + host = Ember.get(this, "host"); + namespace = Ember.get(this, "namespace"); + url = []; + if (host) { + url.push(host); + } + if (namespace) { + url.push(namespace); + } + url.push(this.get("db")); + url = url.join("/"); + if (!host) { + url = "/" + url; + } + return url; + }, + ajax: function (url, type, normalizeResponse, hash) { + return this._ajax("%@/%@".fmt(this.buildURL(), url || ""), type, normalizeResponse, hash); + }, + _ajax: function (url, type, normalizeResponse, hash) { + var adapter; + if (hash === null) { + hash = {}; + } + adapter = this; + return new Ember.RSVP.Promise(function (resolve, reject) { + var headers; + if (url.split("/").pop() === "") { + url = url.substr(0, url.length - 1); + } + hash.url = url; + hash.type = type; + hash.dataType = "json"; + hash.contentType = "application/json; charset=utf-8"; + hash.context = adapter; + if (hash.data && type !== "GET") { + hash.data = JSON.stringify(hash.data); + } + if (adapter.headers) { + headers = adapter.headers; + hash.beforeSend = function (xhr) { + return Ember.keys(headers).forEach(function (key) { + return xhr.setRequestHeader(key, headers[key]); + }); + }; + } + if (!hash.success) { + hash.success = function (json) { + var _modelJson; + _modelJson = normalizeResponse.call(adapter, json); + return Ember.run(null, resolve, _modelJson); + }; + } + hash.error = function (jqXHR, textStatus, errorThrown) { + if (jqXHR) { + jqXHR.then = null; + } + return Ember.run(null, reject, jqXHR); + }; + return Ember.$.ajax(hash); + }); + }, + _normalizeRevision: function (json) { + if (json && json._rev) { + json.rev = json._rev; + delete json._rev; + } + return json; + }, + shouldCommit: function (snapshot, relationships) { + return this._super.apply(arguments); + }, + find: function (store, type, id) { + var normalizeResponse; + if (this._checkForRevision(id)) { + return this.findWithRev(store, type, id); + } else { + normalizeResponse = function (data) { + var _modelJson; + this._normalizeRevision(data); + _modelJson = {}; + _modelJson[type.modelName] = data; + return _modelJson; + }; + return this.ajax(id, "GET", normalizeResponse); + } + }, + findWithRev: function (store, type, id, hash) { + var normalizeResponse, url, _id, _ref, _rev; + _ref = id.split("/").slice(0, 2); + _id = _ref[0]; + _rev = _ref[1]; + url = "%@?rev=%@".fmt(_id, _rev); + normalizeResponse = function (data) { + var _modelJson; + this._normalizeRevision(data); + _modelJson = {}; + data._id = id; + _modelJson[type.modelName] = data; + return _modelJson; + }; + return this.ajax(url, "GET", normalizeResponse, hash); + }, + findManyWithRev: function (store, type, ids) { + var docs, hash, key, self, + _this = this; + key = Ember.String.pluralize(type.modelName); + self = this; + docs = {}; + docs[key] = []; + hash = { + async: false + }; + ids.forEach(function (id) { + var url, _id, _ref, _rev; + _ref = id.split("/").slice(0, 2); + _id = _ref[0]; + _rev = _ref[1]; + url = "%@?rev=%@".fmt(_id, _rev); + url = "%@/%@".fmt(_this.buildURL(), url); + hash.url = url; + hash.type = "GET"; + hash.dataType = "json"; + hash.contentType = "application/json; charset=utf-8"; + hash.success = function (json) { + json._id = id; + self._normalizeRevision(json); + return docs[key].push(json); + }; + return Ember.$.ajax(hash); + }); + return docs; + }, + findMany: function (store, type, ids) { + var data, normalizeResponse; + if (this._checkForRevision(ids[0])) { + return this.findManyWithRev(store, type, ids); + } else { + data = { + include_docs: true, + keys: ids + }; + normalizeResponse = function (data) { + var json, + _this = this; + json = {}; + json[Ember.String.pluralize(type.modelName)] = data.rows.getEach("doc").map(function (doc) { + return _this._normalizeRevision(doc); + }); + return json; + }; + return this.ajax("_all_docs?include_docs=true", "POST", normalizeResponse, { + data: data + }); + } + }, + findQuery: function (store, type, query, modelArray) { + var designDoc, normalizeResponse; + designDoc = query.designDoc || this.get("designDoc"); + if (!query.options) { + query.options = {}; + } + query.options.include_docs = true; + normalizeResponse = function (data) { + var json, + _this = this; + json = {}; + json[designDoc] = data.rows.getEach("doc").map(function (doc) { + return _this._normalizeRevision(doc); + }); + json.total_rows = data.total_rows; + return json; + }; + return this.ajax("_design/%@/_view/%@".fmt(designDoc, query.viewName), "GET", normalizeResponse, { + context: this, + data: query.options + }); + }, + findAll: function (store, type) { + var data, designDoc, normalizeResponse, typeString, typeViewName; + typeString = Ember.String.singularize(type.modelName); + designDoc = this.get("designDoc") || typeString; + typeViewName = this.get("typeViewName"); + normalizeResponse = function (data) { + var json, + _this = this; + json = {}; + json[[Ember.String.pluralize(type.modelName)]] = data.rows.getEach("doc").map(function (doc) { + return _this._normalizeRevision(doc); + }); + return json; + }; + data = { + include_docs: true, + key: "\"" + typeString + "\"" + }; + return this.ajax("_design/%@/_view/%@".fmt(designDoc, typeViewName), "GET", normalizeResponse, { + data: data + }); + }, + createRecord: function (store, type, snapshot) { + var json; + json = store.serializerFor(type.modelName).serialize(snapshot); + delete json.rev; + return this._push(store, type, snapshot, json); + }, + updateRecord: function (store, type, snapshot) { + var json, snapData; + json = this.serialize(snapshot, { + associations: true, + includeId: true + }); + snapData = snapshot.record._data; + if ("attachments" in snapData ? snapData.attachments.length > 0 : void 0) { + this._updateAttachmnets(snapshot, json); + } + delete json.rev; + return this._push(store, type, snapshot, json); + }, + deleteRecord: function (store, type, snapshot) { + return this.ajax("%@?rev=%@".fmt(snapshot.id, snapshot.attr("rev")), "DELETE", (function () {}), {}); + }, + _updateAttachmnets: function (snapshot, json) { + var _attachments, sharedStore; + _attachments = {}; + sharedStore = this.get("sharedStore"); + snapshot._hasManyRelationships.attachments.forEach(function (item) { + var attachment; + attachment = sharedStore.get("attachment", item.get("id")); + _attachments[attachment.file_name] = { + content_type: attachment.content_type, + digest: attachment.digest, + length: attachment.length, + stub: attachment.stub, + revpos: attachment.revpos + }; + return _attachments[attachment.file_name]; + }); + json._attachments = _attachments; + delete json.attachments; + return delete json.history; + }, + _checkForRevision: function (id) { + return id.split("/").length > 1; + }, + _push: function (store, type, snapshot, json) { + var id, method, normalizeResponse; + id = snapshot.id || ""; + method = snapshot.id ? "PUT" : "POST"; + if (snapshot.attr("rev")) { + json._rev = snapshot.attr("rev"); + } + normalizeResponse = function (data) { + var _data, _modelJson; + _data = json || {}; + this._normalizeRevision(data); + _modelJson = {}; + _modelJson[type.modelName] = Ember.$.extend(_data, data); + return _modelJson; + }; + return this.ajax(id, method, normalizeResponse, { + data: json + }); + } +}); diff --git a/addon/adapters/rev.js b/addon/adapters/rev.js new file mode 100644 index 0000000..c61e4ba --- /dev/null +++ b/addon/adapters/rev.js @@ -0,0 +1,56 @@ +import Ember from "ember"; +import DS from "ember-data"; + +export default DS.Adapter.extend({ + sharedStore: Ember.inject.service(), + find: function (store, type, id) { + return this.ajax("%@?revs_info=true".fmt(id.split("/")[0]), "GET", { + context: this + }, id); + }, + updateRecord: function (store, type, record) {}, + deleteRecord: function (store, type, record) {}, + ajax: function (url, type, hash, id) { + return this._ajax("%@/%@".fmt(this.buildURL(), url || ""), type, hash, id); + }, + _ajax: function (url, type, hash, id) { + var sharedStore = this.get("sharedStore"); + hash.url = url; + hash.type = type; + hash.dataType = "json"; + hash.contentType = "application/json; charset=utf-8"; + hash.context = this; + if (hash.data && type !== "GET") { + hash.data = JSON.stringify(hash.data); + } + return new Ember.RSVP.Promise(function (resolve, reject) { + hash.success = function (data) { + sharedStore.add("revs", id, data); + return Ember.run(null, resolve, { + history: { + id: id + } + }); + }; + return Ember.$.ajax(hash); + }); + }, + buildURL: function () { + var host, namespace, url; + host = Ember.get(this, "host"); + namespace = Ember.get(this, "namespace"); + url = []; + if (host) { + url.push(host); + } + if (namespace) { + url.push(namespace); + } + url.push(this.get("db")); + url = url.join("/"); + if (!host) { + url = "/" + url; + } + return url; + } +}); diff --git a/addon/changes-feed.js b/addon/changes-feed.js new file mode 100644 index 0000000..15274b8 --- /dev/null +++ b/addon/changes-feed.js @@ -0,0 +1,87 @@ +import Ember from "ember"; + +export default Ember.ObjectProxy.extend({ + content: {}, + longpoll: function () { + this.feed = "longpoll"; + return this._ajax.apply(this, arguments); + }, + normal: function () { + this.feed = "normal"; + return this._ajax.apply(this, arguments); + }, + continuous: function () { + this.feed = "continuous"; + return this._ajax.apply(this, arguments); + }, + fromTail: function (callback) { + var _this = this; + return Ember.$.ajax({ + url: "%@%@/_changes?descending=true&limit=1".fmt(this._buildUrl(), this.get("db")), + dataType: "json", + success: function (data) { + _this.set("since", data.last_seq); + if (callback) { + return callback.call(_this); + } + } + }); + }, + stop: function () { + this.set("stopTracking", true); + return this; + }, + start: function (callback) { + this.set("stopTracking", false); + return this.fromTail(callback); + }, + _ajax: function (callback, self) { + var _this = this; + return Ember.$.ajax({ + type: "GET", + url: this._makeRequestPath(), + dataType: "json", + success: function (data) { + var _ref; + if (!_this.get("stopTracking")) { + if ((data !== null ? (_ref = data.results) !== null ? _ref.length : void 0 : void 0) && callback) { + callback.call(self, data.results); + } + return _this.set("since", data.last_seq); + } + }, + complete: function () { + if (!_this.get("stopTracking")) { + return setTimeout((function () { + return _this._ajax(callback, self); + }), 1000); + } + } + }); + }, + _buildUrl: function () { + var url; + url = this.get("host") || "/"; + if (url.substring(url.length - 1) !== "/") { + url += "/"; + } + return url; + }, + _makeRequestPath: function () { + var feed, params; + feed = this.feed || "longpool"; + params = this._makeFeedParams(); + return "%@%@/_changes?feed=%@%@".fmt(this._buildUrl(), this.get("db"), feed, params); + }, + _makeFeedParams: function () { + var path, + _this = this; + path = ""; + ["include_docs", "limit", "descending", "heartbeat", "timeout", "filter", "filter_param", "style", "since"].forEach(function (param) { + if (_this.get(param)) { + return path += "&%@=%@".fmt(param, _this.get(param)); + } + }); + return path; + } +}); diff --git a/addon/index.js b/addon/index.js index f55ea60..7e3a0ad 100644 --- a/addon/index.js +++ b/addon/index.js @@ -1,4 +1,19 @@ -/* global Ember*/ +import ChangesFeed from "./changes-feed"; +import AttachmentAdapter from "./adapters/attachment"; +import DocumentAdapter from "./adapters/document"; +import RevAdapter from "./adapters/rev"; +import AttachmentSerializer from "./serializers/attachment"; +import DocumentSerializer from "./serializers/document"; +import RevSerializer from "./serializers/rev"; +import SharedStore from "./services/shared-store"; -let version = '2.0.0-alpha'; -Ember.libraries.register('Ember CouchDB Kit', version); +export { + ChangesFeed, + AttachmentAdapter, + DocumentAdapter, + RevAdapter, + AttachmentSerializer, + DocumentSerializer, + RevSerializer, + SharedStore +}; diff --git a/addon/serializers/attachment.js b/addon/serializers/attachment.js new file mode 100644 index 0000000..5d6b903 --- /dev/null +++ b/addon/serializers/attachment.js @@ -0,0 +1,25 @@ +import DS from "ember-data"; + +export default DS.RESTSerializer.extend({ + primaryKey: "id", + normalize: function (type, hash) { + var rev, self; + self = this; + rev = hash._rev || hash.rev; + this.store.find(hash.model_name, hash.doc_id).then(function (document) { + if (document.get("_data.rev") !== rev) { + if (self.getIntRevision(document.get("_data.rev")) < self.getIntRevision(rev)) { + return document.set("_data.rev", rev); + } + } + }); + return this._super(type, hash); + }, + getIntRevision: function (revision) { + return parseInt(revision.split("-")[0]); + }, + normalizeId: function (hash) { + hash.id = hash._id || hash.id; + return hash.id; + } +}); diff --git a/addon/serializers/document.js b/addon/serializers/document.js new file mode 100644 index 0000000..37d2332 --- /dev/null +++ b/addon/serializers/document.js @@ -0,0 +1,114 @@ +import Ember from "ember"; +import DS from "ember-data"; + +export default DS.RESTSerializer.extend({ + sharedStore: Ember.inject.service(), + primaryKey: "_id", + normalize: function (type, hash, prop) { + this.normalizeId(hash); + this.normalizeAttachments(hash._attachments, type.modelName, hash); + this.addHistoryId(hash); + this.normalizeUsingDeclaredMapping(type, hash); + this.normalizeAttributes(type, hash); + this.normalizeRelationships(type, hash); + if (this.normalizeHash && this.normalizeHash[prop]) { + return this.normalizeHash[prop](hash); + } + if (!hash) { + return hash; + } + this.applyTransforms(type, hash); + return hash; + }, + extractSingle: function (store, type, payload, id, requestType) { + return this._super(store, type, payload, id, requestType); + }, + extractMeta: function (store, type, payload) { + if (payload && payload.total_rows) { + store.setMetadataFor(type, { + total_rows: payload.total_rows + }); + delete payload.total_rows; + } + }, + serialize: function (snapshot, options) { + return this._super(snapshot, options); + }, + addHistoryId: function (hash) { + hash.history = "%@/history".fmt(hash.id); + return hash.history; + }, + normalizeAttachments: function (attachments, type, hash) { + var attachment, k, key, v, _attachments; + _attachments = []; + for (k in attachments) { + if (attachments.hasOwnProperty(k)) { + v = attachments[k]; + key = hash._id + "/" + k; + attachment = { + id: key, + content_type: v.content_type, + digest: v.digest, + length: v.length, + stub: v.stub, + doc_id: hash._id, + rev: hash.rev, + file_name: k, + model_name: type, + revpos: v.revpos, + db: v.db + }; + this.get("sharedStore").add("attachment", key, attachment); + _attachments.push(key); + } + } + hash.attachments = _attachments; + return hash.attachments; + }, + normalizeId: function (hash) { + hash.id = hash._id || hash.id; + return hash.id; + }, + normalizeRelationships: function (type, hash) { + var key, payloadKey; + payloadKey = void 0; + key = void 0; + if (this.keyForRelationship) { + return type.eachRelationship((function (key, relationship) { + payloadKey = this.keyForRelationship(key, relationship.kind); + if (key === payloadKey) { + return; + } + hash[key] = hash[payloadKey]; + return delete hash[payloadKey]; + }), this); + } + }, + serializeBelongsTo: function (snapshot, json, relationship) { + var attribute, belongsTo, key; + attribute = relationship.options.attribute || "id"; + key = relationship.key; + belongsTo = snapshot.belongsTo(key); + if (Ember.isNone(belongsTo)) { + return; + } + json[key] = attribute === "id" ? belongsTo.id : belongsTo.attr(attribute); + if (relationship.options.polymorphic) { + json[key + "_type"] = belongsTo.modelName; + return json[key + "_type"]; + } + }, + serializeHasMany: function (snapshot, json, relationship) { + var attribute, key, relationshipType; + attribute = relationship.options.attribute || "id"; + key = relationship.key; + relationshipType = snapshot.type.determineRelationshipType(relationship); + switch (relationshipType) { + case "manyToNone": + case "manyToMany": + case "manyToOne": + json[key] = snapshot.hasMany(key).mapBy(attribute); + return json[key]; + } + } +}); diff --git a/addon/serializers/rev.js b/addon/serializers/rev.js new file mode 100644 index 0000000..2c63b7f --- /dev/null +++ b/addon/serializers/rev.js @@ -0,0 +1,26 @@ +import Ember from "ember"; +import DS from "ember-data"; + +export default DS.RESTSerializer.extend({ + sharedStore: Ember.inject.service(), + primaryKey: "id", + normalize: function (type, hash, prop) { + this.normalizeRelationships(type, hash); + return this._super(type, hash, prop); + }, + extractId: function (type, hash) { + return hash._id || hash.id; + }, + normalizeRelationships: function (type, hash) { + var sharedStore = this.get("sharedStore"); + return type.eachRelationship((function (key, relationship) { + if (relationship.kind === "belongsTo") { + hash[key] = sharedStore.mapRevIds("revs", this.extractId(type, hash))[1]; + } + if (relationship.kind === "hasMany") { + hash[key] = sharedStore.mapRevIds("revs", this.extractId(type, hash)); + return hash[key]; + } + }), this); + } +}); diff --git a/addon/services/shared-store.js b/addon/services/shared-store.js new file mode 100644 index 0000000..49e2f0e --- /dev/null +++ b/addon/services/shared-store.js @@ -0,0 +1,39 @@ +import Ember from "ember"; + +export default Ember.Service.extend({ + _data: {}, + add: function (type, key, value) { + var _data = this.get("_data"); + _data[type + ":" + key] = value; + return _data[type + ":" + key]; + }, + get: function (type, key) { + var _data = this.get("_data"); + return _data[type + ":" + key]; + }, + remove: function (type, key) { + var _data = this.get("_data"); + return delete _data[type + ":" + key]; + }, + mapRevIds: function (type, key) { + var _this = this; + return this.get(type, key)._revs_info.map(function (_rev) { + return "%@/%@".fmt(_this.get(type, key)._id, _rev.rev); + }); + }, + stopAll: function () { + var k, v, _results, _data = this.get("_data"); + _results = []; + for (k in _data) { + if (_data.hasOwnProperty(k)) { + v = _data[k]; + if (k.indexOf("changes_worker") === 0) { + _results.push(v.stop()); + } else { + _results.push(void 0); + } + } + } + return _results; + } +}); diff --git a/bower.json b/bower.json index 57079dc..8a8a75b 100644 --- a/bower.json +++ b/bower.json @@ -1,8 +1,8 @@ { - "name": "ember-couchdb-kit", + "name": "ember-couch", "private": true, "dependencies": { - "ember": "components/ember#canary", + "ember": "^1.13.5", "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", "ember-data": "1.13.6", @@ -13,8 +13,5 @@ "jquery": "^1.11.1", "loader.js": "ember-cli/loader.js#3.2.0", "qunit": "~1.17.1" - }, - "resolutions": { - "ember": "canary" } -} \ No newline at end of file +} diff --git a/config/ember-try.js b/config/ember-try.js index 83dab0f..1d572d4 100644 --- a/config/ember-try.js +++ b/config/ember-try.js @@ -1,35 +1,85 @@ module.exports = { - scenarios: [ - { - name: 'default', - dependencies: { } - }, - { - name: 'ember-release', - dependencies: { - 'ember': 'components/ember#release' - }, - resolutions: { - 'ember': 'release' - } - }, - { - name: 'ember-beta', - dependencies: { - 'ember': 'components/ember#beta' - }, - resolutions: { - 'ember': 'beta' - } - }, - { - name: 'ember-canary', - dependencies: { - 'ember': 'components/ember#canary' - }, - resolutions: { - 'ember': 'canary' - } - } - ] + scenarios: [ + { + name: "default", + dependencies: { } + }, + { + name: 'ember-data-beta.15', + dependencies: { + 'ember': '1.10.0', + 'ember-data': '1.0.0-beta.15' + }, + resolutions: { + 'ember': '1.10.0' + } + }, + { + name: 'ember-data-beta.16', + dependencies: { + 'ember': '1.10.0', + 'ember-data': '1.0.0-beta.16.1' + }, + resolutions: { + 'ember': '1.10.0' + } + }, + { + name: 'ember-data-beta.18', + dependencies: { + 'ember': '1.12.1', + 'ember-data': '1.0.0-beta.18' + }, + resolutions: { + 'ember': '1.12.1' + } + }, + { + name: 'ember-data-beta.19', + dependencies: { + 'ember': '1.13.2', + 'ember-data': '1.0.0-beta.19.2' + }, + resolutions: { + 'ember': '1.13.2' + } + }, + { + name: 'ember-data-1.13', + dependencies: { + 'ember': '1.13.5', + 'ember-data': '1.13.6' + }, + resolutions: { + 'ember': '1.13.5' + } + }, + { + name: "ember-release", + dependencies: { + "ember": "components/ember#release" + }, + resolutions: { + "ember": "release" + } + }, + { + name: "ember-beta", + dependencies: { + "ember": "components/ember#beta" + }, + resolutions: { + "ember": "beta" + } + }, + { + name: "ember-canary", + dependencies: { + "ember": "components/ember#canary" + }, + resolutions: { + "ember": "canary" + } + } + ] }; diff --git a/config/environment.js b/config/environment.js index 0dfaed4..8ff1852 100644 --- a/config/environment.js +++ b/config/environment.js @@ -1,5 +1,5 @@ -'use strict'; +"use strict"; module.exports = function(/* environment, appConfig */) { - return { }; + return { }; }; diff --git a/ember-cli-build.js b/ember-cli-build.js index d37d64c..3518724 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -2,16 +2,19 @@ var EmberApp = require('ember-cli/lib/broccoli/ember-addon'); module.exports = function(defaults) { - var app = new EmberApp(defaults, { - // Add options here - }); + var app = new EmberApp(defaults, { + jscsOptions: { + enabled: true, + esnext: true + } + }); - /* - This build file specifes the options for the dummy test app of this - addon, located in `/tests/dummy` - This build file does *not* influence how the addon or the app using it - behave. You most likely want to be modifying `./index.js` or app's build file - */ + /* + This build file specifes the options for the dummy test app of this + addon, located in `/tests/dummy` + This build file does *not* influence how the addon or the app using it + behave. You most likely want to be modifying `./index.js` or app's build file + */ - return app.toTree(); + return app.toTree(); }; diff --git a/index.js b/index.js index a8e5fa9..a808d7c 100644 --- a/index.js +++ b/index.js @@ -2,5 +2,5 @@ 'use strict'; module.exports = { - name: 'ember-couchdb-kit' + name: 'ember-couch' }; diff --git a/package.json b/package.json index eb25316..6d6d582 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "ember-couchdb-kit", + "name": "ember-couch", "version": "2.0.0-alpha", "description": "An Ember.js adapter for Apache CouchDB", "directories": { @@ -13,15 +13,16 @@ }, "repository": { "type": "git", - "url": "https://github.com/Zatvobor/ember-couchdb-kit.git" + "url": "https://github.com/ValidUSA/ember-couch.git" }, "engines": { "node": ">= 0.10.0" }, - "author": "Aleksey Zatvobor", + "author": "Valid USA", "license": "MIT", "devDependencies": { "broccoli-asset-rev": "^2.0.2", + "broccoli-jscs": "0.0.22", "ember-cli": "1.13.1", "ember-cli-app-version": "0.4.0", "ember-cli-content-security-policy": "0.4.0", @@ -34,13 +35,16 @@ "ember-cli-release": "0.2.3", "ember-cli-uglify": "^1.0.1", "ember-data": "1.13.6", + "ember-disable-prototype-extensions": "^1.0.0", "ember-disable-proxy-controllers": "^1.0.0", "ember-export-application-global": "^1.0.2", - "ember-disable-prototype-extensions": "^1.0.0", "ember-try": "0.0.6" }, "keywords": [ - "ember-addon", "couch db" + "ember-addon", + "couch db", + "couchdb", + "couch" ], "dependencies": { "ember-cli-babel": "^5.0.0" @@ -48,4 +52,4 @@ "ember-addon": { "configPath": "tests/dummy/config" } -} \ No newline at end of file +} diff --git a/tests/.jshintrc b/tests/.jshintrc index 6ec0b7c..62ef27e 100644 --- a/tests/.jshintrc +++ b/tests/.jshintrc @@ -1,52 +1,61 @@ { - "predef": [ - "document", - "window", - "location", - "setTimeout", - "$", - "-Promise", - "define", - "console", - "visit", - "exists", - "fillIn", - "click", - "keyEvent", - "triggerEvent", - "find", - "findWithAssert", - "wait", - "DS", - "andThen", - "currentURL", - "currentPath", - "currentRouteName" - ], - "node": false, - "browser": false, - "boss": true, - "curly": true, - "debug": false, - "devel": false, - "eqeqeq": true, - "evil": true, - "forin": false, - "immed": false, - "laxbreak": false, - "newcap": true, - "noarg": true, - "noempty": false, - "nonew": false, - "nomen": false, - "onevar": false, - "plusplus": false, - "regexp": false, - "undef": true, - "sub": true, - "strict": false, - "white": false, - "eqnull": true, - "esnext": true, - "unused": true + "bitwise": true, + "curly": true, + "eqeqeq": true, + "es3": false, + "es5": false, + "forin": true, + "freeze": true, + "funcscope": true, + "futurehostile": true, + "globals": { + "$": false, + "visit": false, + "click": false, + "andThen": false, + "currentURL": false, + "test": false, + "find": false, + "fillIn": false, + "module": false + }, + "globalstrict": false, + "iterator": false, + "latedef": true, + "maxcomplexity": 8, + "maxdepth": 6, + "maxerr": 5, + "maxparams": 5, + "maxstatements": 100, + "noarg": true, + "nocomma": true, + "nonbsp": true, + "nonew": true, + "notypeof": false, + "plusplus": false, + "shadow": false, + "singleGroups": true, + "undef": true, + + "asi": false, + "boss": false, + "debug": false, + "elision": false, + "eqnull": false, + "esnext": true, + "evil": false, + "expr": false, + "lastsemic": false, + "loopfunc": false, + "moz": false, + "noyield": false, + "phantom": [], + "proto": false, + "scripturl": false, + "supernew": false, + "validthis": false, + "withstmt": false, + "browser": true, + "jquery": true, + "unused": "params" } diff --git a/tests/dummy/app/adapters/application.js b/tests/dummy/app/adapters/application.js new file mode 100644 index 0000000..566572d --- /dev/null +++ b/tests/dummy/app/adapters/application.js @@ -0,0 +1,7 @@ +/* global App */ +import DocumentAdapter from "ember-couch/adapters/document"; + +export default DocumentAdapter.extend({ + db: "boards", + host: App.Host +}); diff --git a/tests/dummy/app/adapters/attachment.js b/tests/dummy/app/adapters/attachment.js new file mode 100644 index 0000000..f8038d5 --- /dev/null +++ b/tests/dummy/app/adapters/attachment.js @@ -0,0 +1,7 @@ +/* global App */ +import AttachmentAdapter from "ember-couch/adapters/attachment"; + +export default AttachmentAdapter.extend({ + db: "boards", + host: App.Host +}); diff --git a/tests/dummy/app/app.js b/tests/dummy/app/app.js index 8d66b95..5f06137 100644 --- a/tests/dummy/app/app.js +++ b/tests/dummy/app/app.js @@ -1,16 +1,18 @@ -import Ember from 'ember'; -import Resolver from 'ember/resolver'; -import loadInitializers from 'ember/load-initializers'; -import config from './config/environment'; +import Ember from "ember"; +import Resolver from "ember/resolver"; +import loadInitializers from "ember/load-initializers"; +import config from "./config/environment"; var App; Ember.MODEL_FACTORY_INJECTIONS = true; App = Ember.Application.extend({ - modulePrefix: config.modulePrefix, - podModulePrefix: config.podModulePrefix, - Resolver: Resolver + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix, + Resolver: Resolver, + Boards: config.APP.Boards, + Host: config.APP.Host }); loadInitializers(App, config.modulePrefix); diff --git a/tests/dummy/app/components/eck-attachment.js b/tests/dummy/app/components/eck-attachment.js new file mode 100644 index 0000000..c5abec3 --- /dev/null +++ b/tests/dummy/app/components/eck-attachment.js @@ -0,0 +1,19 @@ +import Ember from "ember"; + +export default Ember.Component.extend({ + tagName: "input", + attributeBindings: ["style", "type", "multiple"], + style: "display:none", + type: "file", + multiple: true, + + actions: { + browseFile: function (e) { + this.$().click(); + } + }, + + change: function (event) { + this.get("controller").send("addAttachment", event.target.files, this.get("context")); + } +}); diff --git a/tests/dummy/app/components/eck-cancel.js b/tests/dummy/app/components/eck-cancel.js new file mode 100644 index 0000000..0e4de51 --- /dev/null +++ b/tests/dummy/app/components/eck-cancel.js @@ -0,0 +1,10 @@ +import Ember from "ember"; + +export default Ember.Component.extend({ + tagName: "span", + + click: function (event) { + event.preventDefault(); + this.set("parentView.create", false); + } +}); diff --git a/tests/dummy/app/components/eck-delete-attachment.js b/tests/dummy/app/components/eck-delete-attachment.js new file mode 100644 index 0000000..9574f82 --- /dev/null +++ b/tests/dummy/app/components/eck-delete-attachment.js @@ -0,0 +1,11 @@ +import Ember from "ember"; + +export default Ember.Component.extend({ + tagName: "span", + classNames: ["badge"], + + click: function (event) { + event.preventDefault(); + this.get("controller").send("deleteAttachment", this.get("context")); + } +}); diff --git a/tests/dummy/app/components/eck-delete-issue.js b/tests/dummy/app/components/eck-delete-issue.js new file mode 100644 index 0000000..72e3000 --- /dev/null +++ b/tests/dummy/app/components/eck-delete-issue.js @@ -0,0 +1,11 @@ +import Ember from "ember"; + +export default Ember.Component.extend({ + tagName: "button", + classNames: ["btn", "btn-xs", "btn-danger"], + + click: function (event) { + event.preventDefault(); + this.get("controller").send("deleteIssue", this.get("context")); + } +}); diff --git a/tests/dummy/app/components/eck-focused-text-area.js b/tests/dummy/app/components/eck-focused-text-area.js new file mode 100644 index 0000000..fb7dd3b --- /dev/null +++ b/tests/dummy/app/components/eck-focused-text-area.js @@ -0,0 +1,7 @@ +import Ember from "ember"; + +export default Ember.Component.extend({ + elementDidChange: function () { + this.$().focus(); + }.observes("element") +}); diff --git a/tests/dummy/app/components/eck-issue.js b/tests/dummy/app/components/eck-issue.js new file mode 100644 index 0000000..35b39df --- /dev/null +++ b/tests/dummy/app/components/eck-issue.js @@ -0,0 +1,43 @@ +import Ember from "ember"; + +export default Ember.Component.extend({ + tagName: "form", + edit: false, + attributeBindings: ["draggable"], + draggable: "true", + + submit: function (event) { + event.preventDefault(); + if (this.get("edit")) { + this.get("controller").send("saveIssue", this.get("context")); + } + this.toggleProperty("edit"); + }, + + dragStart: function (event) { + event.dataTransfer.setData("id", this.get("elementId")); + }, + + dragEnter: function (event) { + event.preventDefault(); + event.target.style.opacity = "0.4"; + }, + + dragOver: function (event) { + event.preventDefault(); + }, + + dragLeave: function (event) { + event.preventDefault(); + event.target.style.opacity = "1"; + }, + + drop: function (event) { + var view = Ember.View.views[event.dataTransfer.getData("id")]; + if (this.draggable === "true" || view.draggable === "true") { + this.get("controller").send("dropIssue", view.get("controller"), view.get("context"), this.get("context")); + } + event.preventDefault(); + event.target.style.opacity = "1"; + } +}); diff --git a/tests/dummy/app/components/eck-new-issue.js b/tests/dummy/app/components/eck-new-issue.js new file mode 100644 index 0000000..6c9fa86 --- /dev/null +++ b/tests/dummy/app/components/eck-new-issue.js @@ -0,0 +1,30 @@ +import Ember from "ember"; + +export default Ember.Component.extend({ + tagName: "form", + create: false, + attributeBindings: ["style"], + style: "display:inline", + + submit: function (event) { + this._save(event); + }, + + keyDown: function (event) { + if (event.keyCode === 13) { + this._save(event); + } + }, + + _save: function (event) { + var text; + event.preventDefault(); + if (this.get("create")) { + text = this.get("TextArea.value"); + if (!Ember.isEmpty(text)) { + this.get("controller").send("createIssue", text); + } + } + this.toggleProperty("create"); + } +}); diff --git a/tests/dummy/app/controllers/advanced.js b/tests/dummy/app/controllers/advanced.js new file mode 100644 index 0000000..a36fbfe --- /dev/null +++ b/tests/dummy/app/controllers/advanced.js @@ -0,0 +1,5 @@ +import IndexController from "dummy/controllers/index"; + +export default IndexController.extend({ + name: "advanced" +}); diff --git a/tests/dummy/app/controllers/common.js b/tests/dummy/app/controllers/common.js new file mode 100644 index 0000000..31405e8 --- /dev/null +++ b/tests/dummy/app/controllers/common.js @@ -0,0 +1,5 @@ +import IndexController from "dummy/controllers/index"; + +export default IndexController.extend({ + name: "common" +}); diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js new file mode 100644 index 0000000..e4a6196 --- /dev/null +++ b/tests/dummy/app/controllers/index.js @@ -0,0 +1,92 @@ +/* global App */ +import Ember from "ember"; + +export default Ember.Controller.extend({ + content: Ember.computed.alias("position.issues"), + + actions: { + createIssue: function (text) { + var self = this, + issue = this.get("store").createRecord("issue", { + text: text + }); + issue.save().then(function (issue) { + if (self.get("position.issues.isLoaded")) { + self.get("position.issues").pushObject(issue); + self.get("position").save(); + } else { + self.get("position.issues").then(function (issues) { + self.get("position.issues").pushObject(issue); + self.get("position").save(); + }); + } + }); + }, + + saveIssue: function (model) { + model.save(); + }, + + deleteIssue: function (issue) { + var self = this; + self.get("position.issues").removeObject(issue); + issue.deleteRecord(); + issue.save().then(function () { + self.get("position").save(); + }); + }, + + addAttachment: function (files, model) { + this._actions._addAttachment(0, files, files.length, model, this); + }, + + _addAttachment: function (count, files, size, model, self) { + var file = files[count], + attachmentId = "%@/%@".fmt(model.id, file.name), + params = { + doc_id: model.id, + model_name: App.Issue, + rev: model._data.rev, + id: attachmentId, + file: file, + content_type: file.type, + length: file.size, + file_name: file.name + }, + attachment; + + attachment = self.get("store").createRecord("attachment", params); + attachment.save().then(function () { + model.get("attachments").pushObject(attachment); + model.reload(); + count = count + 1; + if (count < size) { + self._actions._addAttachment(count, files, size, model, self); + } + }); + }, + + deleteAttachment: function (attachment) { + attachment.deleteRecord(); + attachment.save(); + }, + + dropIssue: function (viewController, viewModel, thisModel) { + var position = this.get("content").toArray().indexOf(thisModel), + self = this; + if (position === -1) { + position = 0; + } + viewController.get("content").removeObject(viewModel); + + if (viewController.name !== this.name) { + viewController.get("position").save().then(function () { + self.get("position").reload(); + }); + } + + this.get("content").insertAt(position, viewModel); + this.get("position").save(); + } + } +}); diff --git a/tests/dummy/app/controllers/intermediate.js b/tests/dummy/app/controllers/intermediate.js new file mode 100644 index 0000000..5c19fa5 --- /dev/null +++ b/tests/dummy/app/controllers/intermediate.js @@ -0,0 +1,5 @@ +import IndexController from "dummy/controllers/index"; + +export default IndexController.extend({ + name: "intermediate" +}); diff --git a/tests/dummy/app/helpers/link-to-attachment.js b/tests/dummy/app/helpers/link-to-attachment.js new file mode 100644 index 0000000..83bedb3 --- /dev/null +++ b/tests/dummy/app/helpers/link-to-attachment.js @@ -0,0 +1,10 @@ +/* global App */ +import Ember from "ember"; + +export default Ember.Handlebars.makeBoundHelper(function (attachment) { + var aTagTemplate = "%@", + url = "%@/%@/%@".fmt(App.Host, attachment.get("_data.db"), attachment.get("id")); + return new Ember.Handlebars.SafeString( + aTagTemplate.fmt(url, attachment.get("file_name")) + ); +}); diff --git a/tests/dummy/app/index.html b/tests/dummy/app/index.html index 1c49d36..f48c763 100644 --- a/tests/dummy/app/index.html +++ b/tests/dummy/app/index.html @@ -1,25 +1,25 @@ - - - - Dummy - - + + + + Dummy + + - {{content-for 'head'}} + {{content-for "head"}} - - + + - {{content-for 'head-footer'}} - - - {{content-for 'body'}} + {{content-for "head-footer"}} + + + {{content-for "body"}} - - + + - {{content-for 'body-footer'}} - + {{content-for "body-footer"}} + diff --git a/tests/dummy/app/models/attachment.js b/tests/dummy/app/models/attachment.js new file mode 100644 index 0000000..05d0866 --- /dev/null +++ b/tests/dummy/app/models/attachment.js @@ -0,0 +1,10 @@ +import DS from "ember-data"; + +export default DS.Model.extend({ + content_type: DS.attr("string"), + length: DS.attr("number"), + file_name: DS.attr("string"), + db: DS.attr("string", { + defaultValue: "boards" + }) +}); diff --git a/tests/dummy/app/models/issue.js b/tests/dummy/app/models/issue.js new file mode 100644 index 0000000..a694672 --- /dev/null +++ b/tests/dummy/app/models/issue.js @@ -0,0 +1,9 @@ +import DS from "ember-data"; + +export default DS.Model.extend({ + text: DS.attr("string"), + type: DS.attr("string", { + defaultValue: "issue" + }), + attachments: DS.hasMany("attachment") +}); diff --git a/tests/dummy/app/models/position.js b/tests/dummy/app/models/position.js new file mode 100644 index 0000000..c2a9c15 --- /dev/null +++ b/tests/dummy/app/models/position.js @@ -0,0 +1,8 @@ +import DS from "ember-data"; + +export default DS.Model.extend({ + issues: DS.hasMany("issue"), + type: DS.attr("string", { + defaultValue: "position" + }) +}); diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js index cef554b..adbb646 100644 --- a/tests/dummy/app/router.js +++ b/tests/dummy/app/router.js @@ -1,11 +1,11 @@ -import Ember from 'ember'; -import config from './config/environment'; +import Ember from "ember"; +import config from "./config/environment"; var Router = Ember.Router.extend({ - location: config.locationType + location: config.locationType }); -Router.map(function() { +Router.map(function () { }); export default Router; diff --git a/tests/dummy/app/routes/index.js b/tests/dummy/app/routes/index.js new file mode 100644 index 0000000..d03621a --- /dev/null +++ b/tests/dummy/app/routes/index.js @@ -0,0 +1,109 @@ +/* global App */ +import Ember from "ember"; +import ChangesFeed from "ember-couch/changes-feed"; + +export default Ember.Route.extend({ + + setupController: function (controller, model) { + this._setupPositionHolders(); + this._position(); + this._issue(); + }, + + renderTemplate: function () { + this.render(); + // link particular controller with its outlet + var self = this; + App.Boards.forEach(function (label) { + self.render("board", { + outlet: label, + into: "index", + controller: label + }); + }); + }, + + _setupPositionHolders: function () { + var self = this; + App.Boards.forEach(function (type) { + self.get("store").find("position", type).then( + function (position) { + // set issues into appropriate controller through position model + self.controllerFor(type).set("position", position); + }, + function (position) { + // create position documents (as a part of first time initialization) + if (position.status === 404) { + self.get("store").createRecord("position", { + id: type + }).save().then(function (position) { + self.controllerFor(type).set("position", position); + }); + } + } + ); + }); + }, + + _position: function () { + // create a CouchDB `/_change` listener which serves an position documents + var params = { + include_docs: true, + filter: "issues/only_positions" + }, + position = ChangesFeed.create({ + db: "boards", + host: App.Host, + content: params + }), + self = this; + + // all upcoming changes are passed to `_handlePositionChanges` callback through `fromTail` strategy + position.fromTail(function () { + position.longpoll(self._handlePositionChanges, self); + }); + }, + + _handlePositionChanges: function (data) { + var self = this; + data.forEach(function (obj) { + var position = self.controllerFor(obj.doc._id).get("position"); + // we should reload particular postion model in case of update is received from another user + if (position.get("_data.rev") !== obj.doc._rev) { + position.reload(); + } + }); + }, + + _issue: function () { + // create a CouchDB `/_change` issue listener which serves an issues + var params = { + include_docs: true, + filter: "issues/issue" + }, + issue = ChangesFeed.create({ + db: "boards", + host: App.Host, + content: params + }), + self = this; + + // all upcoming changes are passed to `_handleIssueChanges` callback through `fromTail` strategy + issue.fromTail(function () { + issue.longpoll(self._handleIssueChanges, self); + }); + }, + + _handleIssueChanges: function (data) { + var self = this; + // apply received updates + data.forEach(function (obj) { + var issue = self.get("store").all("issue").toArray().find(function (i) { + return i.get("id") === obj.doc._id; + }); + if (issue !== undefined && issue.get("_data.rev") !== obj.doc._rev) { + issue.reload(); + } + }); + } +}); diff --git a/tests/dummy/app/serializers/application.js b/tests/dummy/app/serializers/application.js new file mode 100644 index 0000000..7afa5c0 --- /dev/null +++ b/tests/dummy/app/serializers/application.js @@ -0,0 +1,3 @@ +import DocumentSerializer from "ember-couch/serializers/document"; + +export default DocumentSerializer.extend(); diff --git a/tests/dummy/app/serializers/attachment.js b/tests/dummy/app/serializers/attachment.js new file mode 100644 index 0000000..779296e --- /dev/null +++ b/tests/dummy/app/serializers/attachment.js @@ -0,0 +1,3 @@ +import AttachmentSerializer from "ember-couch/serializers/attachment"; + +export default AttachmentSerializer.extend(); diff --git a/tests/dummy/app/templates/_create-issue.hbs b/tests/dummy/app/templates/_create-issue.hbs new file mode 100644 index 0000000..fbb33fe --- /dev/null +++ b/tests/dummy/app/templates/_create-issue.hbs @@ -0,0 +1,14 @@ +{{#view App.NewIssueView}} + {{#if view.create}} +
+ {{#view App.FocusedTextArea viewName="TextArea" class="form-control"}} + {{/view}} +
+ + {{#view App.CancelView}} + + {{/view}} + {{else}} + + {{/if}} +{{/view}} \ No newline at end of file diff --git a/tests/dummy/app/templates/_issue-list.hbs b/tests/dummy/app/templates/_issue-list.hbs new file mode 100644 index 0000000..c0be984 --- /dev/null +++ b/tests/dummy/app/templates/_issue-list.hbs @@ -0,0 +1,45 @@ +
    + {{#each controller.content}} + + {{#view App.IssueView contextBinding=this}} +
  • + {{#if view.edit}} +
    + {{#view App.FocusedTextArea class="form-control" viewName="TextAreaEdit" valueBinding=view.context.text}} + {{/view}} +
    + {{#if attachments}} + {{#each attachments}} + {{file_name}} + {{#view App.DeleteAttachmentView contextBinding=this}} × {{/view}} + {{/each}} + {{/if}} + + {{#view App.AttachmentView contextBinding=this}} + + {{/view}} + {{else}} + {{text}} + {{#if attachments}} + , attachments: + {{#each attachments}} + {{linkToAttachment this}} + {{/each}} + {{/if}} +
    + + {{#view App.DeleteIssueView contextBinding=this}} + × + {{/view}} +
    + {{/if}} +
  • + {{/view}} + {{else}} + {{#view App.IssueView draggable=false}} +
  • + Empty board... +
  • + {{/view}} + {{/each}} +
\ No newline at end of file diff --git a/tests/dummy/app/templates/board.hbs b/tests/dummy/app/templates/board.hbs new file mode 100644 index 0000000..6e5fa7c --- /dev/null +++ b/tests/dummy/app/templates/board.hbs @@ -0,0 +1,9 @@ +
+
+

{{name}}

+
+
+ {{partial "createIssue"}} +
+ {{partial "issueList"}} +
\ No newline at end of file diff --git a/tests/dummy/app/templates/index.hbs b/tests/dummy/app/templates/index.hbs new file mode 100644 index 0000000..6beb5d5 --- /dev/null +++ b/tests/dummy/app/templates/index.hbs @@ -0,0 +1,25 @@ +
+ + +
+

A simple app to demonstrate usage of Ember.js adapter for CouchDB. To run this demo locally:

+

$ git clone git@github.com:roundscope/ember-couch.git

+

$ cd ember-couch/example

+

$ npm install && grunt server

+
+
+
{{outlet "common"}}
+
{{outlet "intermediate"}}
+
{{outlet "advanced"}}
+
+
\ No newline at end of file diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js index c59bcd5..4eb8c2d 100644 --- a/tests/dummy/config/environment.js +++ b/tests/dummy/config/environment.js @@ -1,47 +1,47 @@ /* jshint node: true */ -module.exports = function(environment) { - var ENV = { - modulePrefix: 'dummy', - environment: environment, - baseURL: '/', - locationType: 'auto', - EmberENV: { - FEATURES: { - // Here you can enable experimental features on an ember canary build - // e.g. 'with-controller': true - } - }, - - APP: { - // Here you can pass flags/options to your application instance - // when it is created +module.exports = function (environment) { + var ENV = { + modulePrefix: "dummy", + environment: environment, + baseURL: "/", + locationType: "auto", + EmberENV: { + FEATURES: { + // Here you can enable experimental features on an ember canary build + // e.g. "with-controller": true + } + }, + + APP: { + Boards: ["common", "intermediate", "advanced"], + Host: "http://localhost:5984" + } + }; + + if (environment === "development") { + // ENV.APP.LOG_RESOLVER = true; + // ENV.APP.LOG_ACTIVE_GENERATION = true; + // ENV.APP.LOG_TRANSITIONS = true; + // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; + // ENV.APP.LOG_VIEW_LOOKUPS = true; } - }; - if (environment === 'development') { - // ENV.APP.LOG_RESOLVER = true; - // ENV.APP.LOG_ACTIVE_GENERATION = true; - // ENV.APP.LOG_TRANSITIONS = true; - // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; - // ENV.APP.LOG_VIEW_LOOKUPS = true; - } + if (environment === "test") { + // Testem prefers this... + ENV.baseURL = "/"; + ENV.locationType = "none"; - if (environment === 'test') { - // Testem prefers this... - ENV.baseURL = '/'; - ENV.locationType = 'none'; + // keep test console output quieter + ENV.APP.LOG_ACTIVE_GENERATION = false; + ENV.APP.LOG_VIEW_LOOKUPS = false; - // keep test console output quieter - ENV.APP.LOG_ACTIVE_GENERATION = false; - ENV.APP.LOG_VIEW_LOOKUPS = false; - - ENV.APP.rootElement = '#ember-testing'; - } + ENV.APP.rootElement = "#ember-testing"; + } - if (environment === 'production') { + if (environment === "production") { - } + } - return ENV; + return ENV; }; diff --git a/tests/dummy/public/crossdomain.xml b/tests/dummy/public/crossdomain.xml index 0c16a7a..29a035d 100644 --- a/tests/dummy/public/crossdomain.xml +++ b/tests/dummy/public/crossdomain.xml @@ -1,15 +1,15 @@ - + - - + + - - + + diff --git a/tests/helpers/resolver.js b/tests/helpers/resolver.js index 28f4ece..5c0a08a 100644 --- a/tests/helpers/resolver.js +++ b/tests/helpers/resolver.js @@ -1,11 +1,11 @@ -import Resolver from 'ember/resolver'; -import config from '../../config/environment'; +import Resolver from "ember/resolver"; +import config from "../../config/environment"; var resolver = Resolver.create(); resolver.namespace = { - modulePrefix: config.modulePrefix, - podModulePrefix: config.podModulePrefix + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix }; export default resolver; diff --git a/tests/helpers/start-app.js b/tests/helpers/start-app.js index 0f7aab1..18fd3d7 100644 --- a/tests/helpers/start-app.js +++ b/tests/helpers/start-app.js @@ -1,18 +1,19 @@ -import Ember from 'ember'; -import Application from '../../app'; -import config from '../../config/environment'; +import Ember from "ember"; +import Application from "../../app"; +import config from "../../config/environment"; -export default function startApp(attrs) { - var application; +var startApp = function (attrs) { + var application, + attributes = Ember.merge({}, config.APP); + attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; - var attributes = Ember.merge({}, config.APP); - attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; + Ember.run(function () { + application = Application.create(attributes); + application.setupForTesting(); + application.injectTestHelpers(); + }); - Ember.run(function() { - application = Application.create(attributes); - application.setupForTesting(); - application.injectTestHelpers(); - }); + return application; +}; - return application; -} +export default startApp; diff --git a/tests/index.html b/tests/index.html index 8fea6fe..74929d2 100644 --- a/tests/index.html +++ b/tests/index.html @@ -1,33 +1,33 @@ - - - - Dummy Tests - - + + + + Dummy Tests + + - {{content-for 'head'}} - {{content-for 'test-head'}} + {{content-for "head"}} + {{content-for "test-head"}} - - - + + + - {{content-for 'head-footer'}} - {{content-for 'test-head-footer'}} - - + {{content-for "head-footer"}} + {{content-for "test-head-footer"}} + + - {{content-for 'body'}} - {{content-for 'test-body'}} - - - - - + {{content-for "body"}} + {{content-for "test-body"}} + + + + + - {{content-for 'body-footer'}} - {{content-for 'test-body-footer'}} - + {{content-for "body-footer"}} + {{content-for "test-body-footer"}} + diff --git a/tests/test-helper.js b/tests/test-helper.js index e6cfb70..1ac507d 100644 --- a/tests/test-helper.js +++ b/tests/test-helper.js @@ -1,6 +1,6 @@ -import resolver from './helpers/resolver'; +import resolver from "./helpers/resolver"; import { - setResolver -} from 'ember-qunit'; + setResolver +} from "ember-qunit"; setResolver(resolver); From 5d4de0598ed911971c58a483237ded647816f753 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Tue, 28 Jul 2015 15:01:50 -0400 Subject: [PATCH 09/48] Adds Ember Watson and updates README --- MIT-LICENSE | 21 --------------------- README.md | 2 +- package.json | 4 +++- 3 files changed, 4 insertions(+), 23 deletions(-) delete mode 100644 MIT-LICENSE diff --git a/MIT-LICENSE b/MIT-LICENSE deleted file mode 100644 index cec477c..0000000 --- a/MIT-LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013-2015 by Aleksey Zatvobor - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/README.md b/README.md index 14c1558..2a9cfd3 100644 --- a/README.md +++ b/README.md @@ -21,4 +21,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) #### License -`ember-couch` source code is released under MIT-License. Check [MIT-LICENSE](MIT-LICENSE) for more details. +`ember-couch` source code is released under MIT-License. Check [LICENSE.md](LICENSE.md) for more details. diff --git a/package.json b/package.json index 6d6d582..bd06cbc 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "author": "Valid USA", "license": "MIT", "devDependencies": { + "addon": "0.0.0", "broccoli-asset-rev": "^2.0.2", "broccoli-jscs": "0.0.22", "ember-cli": "1.13.1", @@ -38,7 +39,8 @@ "ember-disable-prototype-extensions": "^1.0.0", "ember-disable-proxy-controllers": "^1.0.0", "ember-export-application-global": "^1.0.2", - "ember-try": "0.0.6" + "ember-try": "0.0.6", + "ember-watson": "0.6.0" }, "keywords": [ "ember-addon", From c6ead73616ba2a9c0e90e1f110a060176c91e351 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Tue, 28 Jul 2015 15:10:39 -0400 Subject: [PATCH 10/48] Commit for Travis CI --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a9cfd3..8f93f7e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/validusa/ember-couch.svg?branch=2.0.0-alpha)](https://travis-ci.org/validusa/ember-couch)[![License](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE) +[![Build Status](https://travis-ci.org/ValidUSA/ember-couch.svg?branch=2.0.0-alpha)](https://travis-ci.org/ValidUSA/ember-couch)[![License](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE) #### ember-couch From 41338a7e068cb2197c55ae599f0da90ff62088d3 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Tue, 28 Jul 2015 16:35:58 -0400 Subject: [PATCH 11/48] Getting rid of views --- README.md | 11 +-------- addon/changes-feed.js | 5 ++-- tests/dummy/app/adapters/application.js | 2 +- tests/dummy/app/adapters/attachment.js | 2 +- tests/dummy/app/app.js | 4 +--- tests/dummy/app/routes/index.js | 10 ++++---- tests/dummy/app/templates/_create-issue.hbs | 14 +++++------ tests/dummy/app/templates/_issue-list.hbs | 26 ++++++++++----------- tests/dummy/app/templates/index.hbs | 10 ++++---- tests/dummy/config/environment.js | 7 ++++-- 10 files changed, 44 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 8f93f7e..c93d3a1 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,7 @@ #### ember-couch -An `ember-data` kit for Apache CouchDB. A collection of adapters to work with CouchDB documents, attachments, revisions, changes feed. - -Versions: - -* `v1.0.5` works with `ember 1.11.0` and `ember-data 1.0.0-beta.15` -* `v1.0.3` works with `ember 1.8.1` and `ember-data 1.0.0-beta.15` -* `v1.0.0` works with `ember 1.5.1` and `ember-data 1.0.0-beta.7` -* `v0.9.0` works with `ember 1.0.0` and `ember-data 1.0.0-beta.3` -* `v0.8.0` works with `ember 1.0.0` and `ember-data 1.0.0-beta.2` -* `v0.7.0` works with `ember rc.6.1` and `ember-data 0.13` +An `ember-data` kit for Apache CouchDB. A collection of adapters to work with CouchDB documents, attachments, revisions, changes feed. Based off of [ember-couchdb-kit by Aleksey Zatvobor](https://github.com/Zatvobor/ember-couchdb-kit). #### Contribution diff --git a/addon/changes-feed.js b/addon/changes-feed.js index 15274b8..c9742d8 100644 --- a/addon/changes-feed.js +++ b/addon/changes-feed.js @@ -15,9 +15,10 @@ export default Ember.ObjectProxy.extend({ return this._ajax.apply(this, arguments); }, fromTail: function (callback) { - var _this = this; + var _this = this, + url = Ember.String.fmt("%@%@/_changes?descending=true&limit=1", this._buildUrl(), this.get("db")); return Ember.$.ajax({ - url: "%@%@/_changes?descending=true&limit=1".fmt(this._buildUrl(), this.get("db")), + url: url, dataType: "json", success: function (data) { _this.set("since", data.last_seq); diff --git a/tests/dummy/app/adapters/application.js b/tests/dummy/app/adapters/application.js index 566572d..30d949c 100644 --- a/tests/dummy/app/adapters/application.js +++ b/tests/dummy/app/adapters/application.js @@ -3,5 +3,5 @@ import DocumentAdapter from "ember-couch/adapters/document"; export default DocumentAdapter.extend({ db: "boards", - host: App.Host + host: "http://localhost:5984" }); diff --git a/tests/dummy/app/adapters/attachment.js b/tests/dummy/app/adapters/attachment.js index f8038d5..d92a4ac 100644 --- a/tests/dummy/app/adapters/attachment.js +++ b/tests/dummy/app/adapters/attachment.js @@ -3,5 +3,5 @@ import AttachmentAdapter from "ember-couch/adapters/attachment"; export default AttachmentAdapter.extend({ db: "boards", - host: App.Host + host: "http://localhost:5984" }); diff --git a/tests/dummy/app/app.js b/tests/dummy/app/app.js index 5f06137..b4b270e 100644 --- a/tests/dummy/app/app.js +++ b/tests/dummy/app/app.js @@ -10,9 +10,7 @@ Ember.MODEL_FACTORY_INJECTIONS = true; App = Ember.Application.extend({ modulePrefix: config.modulePrefix, podModulePrefix: config.podModulePrefix, - Resolver: Resolver, - Boards: config.APP.Boards, - Host: config.APP.Host + Resolver: Resolver }); loadInitializers(App, config.modulePrefix); diff --git a/tests/dummy/app/routes/index.js b/tests/dummy/app/routes/index.js index d03621a..f37568f 100644 --- a/tests/dummy/app/routes/index.js +++ b/tests/dummy/app/routes/index.js @@ -3,6 +3,8 @@ import Ember from "ember"; import ChangesFeed from "ember-couch/changes-feed"; export default Ember.Route.extend({ + boards: ["common", "intermediate", "advanced"], + host: "http://localhost:5984", setupController: function (controller, model) { this._setupPositionHolders(); @@ -14,7 +16,7 @@ export default Ember.Route.extend({ this.render(); // link particular controller with its outlet var self = this; - App.Boards.forEach(function (label) { + self.get("boards").forEach(function (label) { self.render("board", { outlet: label, into: "index", @@ -25,7 +27,7 @@ export default Ember.Route.extend({ _setupPositionHolders: function () { var self = this; - App.Boards.forEach(function (type) { + self.get("boards").forEach(function (type) { self.get("store").find("position", type).then( function (position) { // set issues into appropriate controller through position model @@ -53,7 +55,7 @@ export default Ember.Route.extend({ }, position = ChangesFeed.create({ db: "boards", - host: App.Host, + host: this.get("host"), content: params }), self = this; @@ -83,7 +85,7 @@ export default Ember.Route.extend({ }, issue = ChangesFeed.create({ db: "boards", - host: App.Host, + host: this.get("host"), content: params }), self = this; diff --git a/tests/dummy/app/templates/_create-issue.hbs b/tests/dummy/app/templates/_create-issue.hbs index fbb33fe..fe3c7af 100644 --- a/tests/dummy/app/templates/_create-issue.hbs +++ b/tests/dummy/app/templates/_create-issue.hbs @@ -1,14 +1,14 @@ -{{#view App.NewIssueView}} - {{#if view.create}} +{{#eck-new-issue}} + {{#if create}}
- {{#view App.FocusedTextArea viewName="TextArea" class="form-control"}} - {{/view}} + {{#eck-focused-text-area viewName="TextArea" class="form-control"}} + {{/eck-focused-text-area}}
- {{#view App.CancelView}} + {{#eck-cancel}} - {{/view}} + {{/eck-cancel}} {{else}} {{/if}} -{{/view}} \ No newline at end of file +{{/eck-new-issue}} \ No newline at end of file diff --git a/tests/dummy/app/templates/_issue-list.hbs b/tests/dummy/app/templates/_issue-list.hbs index c0be984..8dea2f5 100644 --- a/tests/dummy/app/templates/_issue-list.hbs +++ b/tests/dummy/app/templates/_issue-list.hbs @@ -1,23 +1,23 @@
    - {{#each controller.content}} + {{#each content}} - {{#view App.IssueView contextBinding=this}} + {{#eck-issue context=this}}
  • - {{#if view.edit}} + {{#if edit}}
    - {{#view App.FocusedTextArea class="form-control" viewName="TextAreaEdit" valueBinding=view.context.text}} - {{/view}} + {{#eck-focused-text-area class="form-control" viewName="TextAreaEdit" value=context.text}} + {{/eck-focused-text-area}}
    {{#if attachments}} {{#each attachments}} {{file_name}} - {{#view App.DeleteAttachmentView contextBinding=this}} × {{/view}} + {{#eck-delete-attachment context=this}} × {{/eck-delete-attachment}} {{/each}} {{/if}} - {{#view App.AttachmentView contextBinding=this}} + {{#eck-attachment context=this}} - {{/view}} + {{/eck-attachment}} {{else}} {{text}} {{#if attachments}} @@ -28,18 +28,18 @@ {{/if}}
    - {{#view App.DeleteIssueView contextBinding=this}} + {{#eck-delete-issue context=this}} × - {{/view}} + {{/eck-delete-issue}}
    {{/if}}
  • - {{/view}} + {{/eck-issue}} {{else}} - {{#view App.IssueView draggable=false}} + {{#eck-issue draggable=false}}
  • Empty board...
  • - {{/view}} + {{/eck-issue}} {{/each}}
\ No newline at end of file diff --git a/tests/dummy/app/templates/index.hbs b/tests/dummy/app/templates/index.hbs index 6beb5d5..7f639c9 100644 --- a/tests/dummy/app/templates/index.hbs +++ b/tests/dummy/app/templates/index.hbs @@ -7,15 +7,17 @@

A simple app to demonstrate usage of Ember.js adapter for CouchDB. To run this demo locally:

-

$ git clone git@github.com:roundscope/ember-couch.git

-

$ cd ember-couch/example

-

$ npm install && grunt server

+

Create a local Couch database called "boards"

+

Create three blank documents: "common", "intermediate", and "advanced"

+

$ git clone git@github.com:ValidUSA/ember-couch.git

+

$ cd ember-couch

+

$ npm install && ember server

{{outlet "common"}}
diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js index 4eb8c2d..4d335d9 100644 --- a/tests/dummy/config/environment.js +++ b/tests/dummy/config/environment.js @@ -14,8 +14,7 @@ module.exports = function (environment) { }, APP: { - Boards: ["common", "intermediate", "advanced"], - Host: "http://localhost:5984" + } }; @@ -43,5 +42,9 @@ module.exports = function (environment) { } + ENV.contentSecurityPolicy: { + "connect-src": "'self' http://localhost:5984" + } + return ENV; }; From 155880e53575e921571081dcaaf650612402d7ba Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Tue, 28 Jul 2015 16:58:03 -0400 Subject: [PATCH 12/48] Took care of a few CSP errors and .fmt errors --- addon/changes-feed.js | 4 ++-- tests/dummy/config/environment.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/addon/changes-feed.js b/addon/changes-feed.js index c9742d8..fd549a6 100644 --- a/addon/changes-feed.js +++ b/addon/changes-feed.js @@ -72,7 +72,7 @@ export default Ember.ObjectProxy.extend({ var feed, params; feed = this.feed || "longpool"; params = this._makeFeedParams(); - return "%@%@/_changes?feed=%@%@".fmt(this._buildUrl(), this.get("db"), feed, params); + return Ember.String.fmt("%@%@/_changes?feed=%@%@", this._buildUrl(), this.get("db"), feed, params); }, _makeFeedParams: function () { var path, @@ -80,7 +80,7 @@ export default Ember.ObjectProxy.extend({ path = ""; ["include_docs", "limit", "descending", "heartbeat", "timeout", "filter", "filter_param", "style", "since"].forEach(function (param) { if (_this.get(param)) { - return path += "&%@=%@".fmt(param, _this.get(param)); + return path += Ember.String.fmt("&%@=%@", param, _this.get(param)); } }); return path; diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js index 4d335d9..affdd35 100644 --- a/tests/dummy/config/environment.js +++ b/tests/dummy/config/environment.js @@ -42,8 +42,9 @@ module.exports = function (environment) { } - ENV.contentSecurityPolicy: { - "connect-src": "'self' http://localhost:5984" + ENV.contentSecurityPolicy = { + "connect-src": "'self' http://localhost:5984", + "style-src": "'self' 'unsafe-inline'" } return ENV; From d4a140e30be48df33eeb851538ea635f50873f55 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Wed, 29 Jul 2015 09:52:57 -0400 Subject: [PATCH 13/48] Bower dependencies for dummy app --- bower.json | 4 ++++ ember-cli-build.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/bower.json b/bower.json index 8a8a75b..de38ac3 100644 --- a/bower.json +++ b/bower.json @@ -13,5 +13,9 @@ "jquery": "^1.11.1", "loader.js": "ember-cli/loader.js#3.2.0", "qunit": "~1.17.1" + }, + "devDependencies": { + "bootstrap": "~3.3.5", + "github-fork-ribbon-css": "~0.1.1" } } diff --git a/ember-cli-build.js b/ember-cli-build.js index 3518724..81a5bb0 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -16,5 +16,9 @@ module.exports = function(defaults) { behave. You most likely want to be modifying `./index.js` or app's build file */ + app.import(app.bowerDirectory + "/bootstrap/dist/css/bootstrap.min.css"); + app.import(app.bowerDirectory + "/github-fork-ribbon-css/gh-fork-ribbon.css"); + app.import(app.bowerDirectory + "/bootstrap/dist/js/bootstrap.min.js"); + return app.toTree(); }; From 3f6fc7d76ebd6b4d82c0354612bc306d0eb37413 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Wed, 29 Jul 2015 09:54:44 -0400 Subject: [PATCH 14/48] Changed template partial names to remove deprecation warnings. --- .../dummy/app/templates/{_create-issue.hbs => -create-issue.hbs} | 0 tests/dummy/app/templates/{_issue-list.hbs => -issue-list.hbs} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/dummy/app/templates/{_create-issue.hbs => -create-issue.hbs} (100%) rename tests/dummy/app/templates/{_issue-list.hbs => -issue-list.hbs} (100%) diff --git a/tests/dummy/app/templates/_create-issue.hbs b/tests/dummy/app/templates/-create-issue.hbs similarity index 100% rename from tests/dummy/app/templates/_create-issue.hbs rename to tests/dummy/app/templates/-create-issue.hbs diff --git a/tests/dummy/app/templates/_issue-list.hbs b/tests/dummy/app/templates/-issue-list.hbs similarity index 100% rename from tests/dummy/app/templates/_issue-list.hbs rename to tests/dummy/app/templates/-issue-list.hbs From 52a1c0962b58fad79be1043f41f640cb49ea7c37 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Wed, 29 Jul 2015 10:13:53 -0400 Subject: [PATCH 15/48] fixed deprecations for {{each}} in the dummy apps handlebars templates. --- tests/dummy/app/templates/-issue-list.hbs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/dummy/app/templates/-issue-list.hbs b/tests/dummy/app/templates/-issue-list.hbs index 8dea2f5..615f7b6 100644 --- a/tests/dummy/app/templates/-issue-list.hbs +++ b/tests/dummy/app/templates/-issue-list.hbs @@ -1,7 +1,7 @@
    - {{#each content}} + {{#each content as |cont|}} - {{#eck-issue context=this}} + {{#eck-issue context=cont}}
  • {{#if edit}}
    @@ -9,26 +9,26 @@ {{/eck-focused-text-area}}
    {{#if attachments}} - {{#each attachments}} - {{file_name}} - {{#eck-delete-attachment context=this}} × {{/eck-delete-attachment}} + {{#each attachments as |attachment|}} + {{attachment.file_name}} + {{#eck-delete-attachment context=attachment}} × {{/eck-delete-attachment}} {{/each}} {{/if}} - {{#eck-attachment context=this}} + {{#eck-attachment context=cont}} {{/eck-attachment}} {{else}} - {{text}} + {{cont.text}} {{#if attachments}} , attachments: - {{#each attachments}} - {{linkToAttachment this}} + {{#each attachments as |attachment|}} + {{linkToAttachment attachment}} {{/each}} {{/if}}
    - {{#eck-delete-issue context=this}} + {{#eck-delete-issue context=cont}} × {{/eck-delete-issue}}
    From b88d74c6b200045274176b978d96c2681c742c3d Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Wed, 29 Jul 2015 10:47:49 -0400 Subject: [PATCH 16/48] String formatting and couch commands --- addon/adapters/attachment.js | 5 +++-- addon/adapters/document.js | 17 +++++++++-------- addon/adapters/rev.js | 7 ++++--- addon/serializers/document.js | 5 +++-- addon/serializers/rev.js | 3 ++- addon/services/shared-store.js | 2 +- tests/dummy/app/templates/index.hbs | 7 +++++-- 7 files changed, 27 insertions(+), 19 deletions(-) diff --git a/addon/adapters/attachment.js b/addon/adapters/attachment.js index f3e4774..d6fb815 100644 --- a/addon/adapters/attachment.js +++ b/addon/adapters/attachment.js @@ -1,8 +1,9 @@ import Ember from "ember"; import DS from "ember-data"; +import sharedStore from "../services/shared-store"; export default DS.Adapter.extend({ - sharedStore: Ember.inject.service(), + sharedStore: sharedStore, find: function (store, type, id) { var sharedStore = this.get("sharedStore"); return new Ember.RSVP.Promise(function (resolve, reject) { @@ -27,7 +28,7 @@ export default DS.Adapter.extend({ }, createRecord: function (store, type, record) { var adapter, url; - url = "%@/%@?rev=%@".fmt(this.buildURL(), record.get("id"), record.get("rev")); + url = Ember.String.fmt("%@/%@?rev=%@", this.buildURL(), record.get("id"), record.get("rev")); adapter = this; return new Ember.RSVP.Promise(function (resolve, reject) { var data, request, diff --git a/addon/adapters/document.js b/addon/adapters/document.js index 00f97b9..807047a 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -1,8 +1,9 @@ import Ember from "ember"; import DS from "ember-data"; +import sharedStore from "../services/shared-store"; export default DS.Adapter.extend({ - sharedStore: Ember.inject.service(), + sharedStore: sharedStore, defaultSerializer: "_default", customTypeLookup: false, typeViewName: "all", @@ -25,7 +26,7 @@ export default DS.Adapter.extend({ return url; }, ajax: function (url, type, normalizeResponse, hash) { - return this._ajax("%@/%@".fmt(this.buildURL(), url || ""), type, normalizeResponse, hash); + return this._ajax(Ember.String.fmt("%@/%@", this.buildURL(), url || ""), type, normalizeResponse, hash); }, _ajax: function (url, type, normalizeResponse, hash) { var adapter; @@ -100,7 +101,7 @@ export default DS.Adapter.extend({ _ref = id.split("/").slice(0, 2); _id = _ref[0]; _rev = _ref[1]; - url = "%@?rev=%@".fmt(_id, _rev); + url = Ember.String.fmt("%@?rev=%@", _id, _rev); normalizeResponse = function (data) { var _modelJson; this._normalizeRevision(data); @@ -126,8 +127,8 @@ export default DS.Adapter.extend({ _ref = id.split("/").slice(0, 2); _id = _ref[0]; _rev = _ref[1]; - url = "%@?rev=%@".fmt(_id, _rev); - url = "%@/%@".fmt(_this.buildURL(), url); + url = Ember.String.fmt("%@?rev=%@", _id, _rev); + url = Ember.String.fmt("%@/%@", _this.buildURL(), url); hash.url = url; hash.type = "GET"; hash.dataType = "json"; @@ -181,7 +182,7 @@ export default DS.Adapter.extend({ json.total_rows = data.total_rows; return json; }; - return this.ajax("_design/%@/_view/%@".fmt(designDoc, query.viewName), "GET", normalizeResponse, { + return this.ajax(Ember.String.fmt("_design/%@/_view/%@", designDoc, query.viewName), "GET", normalizeResponse, { context: this, data: query.options }); @@ -204,7 +205,7 @@ export default DS.Adapter.extend({ include_docs: true, key: "\"" + typeString + "\"" }; - return this.ajax("_design/%@/_view/%@".fmt(designDoc, typeViewName), "GET", normalizeResponse, { + return this.ajax(Ember.String.fmt("_design/%@/_view/%@", designDoc, typeViewName), "GET", normalizeResponse, { data: data }); }, @@ -228,7 +229,7 @@ export default DS.Adapter.extend({ return this._push(store, type, snapshot, json); }, deleteRecord: function (store, type, snapshot) { - return this.ajax("%@?rev=%@".fmt(snapshot.id, snapshot.attr("rev")), "DELETE", (function () {}), {}); + return this.ajax(Ember.String.fmt("%@?rev=%@", snapshot.id, snapshot.attr("rev")), "DELETE", (function () {}), {}); }, _updateAttachmnets: function (snapshot, json) { var _attachments, sharedStore; diff --git a/addon/adapters/rev.js b/addon/adapters/rev.js index c61e4ba..632566d 100644 --- a/addon/adapters/rev.js +++ b/addon/adapters/rev.js @@ -1,17 +1,18 @@ import Ember from "ember"; import DS from "ember-data"; +import sharedStore from "../services/shared-store"; export default DS.Adapter.extend({ - sharedStore: Ember.inject.service(), + sharedStore: sharedStore, find: function (store, type, id) { - return this.ajax("%@?revs_info=true".fmt(id.split("/")[0]), "GET", { + return this.ajax(Ember.String.fmt("%@?revs_info=true", id.split("/")[0]), "GET", { context: this }, id); }, updateRecord: function (store, type, record) {}, deleteRecord: function (store, type, record) {}, ajax: function (url, type, hash, id) { - return this._ajax("%@/%@".fmt(this.buildURL(), url || ""), type, hash, id); + return this._ajax(Ember.String.fmt("%@/%@", this.buildURL(), url || ""), type, hash, id); }, _ajax: function (url, type, hash, id) { var sharedStore = this.get("sharedStore"); diff --git a/addon/serializers/document.js b/addon/serializers/document.js index 37d2332..4f0b6e6 100644 --- a/addon/serializers/document.js +++ b/addon/serializers/document.js @@ -1,8 +1,9 @@ import Ember from "ember"; import DS from "ember-data"; +import sharedStore from "../services/shared-store"; export default DS.RESTSerializer.extend({ - sharedStore: Ember.inject.service(), + sharedStore: sharedStore, primaryKey: "_id", normalize: function (type, hash, prop) { this.normalizeId(hash); @@ -35,7 +36,7 @@ export default DS.RESTSerializer.extend({ return this._super(snapshot, options); }, addHistoryId: function (hash) { - hash.history = "%@/history".fmt(hash.id); + hash.history = Ember.String.fmt("%@/history", hash.id); return hash.history; }, normalizeAttachments: function (attachments, type, hash) { diff --git a/addon/serializers/rev.js b/addon/serializers/rev.js index 2c63b7f..668daef 100644 --- a/addon/serializers/rev.js +++ b/addon/serializers/rev.js @@ -1,8 +1,9 @@ import Ember from "ember"; import DS from "ember-data"; +import sharedStore from "../services/shared-store"; export default DS.RESTSerializer.extend({ - sharedStore: Ember.inject.service(), + sharedStore: sharedStore, primaryKey: "id", normalize: function (type, hash, prop) { this.normalizeRelationships(type, hash); diff --git a/addon/services/shared-store.js b/addon/services/shared-store.js index 49e2f0e..3cc9b65 100644 --- a/addon/services/shared-store.js +++ b/addon/services/shared-store.js @@ -18,7 +18,7 @@ export default Ember.Service.extend({ mapRevIds: function (type, key) { var _this = this; return this.get(type, key)._revs_info.map(function (_rev) { - return "%@/%@".fmt(_this.get(type, key)._id, _rev.rev); + return Ember.String.fmt("%@/%@", _this.get(type, key)._id, _rev.rev); }); }, stopAll: function () { diff --git a/tests/dummy/app/templates/index.hbs b/tests/dummy/app/templates/index.hbs index 7f639c9..2436a9b 100644 --- a/tests/dummy/app/templates/index.hbs +++ b/tests/dummy/app/templates/index.hbs @@ -13,8 +13,11 @@

    A simple app to demonstrate usage of Ember.js adapter for CouchDB. To run this demo locally:

    -

    Create a local Couch database called "boards"

    -

    Create three blank documents: "common", "intermediate", and "advanced"

    +

    $ curl -X PUT http://localhost:5984/boards

    +

    $ curl -X PUT http://localhost:5984/boards/_design/issues -H 'Content-Type: application/json' -d '{"_id": "_design/issues","language": "javascript","filters": {"only_positions": "function(doc, req) { if(doc.type == \"position\") { return true; } }","issue": "function(doc, req) {if(doc.type == \"issue\") { return true; } }"}}'

    +

    $ curl -X PUT http://localhost:5984/boards/_design/issues -H 'Content-Type: application/json' -d '{"_id": "common"}'

    +

    $ curl -X PUT http://localhost:5984/boards/_design/issues -H 'Content-Type: application/json' -d '{"_id": "intermediate"}'

    +

    $ curl -X PUT http://localhost:5984/boards/_design/issues -H 'Content-Type: application/json' -d '{"_id": "advanced"}'

    $ git clone git@github.com:ValidUSA/ember-couch.git

    $ cd ember-couch

    $ npm install && ember server

    From fb937c96631f5a6c217f9e6e94db1eed7b228167 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Wed, 29 Jul 2015 10:53:56 -0400 Subject: [PATCH 17/48] Fixes the use model over content in controller deprecation. --- tests/dummy/app/controllers/index.js | 8 ++++---- tests/dummy/app/templates/-issue-list.hbs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js index e4a6196..96da2c5 100644 --- a/tests/dummy/app/controllers/index.js +++ b/tests/dummy/app/controllers/index.js @@ -2,7 +2,7 @@ import Ember from "ember"; export default Ember.Controller.extend({ - content: Ember.computed.alias("position.issues"), + model: Ember.computed.alias("position.issues"), actions: { createIssue: function (text) { @@ -72,12 +72,12 @@ export default Ember.Controller.extend({ }, dropIssue: function (viewController, viewModel, thisModel) { - var position = this.get("content").toArray().indexOf(thisModel), + var position = this.get("model").toArray().indexOf(thisModel), self = this; if (position === -1) { position = 0; } - viewController.get("content").removeObject(viewModel); + viewController.get("model").removeObject(viewModel); if (viewController.name !== this.name) { viewController.get("position").save().then(function () { @@ -85,7 +85,7 @@ export default Ember.Controller.extend({ }); } - this.get("content").insertAt(position, viewModel); + this.get("model").insertAt(position, viewModel); this.get("position").save(); } } diff --git a/tests/dummy/app/templates/-issue-list.hbs b/tests/dummy/app/templates/-issue-list.hbs index 615f7b6..3189351 100644 --- a/tests/dummy/app/templates/-issue-list.hbs +++ b/tests/dummy/app/templates/-issue-list.hbs @@ -1,7 +1,7 @@
      - {{#each content as |cont|}} + {{#each model as |content|}} - {{#eck-issue context=cont}} + {{#eck-issue context=content}}
    • {{#if edit}}
      @@ -15,11 +15,11 @@ {{/each}} {{/if}} - {{#eck-attachment context=cont}} + {{#eck-attachment context=content}} {{/eck-attachment}} {{else}} - {{cont.text}} + {{content.text}} {{#if attachments}} , attachments: {{#each attachments as |attachment|}} @@ -28,7 +28,7 @@ {{/if}}
      - {{#eck-delete-issue context=cont}} + {{#eck-delete-issue context=content}} × {{/eck-delete-issue}}
      From 00c459ad2745adbdded901c8099fb025182b70af Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Wed, 29 Jul 2015 11:43:03 -0400 Subject: [PATCH 18/48] Changing extract hooks --- addon/adapters/document.js | 2 +- addon/serializers/document.js | 8 +++++--- addon/serializers/rev.js | 4 ++-- tests/dummy/app/templates/index.hbs | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/addon/adapters/document.js b/addon/adapters/document.js index 807047a..e638363 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -155,7 +155,7 @@ export default DS.Adapter.extend({ var json, _this = this; json = {}; - json[Ember.String.pluralize(type.modelName)] = data.rows.getEach("doc").map(function (doc) { + json[Ember.String.pluralize(type.modelName)] = data.rows.map(function (doc) { return _this._normalizeRevision(doc); }); return json; diff --git a/addon/serializers/document.js b/addon/serializers/document.js index 4f0b6e6..daa7c53 100644 --- a/addon/serializers/document.js +++ b/addon/serializers/document.js @@ -21,16 +21,18 @@ export default DS.RESTSerializer.extend({ this.applyTransforms(type, hash); return hash; }, - extractSingle: function (store, type, payload, id, requestType) { + normalizeSingleResponse: function (store, type, payload, id, requestType) { return this._super(store, type, payload, id, requestType); }, extractMeta: function (store, type, payload) { + var result = {}; if (payload && payload.total_rows) { - store.setMetadataFor(type, { + result = { total_rows: payload.total_rows - }); + }; delete payload.total_rows; } + return result; }, serialize: function (snapshot, options) { return this._super(snapshot, options); diff --git a/addon/serializers/rev.js b/addon/serializers/rev.js index 668daef..2c13b30 100644 --- a/addon/serializers/rev.js +++ b/addon/serializers/rev.js @@ -6,13 +6,13 @@ export default DS.RESTSerializer.extend({ sharedStore: sharedStore, primaryKey: "id", normalize: function (type, hash, prop) { - this.normalizeRelationships(type, hash); + this.extractRelationships(type, hash); return this._super(type, hash, prop); }, extractId: function (type, hash) { return hash._id || hash.id; }, - normalizeRelationships: function (type, hash) { + extractRelationships: function (type, hash) { var sharedStore = this.get("sharedStore"); return type.eachRelationship((function (key, relationship) { if (relationship.kind === "belongsTo") { diff --git a/tests/dummy/app/templates/index.hbs b/tests/dummy/app/templates/index.hbs index 2436a9b..5084a12 100644 --- a/tests/dummy/app/templates/index.hbs +++ b/tests/dummy/app/templates/index.hbs @@ -20,7 +20,7 @@

      $ curl -X PUT http://localhost:5984/boards/_design/issues -H 'Content-Type: application/json' -d '{"_id": "advanced"}'

      $ git clone git@github.com:ValidUSA/ember-couch.git

      $ cd ember-couch

      -

      $ npm install && ember server

      +

      $ npm install && bower install && ember server

      {{outlet "common"}}
      From a2a018a3d88c95a8edc7023d252cdb15455decb1 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Wed, 29 Jul 2015 14:29:20 -0400 Subject: [PATCH 19/48] Getting ready for new serializer format. --- addon/serializers/attachment.js | 1 + addon/serializers/document.js | 1 + addon/serializers/rev.js | 1 + 3 files changed, 3 insertions(+) diff --git a/addon/serializers/attachment.js b/addon/serializers/attachment.js index 5d6b903..ddf34c6 100644 --- a/addon/serializers/attachment.js +++ b/addon/serializers/attachment.js @@ -1,6 +1,7 @@ import DS from "ember-data"; export default DS.RESTSerializer.extend({ + isNewSerializerAPI: true, primaryKey: "id", normalize: function (type, hash) { var rev, self; diff --git a/addon/serializers/document.js b/addon/serializers/document.js index daa7c53..015f126 100644 --- a/addon/serializers/document.js +++ b/addon/serializers/document.js @@ -3,6 +3,7 @@ import DS from "ember-data"; import sharedStore from "../services/shared-store"; export default DS.RESTSerializer.extend({ + isNewSerializerAPI: true, sharedStore: sharedStore, primaryKey: "_id", normalize: function (type, hash, prop) { diff --git a/addon/serializers/rev.js b/addon/serializers/rev.js index 2c13b30..6e90cbd 100644 --- a/addon/serializers/rev.js +++ b/addon/serializers/rev.js @@ -3,6 +3,7 @@ import DS from "ember-data"; import sharedStore from "../services/shared-store"; export default DS.RESTSerializer.extend({ + isNewSerializerAPI: true, sharedStore: sharedStore, primaryKey: "id", normalize: function (type, hash, prop) { From 47333e61609bd3aa00dec0ae5701e65c499626cc Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Wed, 29 Jul 2015 16:29:23 -0400 Subject: [PATCH 20/48] Fix find. --- addon/adapters/attachment.js | 5 +++++ addon/adapters/document.js | 5 +++++ addon/adapters/rev.js | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/addon/adapters/attachment.js b/addon/adapters/attachment.js index d6fb815..07bbd5d 100644 --- a/addon/adapters/attachment.js +++ b/addon/adapters/attachment.js @@ -4,7 +4,12 @@ import sharedStore from "../services/shared-store"; export default DS.Adapter.extend({ sharedStore: sharedStore, + // DEPRECATED + // Find has been deprecated as of Ember Data 1.13. This has been left for backwards compatibility. find: function (store, type, id) { + return this.findRecord(store, type, id); + }, + findRecord: function (store, type, id) { var sharedStore = this.get("sharedStore"); return new Ember.RSVP.Promise(function (resolve, reject) { return Ember.run(null, resolve, { diff --git a/addon/adapters/document.js b/addon/adapters/document.js index e638363..5c7ef18 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -81,7 +81,12 @@ export default DS.Adapter.extend({ shouldCommit: function (snapshot, relationships) { return this._super.apply(arguments); }, + // DEPRECATED + // Find has been deprecated as of Ember Data 1.13. This has been left for backwards compatibility. find: function (store, type, id) { + return this.findRecord(store, type, id); + }, + findRecord: function (store, type, id) { var normalizeResponse; if (this._checkForRevision(id)) { return this.findWithRev(store, type, id); diff --git a/addon/adapters/rev.js b/addon/adapters/rev.js index 632566d..d914960 100644 --- a/addon/adapters/rev.js +++ b/addon/adapters/rev.js @@ -4,7 +4,12 @@ import sharedStore from "../services/shared-store"; export default DS.Adapter.extend({ sharedStore: sharedStore, + // DEPRECATED + // Find has been deprecated as of Ember Data 1.13. This has been left for backwards compatibility. find: function (store, type, id) { + return this.findRecord(store, type, id); + }, + findRecord: function (store, type, id) { return this.ajax(Ember.String.fmt("%@?revs_info=true", id.split("/")[0]), "GET", { context: this }, id); From edce304d8cd60252ed43a53178b03cccca12c444 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Wed, 29 Jul 2015 16:32:43 -0400 Subject: [PATCH 21/48] Fixes the add issue, cancel, and save buttons. --- tests/dummy/app/components/eck-cancel.js | 8 +++++++- .../dummy/app/components/eck-focused-text-area.js | 10 +++++++--- tests/dummy/app/components/eck-new-issue.js | 13 +++++++++++-- tests/dummy/app/controllers/index.js | 1 - tests/dummy/app/templates/-create-issue.hbs | 14 -------------- tests/dummy/app/templates/board.hbs | 2 +- .../app/templates/components/eck-new-issue.hbs | 12 ++++++++++++ 7 files changed, 38 insertions(+), 22 deletions(-) delete mode 100644 tests/dummy/app/templates/-create-issue.hbs create mode 100644 tests/dummy/app/templates/components/eck-new-issue.hbs diff --git a/tests/dummy/app/components/eck-cancel.js b/tests/dummy/app/components/eck-cancel.js index 0e4de51..0bcdf2e 100644 --- a/tests/dummy/app/components/eck-cancel.js +++ b/tests/dummy/app/components/eck-cancel.js @@ -5,6 +5,12 @@ export default Ember.Component.extend({ click: function (event) { event.preventDefault(); - this.set("parentView.create", false); + this.send("cancelIssueCreate"); + }, + actions: { + cancelIssueCreate: function () { + this.set("action", "setCreateIssue"); + this.sendAction("action", false); + } } }); diff --git a/tests/dummy/app/components/eck-focused-text-area.js b/tests/dummy/app/components/eck-focused-text-area.js index fb7dd3b..2dc13f2 100644 --- a/tests/dummy/app/components/eck-focused-text-area.js +++ b/tests/dummy/app/components/eck-focused-text-area.js @@ -1,7 +1,11 @@ import Ember from "ember"; export default Ember.Component.extend({ - elementDidChange: function () { - this.$().focus(); - }.observes("element") + tagName: "textarea" + // elementDidChange: function () { + // this.$().focus(); + // }.observes("element") + // elementDidChange: Ember.observer("element", function () { + // this.$().focus(); + // }) }); diff --git a/tests/dummy/app/components/eck-new-issue.js b/tests/dummy/app/components/eck-new-issue.js index 6c9fa86..81eb86c 100644 --- a/tests/dummy/app/components/eck-new-issue.js +++ b/tests/dummy/app/components/eck-new-issue.js @@ -20,11 +20,20 @@ export default Ember.Component.extend({ var text; event.preventDefault(); if (this.get("create")) { - text = this.get("TextArea.value"); + text = this.get("childViews")[0].element.value; if (!Ember.isEmpty(text)) { - this.get("controller").send("createIssue", text); + this.send("createNewIssue", text); } } this.toggleProperty("create"); + }, + actions: { + createNewIssue: function (text) { + this.set("action", "createIssue"); + this.sendAction("action", text); + }, + setCreateIssue: function (val) { + this.set("create", val); + } } }); diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js index 96da2c5..fa45bcc 100644 --- a/tests/dummy/app/controllers/index.js +++ b/tests/dummy/app/controllers/index.js @@ -3,7 +3,6 @@ import Ember from "ember"; export default Ember.Controller.extend({ model: Ember.computed.alias("position.issues"), - actions: { createIssue: function (text) { var self = this, diff --git a/tests/dummy/app/templates/-create-issue.hbs b/tests/dummy/app/templates/-create-issue.hbs deleted file mode 100644 index fe3c7af..0000000 --- a/tests/dummy/app/templates/-create-issue.hbs +++ /dev/null @@ -1,14 +0,0 @@ -{{#eck-new-issue}} - {{#if create}} -
      - {{#eck-focused-text-area viewName="TextArea" class="form-control"}} - {{/eck-focused-text-area}} -
      - - {{#eck-cancel}} - - {{/eck-cancel}} - {{else}} - - {{/if}} -{{/eck-new-issue}} \ No newline at end of file diff --git a/tests/dummy/app/templates/board.hbs b/tests/dummy/app/templates/board.hbs index 6e5fa7c..71d190e 100644 --- a/tests/dummy/app/templates/board.hbs +++ b/tests/dummy/app/templates/board.hbs @@ -3,7 +3,7 @@

      {{name}}

      - {{partial "createIssue"}} + {{#eck-new-issue}}{{/eck-new-issue}}
      {{partial "issueList"}}
    \ No newline at end of file diff --git a/tests/dummy/app/templates/components/eck-new-issue.hbs b/tests/dummy/app/templates/components/eck-new-issue.hbs new file mode 100644 index 0000000..86c45aa --- /dev/null +++ b/tests/dummy/app/templates/components/eck-new-issue.hbs @@ -0,0 +1,12 @@ +{{#if create}} +
    + {{#eck-focused-text-area viewName="TextArea" class="form-control"}} + {{/eck-focused-text-area}} +
    + + {{#eck-cancel}} + + {{/eck-cancel}} +{{else}} + +{{/if}} From 36316eb1257962ba8438cc40187cc265a86607f3 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Thu, 30 Jul 2015 09:50:01 -0400 Subject: [PATCH 22/48] Fixed auto focusing of the textareas when inserted. --- tests/dummy/app/components/eck-focused-text-area.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/dummy/app/components/eck-focused-text-area.js b/tests/dummy/app/components/eck-focused-text-area.js index 2dc13f2..027fe9f 100644 --- a/tests/dummy/app/components/eck-focused-text-area.js +++ b/tests/dummy/app/components/eck-focused-text-area.js @@ -1,11 +1,8 @@ import Ember from "ember"; export default Ember.Component.extend({ - tagName: "textarea" - // elementDidChange: function () { - // this.$().focus(); - // }.observes("element") - // elementDidChange: Ember.observer("element", function () { - // this.$().focus(); - // }) + tagName: "textarea", + didInsertElement: function () { + this.$().focus(); + } }); From 4917c7882bf2b5c3c972c14037fbc8dcf5f6316a Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Thu, 30 Jul 2015 09:53:55 -0400 Subject: [PATCH 23/48] minor fixes --- addon/adapters/document.js | 2 +- addon/serializers/document.js | 7 ++++--- tests/dummy/app/components/eck-attachment.js | 2 +- tests/dummy/app/components/eck-new-issue.js | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/addon/adapters/document.js b/addon/adapters/document.js index 5c7ef18..c62ff79 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -263,7 +263,7 @@ export default DS.Adapter.extend({ var id, method, normalizeResponse; id = snapshot.id || ""; method = snapshot.id ? "PUT" : "POST"; - if (snapshot.attr("rev")) { + if (snapshot._attributes.rev) { json._rev = snapshot.attr("rev"); } normalizeResponse = function (data) { diff --git a/addon/serializers/document.js b/addon/serializers/document.js index 015f126..a4b6973 100644 --- a/addon/serializers/document.js +++ b/addon/serializers/document.js @@ -103,15 +103,16 @@ export default DS.RESTSerializer.extend({ } }, serializeHasMany: function (snapshot, json, relationship) { - var attribute, key, relationshipType; + var attribute, key, relationshipType, keyArray; attribute = relationship.options.attribute || "id"; key = relationship.key; - relationshipType = snapshot.type.determineRelationshipType(relationship); + relationshipType = snapshot.type.determineRelationshipType(relationship, this.get("store")); switch (relationshipType) { case "manyToNone": case "manyToMany": case "manyToOne": - json[key] = snapshot.hasMany(key).mapBy(attribute); + keyArray = Ember.A(snapshot.hasMany(key)); + json[key] = keyArray.mapBy(attribute); return json[key]; } } diff --git a/tests/dummy/app/components/eck-attachment.js b/tests/dummy/app/components/eck-attachment.js index c5abec3..c1ce1e7 100644 --- a/tests/dummy/app/components/eck-attachment.js +++ b/tests/dummy/app/components/eck-attachment.js @@ -3,7 +3,7 @@ import Ember from "ember"; export default Ember.Component.extend({ tagName: "input", attributeBindings: ["style", "type", "multiple"], - style: "display:none", + style: Ember.Handlebars.SafeString("display:none"), type: "file", multiple: true, diff --git a/tests/dummy/app/components/eck-new-issue.js b/tests/dummy/app/components/eck-new-issue.js index 81eb86c..7ceba3d 100644 --- a/tests/dummy/app/components/eck-new-issue.js +++ b/tests/dummy/app/components/eck-new-issue.js @@ -4,7 +4,7 @@ export default Ember.Component.extend({ tagName: "form", create: false, attributeBindings: ["style"], - style: "display:inline", + style: Ember.Handlebars.SafeString("display:inline"), submit: function (event) { this._save(event); From fa9ae2334b3c9b1b8cbd48a271ca2e136c1f9573 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Thu, 30 Jul 2015 16:05:43 -0400 Subject: [PATCH 24/48] Tweaks to serializer and adapter. Plus, dummy model fix. --- addon/adapters/document.js | 2 +- addon/serializers/document.js | 5 +++-- tests/dummy/app/models/position.js | 4 +++- tests/dummy/app/routes/index.js | 4 ++-- tests/dummy/app/templates/board.hbs | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/addon/adapters/document.js b/addon/adapters/document.js index c62ff79..bb5ca1a 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -226,7 +226,7 @@ export default DS.Adapter.extend({ associations: true, includeId: true }); - snapData = snapshot.record._data; + snapData = snapshot.record; if ("attachments" in snapData ? snapData.attachments.length > 0 : void 0) { this._updateAttachmnets(snapshot, json); } diff --git a/addon/serializers/document.js b/addon/serializers/document.js index a4b6973..07144c7 100644 --- a/addon/serializers/document.js +++ b/addon/serializers/document.js @@ -5,7 +5,7 @@ import sharedStore from "../services/shared-store"; export default DS.RESTSerializer.extend({ isNewSerializerAPI: true, sharedStore: sharedStore, - primaryKey: "_id", + primaryKey: "id", normalize: function (type, hash, prop) { this.normalizeId(hash); this.normalizeAttachments(hash._attachments, type.modelName, hash); @@ -20,7 +20,8 @@ export default DS.RESTSerializer.extend({ return hash; } this.applyTransforms(type, hash); - return hash; + // return hash; + return this._super(type, hash, prop); }, normalizeSingleResponse: function (store, type, payload, id, requestType) { return this._super(store, type, payload, id, requestType); diff --git a/tests/dummy/app/models/position.js b/tests/dummy/app/models/position.js index c2a9c15..35d1eff 100644 --- a/tests/dummy/app/models/position.js +++ b/tests/dummy/app/models/position.js @@ -1,7 +1,9 @@ import DS from "ember-data"; export default DS.Model.extend({ - issues: DS.hasMany("issue"), + issues: DS.hasMany("issue", { + async: false + }), type: DS.attr("string", { defaultValue: "position" }) diff --git a/tests/dummy/app/routes/index.js b/tests/dummy/app/routes/index.js index f37568f..ec9032a 100644 --- a/tests/dummy/app/routes/index.js +++ b/tests/dummy/app/routes/index.js @@ -28,7 +28,7 @@ export default Ember.Route.extend({ _setupPositionHolders: function () { var self = this; self.get("boards").forEach(function (type) { - self.get("store").find("position", type).then( + self.get("store").findRecord("position", type).then( function (position) { // set issues into appropriate controller through position model self.controllerFor(type).set("position", position); @@ -100,7 +100,7 @@ export default Ember.Route.extend({ var self = this; // apply received updates data.forEach(function (obj) { - var issue = self.get("store").all("issue").toArray().find(function (i) { + var issue = self.get("store").peekAll("issue").toArray().find(function (i) { return i.get("id") === obj.doc._id; }); if (issue !== undefined && issue.get("_data.rev") !== obj.doc._rev) { diff --git a/tests/dummy/app/templates/board.hbs b/tests/dummy/app/templates/board.hbs index 71d190e..148b2bb 100644 --- a/tests/dummy/app/templates/board.hbs +++ b/tests/dummy/app/templates/board.hbs @@ -3,7 +3,7 @@

    {{name}}

- {{#eck-new-issue}}{{/eck-new-issue}} + {{eck-new-issue}}
{{partial "issueList"}} \ No newline at end of file From ecdec5706d939bb3e7a72ca2b656bf3d0deb02d2 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Thu, 30 Jul 2015 16:38:51 -0400 Subject: [PATCH 25/48] Added an action in the delete component to send delete to the controller. --- tests/dummy/app/components/eck-delete-issue.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/dummy/app/components/eck-delete-issue.js b/tests/dummy/app/components/eck-delete-issue.js index 72e3000..c89b9de 100644 --- a/tests/dummy/app/components/eck-delete-issue.js +++ b/tests/dummy/app/components/eck-delete-issue.js @@ -7,5 +7,11 @@ export default Ember.Component.extend({ click: function (event) { event.preventDefault(); this.get("controller").send("deleteIssue", this.get("context")); + }, + actions: { + deleteIssue: function (context) { + this.set("action", "deleteIssue"); + this.sendAction("action", context); + } } }); From 9b18f4351a84fc054a426e2464ade37323feab9a Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Thu, 30 Jul 2015 16:56:08 -0400 Subject: [PATCH 26/48] More fixes. Soon, they work will be done. --- addon/adapters/document.js | 2 +- tests/dummy/app/components/eck-delete-issue.js | 2 +- tests/dummy/app/models/issue.js | 3 ++- tests/dummy/app/models/position.js | 3 ++- tests/dummy/app/templates/-issue-list.hbs | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/addon/adapters/document.js b/addon/adapters/document.js index bb5ca1a..0e0b562 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -30,7 +30,7 @@ export default DS.Adapter.extend({ }, _ajax: function (url, type, normalizeResponse, hash) { var adapter; - if (hash === null) { + if (!hash) { hash = {}; } adapter = this; diff --git a/tests/dummy/app/components/eck-delete-issue.js b/tests/dummy/app/components/eck-delete-issue.js index c89b9de..5d4f40d 100644 --- a/tests/dummy/app/components/eck-delete-issue.js +++ b/tests/dummy/app/components/eck-delete-issue.js @@ -6,7 +6,7 @@ export default Ember.Component.extend({ click: function (event) { event.preventDefault(); - this.get("controller").send("deleteIssue", this.get("context")); + this.get("controller").send("deleteIssue", this.get("value")); }, actions: { deleteIssue: function (context) { diff --git a/tests/dummy/app/models/issue.js b/tests/dummy/app/models/issue.js index a694672..25108bb 100644 --- a/tests/dummy/app/models/issue.js +++ b/tests/dummy/app/models/issue.js @@ -5,5 +5,6 @@ export default DS.Model.extend({ type: DS.attr("string", { defaultValue: "issue" }), - attachments: DS.hasMany("attachment") + attachments: DS.hasMany("attachment"), + rev: DS.attr("string") }); diff --git a/tests/dummy/app/models/position.js b/tests/dummy/app/models/position.js index 35d1eff..ad6755b 100644 --- a/tests/dummy/app/models/position.js +++ b/tests/dummy/app/models/position.js @@ -6,5 +6,6 @@ export default DS.Model.extend({ }), type: DS.attr("string", { defaultValue: "position" - }) + }), + rev: DS.attr("string") }); diff --git a/tests/dummy/app/templates/-issue-list.hbs b/tests/dummy/app/templates/-issue-list.hbs index 3189351..254b278 100644 --- a/tests/dummy/app/templates/-issue-list.hbs +++ b/tests/dummy/app/templates/-issue-list.hbs @@ -28,7 +28,7 @@ {{/if}}
- {{#eck-delete-issue context=content}} + {{#eck-delete-issue value=content}} × {{/eck-delete-issue}}
From e7c50e786df90f75d791783ec1f54aca41170b7a Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Fri, 31 Jul 2015 12:28:04 -0400 Subject: [PATCH 27/48] Addon and dummy updates --- addon/adapters/document.js | 6 +++--- addon/serializers/document.js | 20 +++++++++++++++++-- .../app/components/eck-delete-attachment.js | 9 ++++++++- .../dummy/app/components/eck-delete-issue.js | 6 +++--- tests/dummy/app/components/eck-issue.js | 15 ++++++++++++-- tests/dummy/app/models/position.js | 2 +- tests/dummy/app/templates/-issue-list.hbs | 8 ++++---- 7 files changed, 50 insertions(+), 16 deletions(-) diff --git a/addon/adapters/document.js b/addon/adapters/document.js index 0e0b562..2ac1f82 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -72,9 +72,9 @@ export default DS.Adapter.extend({ }); }, _normalizeRevision: function (json) { - if (json && json._rev) { - json.rev = json._rev; - delete json._rev; + if (json && json.doc && json.doc._rev) { + json.rev = json.doc._rev; + delete json.doc._rev; } return json; }, diff --git a/addon/serializers/document.js b/addon/serializers/document.js index 07144c7..8c1f7b9 100644 --- a/addon/serializers/document.js +++ b/addon/serializers/document.js @@ -10,6 +10,7 @@ export default DS.RESTSerializer.extend({ this.normalizeId(hash); this.normalizeAttachments(hash._attachments, type.modelName, hash); this.addHistoryId(hash); + this.normalizeDoc(hash); this.normalizeUsingDeclaredMapping(type, hash); this.normalizeAttributes(type, hash); this.normalizeRelationships(type, hash); @@ -20,7 +21,6 @@ export default DS.RESTSerializer.extend({ return hash; } this.applyTransforms(type, hash); - // return hash; return this._super(type, hash, prop); }, normalizeSingleResponse: function (store, type, payload, id, requestType) { @@ -71,7 +71,13 @@ export default DS.RESTSerializer.extend({ return hash.attachments; }, normalizeId: function (hash) { - hash.id = hash._id || hash.id; + hash.id = hash.doc && hash.doc._id || hash._id || hash.id; + if (hash.doc && hash.doc._id) { + delete hash.doc._id; + } + if (hash._id) { + delete hash._id; + } return hash.id; }, normalizeRelationships: function (type, hash) { @@ -89,6 +95,16 @@ export default DS.RESTSerializer.extend({ }), this); } }, + normalizeDoc: function (hash) { + var k; + if (hash.doc) { + Ember.$.each(hash.doc, function (k, v) { + hash[k] = v; + }); + delete hash.doc; + } + return hash; + }, serializeBelongsTo: function (snapshot, json, relationship) { var attribute, belongsTo, key; attribute = relationship.options.attribute || "id"; diff --git a/tests/dummy/app/components/eck-delete-attachment.js b/tests/dummy/app/components/eck-delete-attachment.js index 9574f82..e12c5e4 100644 --- a/tests/dummy/app/components/eck-delete-attachment.js +++ b/tests/dummy/app/components/eck-delete-attachment.js @@ -6,6 +6,13 @@ export default Ember.Component.extend({ click: function (event) { event.preventDefault(); - this.get("controller").send("deleteAttachment", this.get("context")); + this.send("deleteAttachment", this.get("value")); + }, + + actions: { + deleteAttachment: function (value) { + this.set("action", "deleteAttachment"); + this.sendAction("action", value); + } } }); diff --git a/tests/dummy/app/components/eck-delete-issue.js b/tests/dummy/app/components/eck-delete-issue.js index 5d4f40d..c07adf3 100644 --- a/tests/dummy/app/components/eck-delete-issue.js +++ b/tests/dummy/app/components/eck-delete-issue.js @@ -6,12 +6,12 @@ export default Ember.Component.extend({ click: function (event) { event.preventDefault(); - this.get("controller").send("deleteIssue", this.get("value")); + this.send("deleteIssue", this.get("value")); }, actions: { - deleteIssue: function (context) { + deleteIssue: function (value) { this.set("action", "deleteIssue"); - this.sendAction("action", context); + this.sendAction("action", value); } } }); diff --git a/tests/dummy/app/components/eck-issue.js b/tests/dummy/app/components/eck-issue.js index 35b39df..8eed31e 100644 --- a/tests/dummy/app/components/eck-issue.js +++ b/tests/dummy/app/components/eck-issue.js @@ -9,7 +9,7 @@ export default Ember.Component.extend({ submit: function (event) { event.preventDefault(); if (this.get("edit")) { - this.get("controller").send("saveIssue", this.get("context")); + this.send("saveIssue", this.get("value")); } this.toggleProperty("edit"); }, @@ -35,9 +35,20 @@ export default Ember.Component.extend({ drop: function (event) { var view = Ember.View.views[event.dataTransfer.getData("id")]; if (this.draggable === "true" || view.draggable === "true") { - this.get("controller").send("dropIssue", view.get("controller"), view.get("context"), this.get("context")); + this.send("dropIssue", view.get("controller"), view.get("value"), this.get("value")); } event.preventDefault(); event.target.style.opacity = "1"; + }, + + actions: { + saveIssue: function (value) { + this.set("action", "saveIssue"); + this.sendAction("action", value); + }, + dropIssue: function (controller, oldModel, newModel) { + this.set("action", "dropIssue"); + this.sendAction("action", controller, oldModel, newModel); + } } }); diff --git a/tests/dummy/app/models/position.js b/tests/dummy/app/models/position.js index ad6755b..ce4ae70 100644 --- a/tests/dummy/app/models/position.js +++ b/tests/dummy/app/models/position.js @@ -2,7 +2,7 @@ import DS from "ember-data"; export default DS.Model.extend({ issues: DS.hasMany("issue", { - async: false + async: true }), type: DS.attr("string", { defaultValue: "position" diff --git a/tests/dummy/app/templates/-issue-list.hbs b/tests/dummy/app/templates/-issue-list.hbs index 254b278..f682ccf 100644 --- a/tests/dummy/app/templates/-issue-list.hbs +++ b/tests/dummy/app/templates/-issue-list.hbs @@ -1,21 +1,21 @@
    {{#each model as |content|}} - {{#eck-issue context=content}} + {{#eck-issue value=content}}
  • {{#if edit}}
    - {{#eck-focused-text-area class="form-control" viewName="TextAreaEdit" value=context.text}} + {{#eck-focused-text-area class="form-control" viewName="TextAreaEdit" value=value.text}} {{/eck-focused-text-area}}
    {{#if attachments}} {{#each attachments as |attachment|}} {{attachment.file_name}} - {{#eck-delete-attachment context=attachment}} × {{/eck-delete-attachment}} + {{#eck-delete-attachment value=attachment}} × {{/eck-delete-attachment}} {{/each}} {{/if}} - {{#eck-attachment context=content}} + {{#eck-attachment value=content}} {{/eck-attachment}} {{else}} From 1b35d85d0b4a4d82c43087e17d31cb2578e94081 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Fri, 31 Jul 2015 12:57:04 -0400 Subject: [PATCH 28/48] Fixed addon adapter, the _rev can exist in two different spots. --- addon/adapters/document.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/addon/adapters/document.js b/addon/adapters/document.js index 2ac1f82..050960c 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -72,9 +72,14 @@ export default DS.Adapter.extend({ }); }, _normalizeRevision: function (json) { - if (json && json.doc && json.doc._rev) { - json.rev = json.doc._rev; - delete json.doc._rev; + if (json && json._rev) { + json.rev = json._rev; + delete json._rev; + } else { + if (json && json.doc && json.doc._rev) { + json.rev = json.doc._rev; + delete json.doc._rev; + } } return json; }, From 9438e4e012d216bcc15985bbf5e985a5ae8742b3 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Fri, 31 Jul 2015 13:50:30 -0400 Subject: [PATCH 29/48] Allows the edit button to have the text area show up. --- tests/dummy/app/components/eck-issue.js | 4 ++ tests/dummy/app/templates/-issue-list.hbs | 44 ++----------------- .../app/templates/components/eck-issue.hbs | 32 ++++++++++++++ 3 files changed, 40 insertions(+), 40 deletions(-) create mode 100644 tests/dummy/app/templates/components/eck-issue.hbs diff --git a/tests/dummy/app/components/eck-issue.js b/tests/dummy/app/components/eck-issue.js index 8eed31e..eeb9359 100644 --- a/tests/dummy/app/components/eck-issue.js +++ b/tests/dummy/app/components/eck-issue.js @@ -49,6 +49,10 @@ export default Ember.Component.extend({ dropIssue: function (controller, oldModel, newModel) { this.set("action", "dropIssue"); this.sendAction("action", controller, oldModel, newModel); + }, + deleteIssue: function (value) { + this.set("action", "deleteIssue"); + this.sendAction("action", value); } } }); diff --git a/tests/dummy/app/templates/-issue-list.hbs b/tests/dummy/app/templates/-issue-list.hbs index f682ccf..6bc8741 100644 --- a/tests/dummy/app/templates/-issue-list.hbs +++ b/tests/dummy/app/templates/-issue-list.hbs @@ -1,45 +1,9 @@
      {{#each model as |content|}} - - {{#eck-issue value=content}} -
    • - {{#if edit}} -
      - {{#eck-focused-text-area class="form-control" viewName="TextAreaEdit" value=value.text}} - {{/eck-focused-text-area}} -
      - {{#if attachments}} - {{#each attachments as |attachment|}} - {{attachment.file_name}} - {{#eck-delete-attachment value=attachment}} × {{/eck-delete-attachment}} - {{/each}} - {{/if}} - - {{#eck-attachment value=content}} - - {{/eck-attachment}} - {{else}} - {{content.text}} - {{#if attachments}} - , attachments: - {{#each attachments as |attachment|}} - {{linkToAttachment attachment}} - {{/each}} - {{/if}} -
      - - {{#eck-delete-issue value=content}} - × - {{/eck-delete-issue}} -
      - {{/if}} -
    • - {{/eck-issue}} + {{eck-issue content=content}} {{else}} - {{#eck-issue draggable=false}} -
    • - Empty board... -
    • - {{/eck-issue}} +
    • + Empty board... +
    • {{/each}}
    \ No newline at end of file diff --git a/tests/dummy/app/templates/components/eck-issue.hbs b/tests/dummy/app/templates/components/eck-issue.hbs new file mode 100644 index 0000000..e1f7654 --- /dev/null +++ b/tests/dummy/app/templates/components/eck-issue.hbs @@ -0,0 +1,32 @@ +
  • + {{#if edit}} +
    + {{log content.text}} + {{#eck-focused-text-area class="form-control" viewName="TextAreaEdit"}}{{content.text}}{{/eck-focused-text-area}} +
    + {{#if attachments}} + {{#each attachments as |attachment|}} + {{attachment.file_name}} + {{#eck-delete-attachment value=attachment}} × {{/eck-delete-attachment}} + {{/each}} + {{/if}} + + {{#eck-attachment value=content}} + + {{/eck-attachment}} + {{else}} + {{content.text}} + {{#if attachments}} + , attachments: + {{#each attachments as |attachment|}} + {{linkToAttachment attachment}} + {{/each}} + {{/if}} +
    + + {{#eck-delete-issue value=content}} + × + {{/eck-delete-issue}} +
    + {{/if}} +
  • \ No newline at end of file From 95927d29b90ca3568536d281cfc0b9acb0ae3b42 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Fri, 31 Jul 2015 14:23:23 -0400 Subject: [PATCH 30/48] Fixes the edit button. --- tests/dummy/app/components/eck-issue.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/dummy/app/components/eck-issue.js b/tests/dummy/app/components/eck-issue.js index eeb9359..898ba92 100644 --- a/tests/dummy/app/components/eck-issue.js +++ b/tests/dummy/app/components/eck-issue.js @@ -8,8 +8,14 @@ export default Ember.Component.extend({ submit: function (event) { event.preventDefault(); + var text; if (this.get("edit")) { - this.send("saveIssue", this.get("value")); + text = this.get("childViews")[0].element.value; + if (text === "") { + this.send("deleteIssue", this.get("content")); + } else { + this.send("saveIssue", this.get("content"), text); + } } this.toggleProperty("edit"); }, @@ -35,15 +41,16 @@ export default Ember.Component.extend({ drop: function (event) { var view = Ember.View.views[event.dataTransfer.getData("id")]; if (this.draggable === "true" || view.draggable === "true") { - this.send("dropIssue", view.get("controller"), view.get("value"), this.get("value")); + this.send("dropIssue", view.get("controller"), view.get("content"), this.get("content")); } event.preventDefault(); event.target.style.opacity = "1"; }, actions: { - saveIssue: function (value) { + saveIssue: function (value, text) { this.set("action", "saveIssue"); + value.set("text", text); this.sendAction("action", value); }, dropIssue: function (controller, oldModel, newModel) { From d501b80355fb3c9087178935e43665e748221fad Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Fri, 31 Jul 2015 15:32:04 -0400 Subject: [PATCH 31/48] Drag and drop in dummy app. --- tests/dummy/app/components/eck-issue.js | 34 +++--------------- tests/dummy/app/components/list-group-item.js | 36 +++++++++++++++++++ tests/dummy/app/controllers/index.js | 10 +++--- tests/dummy/app/services/drag-helper.js | 5 +++ tests/dummy/app/templates/-issue-list.hbs | 4 +-- tests/dummy/config/environment.js | 3 +- 6 files changed, 54 insertions(+), 38 deletions(-) create mode 100644 tests/dummy/app/components/list-group-item.js create mode 100644 tests/dummy/app/services/drag-helper.js diff --git a/tests/dummy/app/components/eck-issue.js b/tests/dummy/app/components/eck-issue.js index 898ba92..12564cc 100644 --- a/tests/dummy/app/components/eck-issue.js +++ b/tests/dummy/app/components/eck-issue.js @@ -1,6 +1,7 @@ import Ember from "ember"; +import ListGroupItemComponent from "dummy/components/list-group-item"; -export default Ember.Component.extend({ +export default ListGroupItemComponent.extend({ tagName: "form", edit: false, attributeBindings: ["draggable"], @@ -15,36 +16,13 @@ export default Ember.Component.extend({ this.send("deleteIssue", this.get("content")); } else { this.send("saveIssue", this.get("content"), text); - } + } } this.toggleProperty("edit"); }, dragStart: function (event) { - event.dataTransfer.setData("id", this.get("elementId")); - }, - - dragEnter: function (event) { - event.preventDefault(); - event.target.style.opacity = "0.4"; - }, - - dragOver: function (event) { - event.preventDefault(); - }, - - dragLeave: function (event) { - event.preventDefault(); - event.target.style.opacity = "1"; - }, - - drop: function (event) { - var view = Ember.View.views[event.dataTransfer.getData("id")]; - if (this.draggable === "true" || view.draggable === "true") { - this.send("dropIssue", view.get("controller"), view.get("content"), this.get("content")); - } - event.preventDefault(); - event.target.style.opacity = "1"; + this.get("dragHelper").set("component", this); }, actions: { @@ -53,10 +31,6 @@ export default Ember.Component.extend({ value.set("text", text); this.sendAction("action", value); }, - dropIssue: function (controller, oldModel, newModel) { - this.set("action", "dropIssue"); - this.sendAction("action", controller, oldModel, newModel); - }, deleteIssue: function (value) { this.set("action", "deleteIssue"); this.sendAction("action", value); diff --git a/tests/dummy/app/components/list-group-item.js b/tests/dummy/app/components/list-group-item.js new file mode 100644 index 0000000..ef962ea --- /dev/null +++ b/tests/dummy/app/components/list-group-item.js @@ -0,0 +1,36 @@ +import Ember from "ember"; + +export default Ember.Component.extend({ + dragHelper: Ember.inject.service(), + tagName: "li", + + dragEnter: function (event) { + event.preventDefault(); + event.target.style.opacity = "0.4"; + }, + + dragOver: function (event) { + event.preventDefault(); + }, + + dragLeave: function (event) { + event.preventDefault(); + event.target.style.opacity = "1"; + }, + + drop: function (event) { + var component = this.get("dragHelper").get("component"); + if (this.draggable === "true" || component.draggable === "true") { + this.send("dropIssue", component._controller, component.get("content"), this.get("content")); + } + event.preventDefault(); + event.target.style.opacity = "1"; + }, + + actions: { + dropIssue: function (controller, oldModel, newModel) { + this.set("action", "dropIssue"); + this.sendAction("action", controller, oldModel, newModel); + } + } +}); diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js index fa45bcc..9041c39 100644 --- a/tests/dummy/app/controllers/index.js +++ b/tests/dummy/app/controllers/index.js @@ -70,21 +70,21 @@ export default Ember.Controller.extend({ attachment.save(); }, - dropIssue: function (viewController, viewModel, thisModel) { + dropIssue: function (compController, compModel, thisModel) { var position = this.get("model").toArray().indexOf(thisModel), self = this; if (position === -1) { position = 0; } - viewController.get("model").removeObject(viewModel); + compController.get("model").removeObject(compModel); - if (viewController.name !== this.name) { - viewController.get("position").save().then(function () { + if (compController.name !== this.name) { + compController.get("position").save().then(function () { self.get("position").reload(); }); } - this.get("model").insertAt(position, viewModel); + this.get("model").insertAt(position, compModel); this.get("position").save(); } } diff --git a/tests/dummy/app/services/drag-helper.js b/tests/dummy/app/services/drag-helper.js new file mode 100644 index 0000000..3e80692 --- /dev/null +++ b/tests/dummy/app/services/drag-helper.js @@ -0,0 +1,5 @@ +import Ember from "ember"; + +export default Ember.Service.extend({ + component: null +}); diff --git a/tests/dummy/app/templates/-issue-list.hbs b/tests/dummy/app/templates/-issue-list.hbs index 6bc8741..39ec9ef 100644 --- a/tests/dummy/app/templates/-issue-list.hbs +++ b/tests/dummy/app/templates/-issue-list.hbs @@ -2,8 +2,8 @@ {{#each model as |content|}} {{eck-issue content=content}} {{else}} -
  • + {{#list-group-item class="list-group-item"}} Empty board... -
  • + {{/list-group-item}} {{/each}}
\ No newline at end of file diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js index affdd35..c53a26c 100644 --- a/tests/dummy/config/environment.js +++ b/tests/dummy/config/environment.js @@ -44,7 +44,8 @@ module.exports = function (environment) { ENV.contentSecurityPolicy = { "connect-src": "'self' http://localhost:5984", - "style-src": "'self' 'unsafe-inline'" + "style-src": "'self' 'unsafe-inline'", + "script-src": "'self' 'unsafe-inline' 'unsafe-eval'" } return ENV; From 59e90905bd5542eca06c26165d0135f62d7d7933 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Fri, 31 Jul 2015 15:38:54 -0400 Subject: [PATCH 32/48] Updating adding attachments still needs work. --- addon/adapters/document.js | 3 +++ tests/dummy/app/components/eck-attachment.js | 12 +++++++----- tests/dummy/app/components/eck-issue.js | 8 ++++++-- tests/dummy/app/controllers/index.js | 6 +++--- tests/dummy/app/templates/components/eck-issue.hbs | 1 - 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/addon/adapters/document.js b/addon/adapters/document.js index 050960c..aef4c8e 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -271,6 +271,9 @@ export default DS.Adapter.extend({ if (snapshot._attributes.rev) { json._rev = snapshot.attr("rev"); } + if (json.attachments) { + delete json.attachments; + } normalizeResponse = function (data) { var _data, _modelJson; _data = json || {}; diff --git a/tests/dummy/app/components/eck-attachment.js b/tests/dummy/app/components/eck-attachment.js index c1ce1e7..d8d65b1 100644 --- a/tests/dummy/app/components/eck-attachment.js +++ b/tests/dummy/app/components/eck-attachment.js @@ -6,14 +6,16 @@ export default Ember.Component.extend({ style: Ember.Handlebars.SafeString("display:none"), type: "file", multiple: true, - + change: function (event) { + this.send("addAttachment", event.target.files, this.get("value")); + }, actions: { + addAttachment: function (files, content) { + this.set("action", "addAttachment"); + this.sendAction("action", files, content); + }, browseFile: function (e) { this.$().click(); } - }, - - change: function (event) { - this.get("controller").send("addAttachment", event.target.files, this.get("context")); } }); diff --git a/tests/dummy/app/components/eck-issue.js b/tests/dummy/app/components/eck-issue.js index 898ba92..4015f5a 100644 --- a/tests/dummy/app/components/eck-issue.js +++ b/tests/dummy/app/components/eck-issue.js @@ -15,7 +15,7 @@ export default Ember.Component.extend({ this.send("deleteIssue", this.get("content")); } else { this.send("saveIssue", this.get("content"), text); - } + } } this.toggleProperty("edit"); }, @@ -60,6 +60,10 @@ export default Ember.Component.extend({ deleteIssue: function (value) { this.set("action", "deleteIssue"); this.sendAction("action", value); - } + }, + addAttachment: function (files, content) { + this.set("action", "addAttachment"); + this.sendAction("action", files, content); + }, } }); diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js index fa45bcc..c7130b3 100644 --- a/tests/dummy/app/controllers/index.js +++ b/tests/dummy/app/controllers/index.js @@ -41,11 +41,11 @@ export default Ember.Controller.extend({ _addAttachment: function (count, files, size, model, self) { var file = files[count], - attachmentId = "%@/%@".fmt(model.id, file.name), + attachmentId = Ember.String.fmt("%@/%@", model.id, file.name), params = { doc_id: model.id, - model_name: App.Issue, - rev: model._data.rev, + model_name: model, + rev: model._internalModel._data.rev, id: attachmentId, file: file, content_type: file.type, diff --git a/tests/dummy/app/templates/components/eck-issue.hbs b/tests/dummy/app/templates/components/eck-issue.hbs index e1f7654..b802542 100644 --- a/tests/dummy/app/templates/components/eck-issue.hbs +++ b/tests/dummy/app/templates/components/eck-issue.hbs @@ -1,7 +1,6 @@
  • {{#if edit}}
    - {{log content.text}} {{#eck-focused-text-area class="form-control" viewName="TextAreaEdit"}}{{content.text}}{{/eck-focused-text-area}}
    {{#if attachments}} From 10ef86683978bd60e487830f44304d0ce8c3216f Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Fri, 31 Jul 2015 15:53:22 -0400 Subject: [PATCH 33/48] Getting rid of superfluous code. --- tests/dummy/app/components/eck-attachment.js | 3 +-- tests/dummy/app/components/eck-issue.js | 2 +- tests/dummy/app/components/eck-new-issue.js | 2 -- tests/dummy/app/controllers/index.js | 4 +--- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/dummy/app/components/eck-attachment.js b/tests/dummy/app/components/eck-attachment.js index d8d65b1..2aa3dde 100644 --- a/tests/dummy/app/components/eck-attachment.js +++ b/tests/dummy/app/components/eck-attachment.js @@ -2,8 +2,7 @@ import Ember from "ember"; export default Ember.Component.extend({ tagName: "input", - attributeBindings: ["style", "type", "multiple"], - style: Ember.Handlebars.SafeString("display:none"), + attributeBindings: ["type", "multiple"], type: "file", multiple: true, change: function (event) { diff --git a/tests/dummy/app/components/eck-issue.js b/tests/dummy/app/components/eck-issue.js index fa9e68b..8a9bdb3 100644 --- a/tests/dummy/app/components/eck-issue.js +++ b/tests/dummy/app/components/eck-issue.js @@ -38,6 +38,6 @@ export default ListGroupItemComponent.extend({ addAttachment: function (files, content) { this.set("action", "addAttachment"); this.sendAction("action", files, content); - }, + } } }); diff --git a/tests/dummy/app/components/eck-new-issue.js b/tests/dummy/app/components/eck-new-issue.js index 7ceba3d..9299866 100644 --- a/tests/dummy/app/components/eck-new-issue.js +++ b/tests/dummy/app/components/eck-new-issue.js @@ -3,8 +3,6 @@ import Ember from "ember"; export default Ember.Component.extend({ tagName: "form", create: false, - attributeBindings: ["style"], - style: Ember.Handlebars.SafeString("display:inline"), submit: function (event) { this._save(event); diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js index 20fbcfa..bfb0ce3 100644 --- a/tests/dummy/app/controllers/index.js +++ b/tests/dummy/app/controllers/index.js @@ -79,9 +79,7 @@ export default Ember.Controller.extend({ compController.get("model").removeObject(compModel); if (compController.name !== this.name) { - compController.get("position").save().then(function () { - self.get("position").reload(); - }); + compController.get("position").save(); } this.get("model").insertAt(position, compModel); From 8e965a9211af0a7f8b51c3e849f7f5249f71b1f7 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Fri, 31 Jul 2015 15:54:42 -0400 Subject: [PATCH 34/48] Fixes duplication of the id on update. --- addon/adapters/document.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addon/adapters/document.js b/addon/adapters/document.js index aef4c8e..1722a1c 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -235,6 +235,7 @@ export default DS.Adapter.extend({ if ("attachments" in snapData ? snapData.attachments.length > 0 : void 0) { this._updateAttachmnets(snapshot, json); } + delete json.id; delete json.rev; return this._push(store, type, snapshot, json); }, From ebf8c96b123298e7ef9c8c1207ceea717bec3e7f Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Fri, 31 Jul 2015 16:18:23 -0400 Subject: [PATCH 35/48] Another change to attachment. --- tests/dummy/app/controllers/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js index bfb0ce3..0dee0c3 100644 --- a/tests/dummy/app/controllers/index.js +++ b/tests/dummy/app/controllers/index.js @@ -44,7 +44,7 @@ export default Ember.Controller.extend({ attachmentId = Ember.String.fmt("%@/%@", model.id, file.name), params = { doc_id: model.id, - model_name: model, + model_name: model._internalModel.modelName, rev: model._internalModel._data.rev, id: attachmentId, file: file, From 519c6bb68e47e92cb392b69a0742040db80a4d7d Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Fri, 31 Jul 2015 16:59:55 -0400 Subject: [PATCH 36/48] Working with attachments --- addon/adapters/attachment.js | 24 ++++++++++++------------ addon/adapters/rev.js | 4 ++-- addon/serializers/attachment.js | 6 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/addon/adapters/attachment.js b/addon/adapters/attachment.js index 07bbd5d..9d3c54c 100644 --- a/addon/adapters/attachment.js +++ b/addon/adapters/attachment.js @@ -31,9 +31,9 @@ export default DS.Adapter.extend({ }); }); }, - createRecord: function (store, type, record) { + createRecord: function (store, type, snapshot) { var adapter, url; - url = Ember.String.fmt("%@/%@?rev=%@", this.buildURL(), record.get("id"), record.get("rev")); + url = Ember.String.fmt("%@/%@?rev=%@", this.buildURL(), snapshot.record.get("id"), snapshot.record.get("rev")); adapter = this; return new Ember.RSVP.Promise(function (resolve, reject) { var data, request, @@ -42,15 +42,15 @@ export default DS.Adapter.extend({ data.context = adapter; request = new window.XMLHttpRequest(); request.open("PUT", url, true); - request.setRequestHeader("Content-Type", record.get("content_type")); - adapter._updateUploadState(record, request); + request.setRequestHeader("Content-Type", snapshot.attr("content_type")); + adapter._updateUploadState(snapshot, request); request.onreadystatechange = function () { var json; if (request.readyState === 4 && (request.status === 201 || request.status === 200)) { data = JSON.parse(request.response); - data.model_name = record.get("model_name"); - data.doc_id = record.get("doc_id"); - json = adapter.serialize(record, { + data.model_name = snapshot.record.model_name; + data.doc_id = snapshot.record.doc_id; + json = adapter.serialize(snapshot, { includeId: true }); delete data.id; @@ -59,19 +59,19 @@ export default DS.Adapter.extend({ }); } }; - return request.send(record.get("file")); + return request.send(snapshot.record.file); }); }, - updateRecord: function (store, type, record) {}, - deleteRecord: function (store, type, record) { + updateRecord: function (store, type, snapshot) {}, + deleteRecord: function (store, type, snapshot) { return new Ember.RSVP.Promise(function (resolve, reject) { return Ember.run(null, resolve, {}); }); }, - _updateUploadState: function (record, request) { + _updateUploadState: function (snapshot, request) { var view, _this = this; - view = record.get("view"); + view = snapshot._attributes.view; if (view) { view.startUpload(); request.onprogress = function (oEvent) { diff --git a/addon/adapters/rev.js b/addon/adapters/rev.js index d914960..fdd4f79 100644 --- a/addon/adapters/rev.js +++ b/addon/adapters/rev.js @@ -14,8 +14,8 @@ export default DS.Adapter.extend({ context: this }, id); }, - updateRecord: function (store, type, record) {}, - deleteRecord: function (store, type, record) {}, + updateRecord: function (store, type, snapshot) {}, + deleteRecord: function (store, type, snapshot) {}, ajax: function (url, type, hash, id) { return this._ajax(Ember.String.fmt("%@/%@", this.buildURL(), url || ""), type, hash, id); }, diff --git a/addon/serializers/attachment.js b/addon/serializers/attachment.js index ddf34c6..f6b7c2b 100644 --- a/addon/serializers/attachment.js +++ b/addon/serializers/attachment.js @@ -8,9 +8,9 @@ export default DS.RESTSerializer.extend({ self = this; rev = hash._rev || hash.rev; this.store.find(hash.model_name, hash.doc_id).then(function (document) { - if (document.get("_data.rev") !== rev) { - if (self.getIntRevision(document.get("_data.rev")) < self.getIntRevision(rev)) { - return document.set("_data.rev", rev); + if (document.get("_internalModel._data.rev") !== rev) { + if (self.getIntRevision(document.get("_internalModel._data.rev")) < self.getIntRevision(rev)) { + return document.set("_internalModel._data.rev", rev); } } }); From 30388058e46aad3ec09e489948b6a90aaa804baa Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Mon, 3 Aug 2015 12:11:45 -0400 Subject: [PATCH 37/48] Almost there? --- addon/adapters/attachment.js | 19 +++++++++---------- addon/adapters/document.js | 13 ++++++------- addon/adapters/rev.js | 9 ++++----- addon/index.js | 4 +--- addon/{services => mixins}/shared-store.js | 8 ++++---- addon/serializers/attachment.js | 6 +++--- addon/serializers/document.js | 13 ++++++------- addon/serializers/rev.js | 11 +++++------ tests/dummy/app/helpers/link-to-attachment.js | 14 +++++++------- tests/dummy/app/models/issue.js | 2 +- tests/dummy/app/routes/index.js | 12 +++++------- tests/dummy/app/services/globals.js | 6 ++++++ .../app/templates/components/eck-issue.hbs | 10 +++++----- 13 files changed, 62 insertions(+), 65 deletions(-) rename addon/{services => mixins}/shared-store.js (87%) create mode 100644 tests/dummy/app/services/globals.js diff --git a/addon/adapters/attachment.js b/addon/adapters/attachment.js index 9d3c54c..492c32e 100644 --- a/addon/adapters/attachment.js +++ b/addon/adapters/attachment.js @@ -1,28 +1,27 @@ import Ember from "ember"; import DS from "ember-data"; -import sharedStore from "../services/shared-store"; +import sharedStore from "../mixins/shared-store"; -export default DS.Adapter.extend({ - sharedStore: sharedStore, +export default DS.Adapter.extend(sharedStore, { // DEPRECATED // Find has been deprecated as of Ember Data 1.13. This has been left for backwards compatibility. find: function (store, type, id) { return this.findRecord(store, type, id); }, findRecord: function (store, type, id) { - var sharedStore = this.get("sharedStore"); + var self = this; return new Ember.RSVP.Promise(function (resolve, reject) { return Ember.run(null, resolve, { - attachment: sharedStore.get("attachment", id) + attachment: self.getData("attachment", id) }); }); }, findMany: function (store, type, ids) { var docs, - _this = this; + self = this; docs = ids.map(function (item) { - item = _this.get("sharedStore").get("attachment", item); - item.db = _this.get("db"); + item = self.getData("attachment", item); + item.db = self.get("db"); return item; }); return new Ember.RSVP.Promise(function (resolve, reject) { @@ -37,7 +36,7 @@ export default DS.Adapter.extend({ adapter = this; return new Ember.RSVP.Promise(function (resolve, reject) { var data, request, - _this = this; + self = this; data = {}; data.context = adapter; request = new window.XMLHttpRequest(); @@ -70,7 +69,7 @@ export default DS.Adapter.extend({ }, _updateUploadState: function (snapshot, request) { var view, - _this = this; + self = this; view = snapshot._attributes.view; if (view) { view.startUpload(); diff --git a/addon/adapters/document.js b/addon/adapters/document.js index 1722a1c..6eb12bd 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -1,9 +1,8 @@ import Ember from "ember"; import DS from "ember-data"; -import sharedStore from "../services/shared-store"; +import sharedStore from "../mixins/shared-store"; -export default DS.Adapter.extend({ - sharedStore: sharedStore, +export default DS.Adapter.extend(sharedStore, { defaultSerializer: "_default", customTypeLookup: false, typeViewName: "all", @@ -232,7 +231,7 @@ export default DS.Adapter.extend({ includeId: true }); snapData = snapshot.record; - if ("attachments" in snapData ? snapData.attachments.length > 0 : void 0) { + if ("attachments" in snapData ? snapData.get("attachments").length > 0 : void 0) { this._updateAttachmnets(snapshot, json); } delete json.id; @@ -243,12 +242,12 @@ export default DS.Adapter.extend({ return this.ajax(Ember.String.fmt("%@?rev=%@", snapshot.id, snapshot.attr("rev")), "DELETE", (function () {}), {}); }, _updateAttachmnets: function (snapshot, json) { - var _attachments, sharedStore; + var _attachments, + self = this; _attachments = {}; - sharedStore = this.get("sharedStore"); snapshot._hasManyRelationships.attachments.forEach(function (item) { var attachment; - attachment = sharedStore.get("attachment", item.get("id")); + attachment = self.getData("attachment", item.get("id")); _attachments[attachment.file_name] = { content_type: attachment.content_type, digest: attachment.digest, diff --git a/addon/adapters/rev.js b/addon/adapters/rev.js index fdd4f79..e938e8c 100644 --- a/addon/adapters/rev.js +++ b/addon/adapters/rev.js @@ -1,9 +1,8 @@ import Ember from "ember"; import DS from "ember-data"; -import sharedStore from "../services/shared-store"; +import sharedStore from "../mixins/shared-store"; -export default DS.Adapter.extend({ - sharedStore: sharedStore, +export default DS.Adapter.extend(sharedStore, { // DEPRECATED // Find has been deprecated as of Ember Data 1.13. This has been left for backwards compatibility. find: function (store, type, id) { @@ -20,7 +19,6 @@ export default DS.Adapter.extend({ return this._ajax(Ember.String.fmt("%@/%@", this.buildURL(), url || ""), type, hash, id); }, _ajax: function (url, type, hash, id) { - var sharedStore = this.get("sharedStore"); hash.url = url; hash.type = type; hash.dataType = "json"; @@ -30,8 +28,9 @@ export default DS.Adapter.extend({ hash.data = JSON.stringify(hash.data); } return new Ember.RSVP.Promise(function (resolve, reject) { + var self = this; hash.success = function (data) { - sharedStore.add("revs", id, data); + self.addData("revs", id, data); return Ember.run(null, resolve, { history: { id: id diff --git a/addon/index.js b/addon/index.js index 7e3a0ad..8e303fa 100644 --- a/addon/index.js +++ b/addon/index.js @@ -5,7 +5,6 @@ import RevAdapter from "./adapters/rev"; import AttachmentSerializer from "./serializers/attachment"; import DocumentSerializer from "./serializers/document"; import RevSerializer from "./serializers/rev"; -import SharedStore from "./services/shared-store"; export { ChangesFeed, @@ -14,6 +13,5 @@ export { RevAdapter, AttachmentSerializer, DocumentSerializer, - RevSerializer, - SharedStore + RevSerializer }; diff --git a/addon/services/shared-store.js b/addon/mixins/shared-store.js similarity index 87% rename from addon/services/shared-store.js rename to addon/mixins/shared-store.js index 3cc9b65..282f9ab 100644 --- a/addon/services/shared-store.js +++ b/addon/mixins/shared-store.js @@ -1,17 +1,17 @@ import Ember from "ember"; -export default Ember.Service.extend({ +export default Ember.Mixin.create({ _data: {}, - add: function (type, key, value) { + addData: function (type, key, value) { var _data = this.get("_data"); _data[type + ":" + key] = value; return _data[type + ":" + key]; }, - get: function (type, key) { + getData: function (type, key) { var _data = this.get("_data"); return _data[type + ":" + key]; }, - remove: function (type, key) { + removeData: function (type, key) { var _data = this.get("_data"); return delete _data[type + ":" + key]; }, diff --git a/addon/serializers/attachment.js b/addon/serializers/attachment.js index f6b7c2b..381eb78 100644 --- a/addon/serializers/attachment.js +++ b/addon/serializers/attachment.js @@ -8,9 +8,9 @@ export default DS.RESTSerializer.extend({ self = this; rev = hash._rev || hash.rev; this.store.find(hash.model_name, hash.doc_id).then(function (document) { - if (document.get("_internalModel._data.rev") !== rev) { - if (self.getIntRevision(document.get("_internalModel._data.rev")) < self.getIntRevision(rev)) { - return document.set("_internalModel._data.rev", rev); + if (document.get("rev") !== rev) { + if (self.getIntRevision(document.get("rev")) < self.getIntRevision(rev)) { + return document.set("rev", rev); } } }); diff --git a/addon/serializers/document.js b/addon/serializers/document.js index 8c1f7b9..e012102 100644 --- a/addon/serializers/document.js +++ b/addon/serializers/document.js @@ -1,14 +1,13 @@ import Ember from "ember"; import DS from "ember-data"; -import sharedStore from "../services/shared-store"; +import sharedStore from "../mixins/shared-store"; -export default DS.RESTSerializer.extend({ +export default DS.RESTSerializer.extend(sharedStore, { isNewSerializerAPI: true, - sharedStore: sharedStore, primaryKey: "id", normalize: function (type, hash, prop) { this.normalizeId(hash); - this.normalizeAttachments(hash._attachments, type.modelName, hash); + this.normalizeAttachments(hash.doc._attachments, type.modelName, hash); this.addHistoryId(hash); this.normalizeDoc(hash); this.normalizeUsingDeclaredMapping(type, hash); @@ -49,21 +48,21 @@ export default DS.RESTSerializer.extend({ for (k in attachments) { if (attachments.hasOwnProperty(k)) { v = attachments[k]; - key = hash._id + "/" + k; + key = hash.id + "/" + k; attachment = { id: key, content_type: v.content_type, digest: v.digest, length: v.length, stub: v.stub, - doc_id: hash._id, + doc_id: hash.id, rev: hash.rev, file_name: k, model_name: type, revpos: v.revpos, db: v.db }; - this.get("sharedStore").add("attachment", key, attachment); + this.addData("attachment", key, attachment); _attachments.push(key); } } diff --git a/addon/serializers/rev.js b/addon/serializers/rev.js index 6e90cbd..b9e2dd5 100644 --- a/addon/serializers/rev.js +++ b/addon/serializers/rev.js @@ -1,10 +1,9 @@ import Ember from "ember"; import DS from "ember-data"; -import sharedStore from "../services/shared-store"; +import sharedStore from "../mixins/shared-store"; -export default DS.RESTSerializer.extend({ +export default DS.RESTSerializer.extend(sharedStore, { isNewSerializerAPI: true, - sharedStore: sharedStore, primaryKey: "id", normalize: function (type, hash, prop) { this.extractRelationships(type, hash); @@ -14,13 +13,13 @@ export default DS.RESTSerializer.extend({ return hash._id || hash.id; }, extractRelationships: function (type, hash) { - var sharedStore = this.get("sharedStore"); + var self = this; return type.eachRelationship((function (key, relationship) { if (relationship.kind === "belongsTo") { - hash[key] = sharedStore.mapRevIds("revs", this.extractId(type, hash))[1]; + hash[key] = self.mapRevIds("revs", this.extractId(type, hash))[1]; } if (relationship.kind === "hasMany") { - hash[key] = sharedStore.mapRevIds("revs", this.extractId(type, hash)); + hash[key] = self.mapRevIds("revs", this.extractId(type, hash)); return hash[key]; } }), this); diff --git a/tests/dummy/app/helpers/link-to-attachment.js b/tests/dummy/app/helpers/link-to-attachment.js index 83bedb3..9e83e2e 100644 --- a/tests/dummy/app/helpers/link-to-attachment.js +++ b/tests/dummy/app/helpers/link-to-attachment.js @@ -1,10 +1,10 @@ -/* global App */ import Ember from "ember"; -export default Ember.Handlebars.makeBoundHelper(function (attachment) { - var aTagTemplate = "%@", - url = "%@/%@/%@".fmt(App.Host, attachment.get("_data.db"), attachment.get("id")); - return new Ember.Handlebars.SafeString( - aTagTemplate.fmt(url, attachment.get("file_name")) - ); +export default Ember.Helper.extend({ + globals: Ember.inject.service(), + compute(params) { + var aTagTemplate = "%@", + url = Ember.String.fmt("%@/%@/%@", this.get("globals").get("host"), params[0].get("db"), params[0].get("id")); + return Ember.String.htmlSafe(Ember.String.fmt(aTagTemplate, url, params[0].get("file_name"))); + } }); diff --git a/tests/dummy/app/models/issue.js b/tests/dummy/app/models/issue.js index 25108bb..f324cba 100644 --- a/tests/dummy/app/models/issue.js +++ b/tests/dummy/app/models/issue.js @@ -5,6 +5,6 @@ export default DS.Model.extend({ type: DS.attr("string", { defaultValue: "issue" }), - attachments: DS.hasMany("attachment"), + attachments: DS.hasMany("attachment", {async: true}), rev: DS.attr("string") }); diff --git a/tests/dummy/app/routes/index.js b/tests/dummy/app/routes/index.js index ec9032a..4b6faca 100644 --- a/tests/dummy/app/routes/index.js +++ b/tests/dummy/app/routes/index.js @@ -3,9 +3,7 @@ import Ember from "ember"; import ChangesFeed from "ember-couch/changes-feed"; export default Ember.Route.extend({ - boards: ["common", "intermediate", "advanced"], - host: "http://localhost:5984", - + globals: Ember.inject.service(), setupController: function (controller, model) { this._setupPositionHolders(); this._position(); @@ -16,7 +14,7 @@ export default Ember.Route.extend({ this.render(); // link particular controller with its outlet var self = this; - self.get("boards").forEach(function (label) { + self.get("globals").get("boards").forEach(function (label) { self.render("board", { outlet: label, into: "index", @@ -27,7 +25,7 @@ export default Ember.Route.extend({ _setupPositionHolders: function () { var self = this; - self.get("boards").forEach(function (type) { + self.get("globals").get("boards").forEach(function (type) { self.get("store").findRecord("position", type).then( function (position) { // set issues into appropriate controller through position model @@ -55,7 +53,7 @@ export default Ember.Route.extend({ }, position = ChangesFeed.create({ db: "boards", - host: this.get("host"), + host: this.get("globals").get("host"), content: params }), self = this; @@ -85,7 +83,7 @@ export default Ember.Route.extend({ }, issue = ChangesFeed.create({ db: "boards", - host: this.get("host"), + host: this.get("globals").get("host"), content: params }), self = this; diff --git a/tests/dummy/app/services/globals.js b/tests/dummy/app/services/globals.js new file mode 100644 index 0000000..e3fa9e5 --- /dev/null +++ b/tests/dummy/app/services/globals.js @@ -0,0 +1,6 @@ +import Ember from "ember"; + +export default Ember.Service.extend({ + host: "http://localhost:5984", + boards: ["common", "intermediate", "advanced"] +}); diff --git a/tests/dummy/app/templates/components/eck-issue.hbs b/tests/dummy/app/templates/components/eck-issue.hbs index b802542..b793542 100644 --- a/tests/dummy/app/templates/components/eck-issue.hbs +++ b/tests/dummy/app/templates/components/eck-issue.hbs @@ -3,8 +3,8 @@
    {{#eck-focused-text-area class="form-control" viewName="TextAreaEdit"}}{{content.text}}{{/eck-focused-text-area}}
    - {{#if attachments}} - {{#each attachments as |attachment|}} + {{#if content.attachments}} + {{#each content.attachments as |attachment|}} {{attachment.file_name}} {{#eck-delete-attachment value=attachment}} × {{/eck-delete-attachment}} {{/each}} @@ -15,10 +15,10 @@ {{/eck-attachment}} {{else}} {{content.text}} - {{#if attachments}} + {{#if content.attachments}} , attachments: - {{#each attachments as |attachment|}} - {{linkToAttachment attachment}} + {{#each content.attachments as |attachment|}} + {{link-to-attachment attachment}} {{/each}} {{/if}}
    From 60cbce47b183a73337fbbf51947beebb494bc76b Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Mon, 3 Aug 2015 14:01:24 -0400 Subject: [PATCH 38/48] Working on attacments --- addon/adapters/document.js | 2 +- addon/serializers/document.js | 7 ++- tests/dummy/app/components/eck-issue.js | 4 ++ tests/dummy/app/helpers/link-to-attachment.js | 29 ++++++++- tests/dummy/app/templates/-issue-list.hbs | 4 +- .../app/templates/components/eck-issue.hbs | 62 ++++++++++--------- 6 files changed, 71 insertions(+), 37 deletions(-) diff --git a/addon/adapters/document.js b/addon/adapters/document.js index 6eb12bd..06cf08d 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -231,7 +231,7 @@ export default DS.Adapter.extend(sharedStore, { includeId: true }); snapData = snapshot.record; - if ("attachments" in snapData ? snapData.get("attachments").length > 0 : void 0) { + if ("attachments" in snapData ? snapData.get("attachments").get("length") > 0 : void 0) { this._updateAttachmnets(snapshot, json); } delete json.id; diff --git a/addon/serializers/document.js b/addon/serializers/document.js index e012102..c2fa4e0 100644 --- a/addon/serializers/document.js +++ b/addon/serializers/document.js @@ -7,7 +7,7 @@ export default DS.RESTSerializer.extend(sharedStore, { primaryKey: "id", normalize: function (type, hash, prop) { this.normalizeId(hash); - this.normalizeAttachments(hash.doc._attachments, type.modelName, hash); + this.normalizeAttachments(hash, type.modelName); this.addHistoryId(hash); this.normalizeDoc(hash); this.normalizeUsingDeclaredMapping(type, hash); @@ -42,8 +42,9 @@ export default DS.RESTSerializer.extend(sharedStore, { hash.history = Ember.String.fmt("%@/history", hash.id); return hash.history; }, - normalizeAttachments: function (attachments, type, hash) { - var attachment, k, key, v, _attachments; + normalizeAttachments: function (hash, type) { + var attachment, k, key, v, _attachments, attachments; + attachments = hash.doc && hash.doc._attachments || hash._attachments || hash.attachments; _attachments = []; for (k in attachments) { if (attachments.hasOwnProperty(k)) { diff --git a/tests/dummy/app/components/eck-issue.js b/tests/dummy/app/components/eck-issue.js index 8a9bdb3..6eb901b 100644 --- a/tests/dummy/app/components/eck-issue.js +++ b/tests/dummy/app/components/eck-issue.js @@ -38,6 +38,10 @@ export default ListGroupItemComponent.extend({ addAttachment: function (files, content) { this.set("action", "addAttachment"); this.sendAction("action", files, content); + }, + deleteAttachment: function (value) { + this.set("action", "deleteAttachment"); + this.sendAction("action", value); } } }); diff --git a/tests/dummy/app/helpers/link-to-attachment.js b/tests/dummy/app/helpers/link-to-attachment.js index 9e83e2e..3f06796 100644 --- a/tests/dummy/app/helpers/link-to-attachment.js +++ b/tests/dummy/app/helpers/link-to-attachment.js @@ -4,7 +4,32 @@ export default Ember.Helper.extend({ globals: Ember.inject.service(), compute(params) { var aTagTemplate = "%@", - url = Ember.String.fmt("%@/%@/%@", this.get("globals").get("host"), params[0].get("db"), params[0].get("id")); - return Ember.String.htmlSafe(Ember.String.fmt(aTagTemplate, url, params[0].get("file_name"))); + url = Ember.String.fmt("%@/%@/%@", this.get("globals").get("host"), params[0].get("db"), params[0].get("id")), + paramsLoadingPromise = new Promise(function (resolve, reject) { + var count = 0, + repeat = function () { + if (count < 20) { + window.setTimeout(function () { + if (params[0].currentState.isLoading) { + count += 1; + repeat(); + } else { + resolve(Ember.String.htmlSafe(Ember.String.fmt(aTagTemplate, url, params[0].get("file_name")))); + } + }, 50); + } else { + reject(); + } + }; + repeat(); + }), + htmlString; + if (params[0].currentState.isLoading) { + return paramsLoadingPromise.then(function (string) { + return string; + }); + } else { + return Ember.String.htmlSafe(Ember.String.fmt(aTagTemplate, url, params[0].get("file_name"))); + } } }); diff --git a/tests/dummy/app/templates/-issue-list.hbs b/tests/dummy/app/templates/-issue-list.hbs index 39ec9ef..b481513 100644 --- a/tests/dummy/app/templates/-issue-list.hbs +++ b/tests/dummy/app/templates/-issue-list.hbs @@ -1,6 +1,8 @@
      {{#each model as |content|}} - {{eck-issue content=content}} +
    • + {{eck-issue content=content}} +
    • {{else}} {{#list-group-item class="list-group-item"}} Empty board... diff --git a/tests/dummy/app/templates/components/eck-issue.hbs b/tests/dummy/app/templates/components/eck-issue.hbs index b793542..24972e9 100644 --- a/tests/dummy/app/templates/components/eck-issue.hbs +++ b/tests/dummy/app/templates/components/eck-issue.hbs @@ -1,31 +1,33 @@ -
    • - {{#if edit}} -
      - {{#eck-focused-text-area class="form-control" viewName="TextAreaEdit"}}{{content.text}}{{/eck-focused-text-area}} -
      - {{#if content.attachments}} - {{#each content.attachments as |attachment|}} - {{attachment.file_name}} - {{#eck-delete-attachment value=attachment}} × {{/eck-delete-attachment}} - {{/each}} - {{/if}} - - {{#eck-attachment value=content}} - - {{/eck-attachment}} - {{else}} - {{content.text}} - {{#if content.attachments}} - , attachments: - {{#each content.attachments as |attachment|}} - {{link-to-attachment attachment}} - {{/each}} - {{/if}} -
      - - {{#eck-delete-issue value=content}} - × - {{/eck-delete-issue}} -
      +{{#if edit}} +
      + {{#eck-focused-text-area class="form-control" viewName="TextAreaEdit"}}{{content.text}}{{/eck-focused-text-area}} +
      + {{#if content.attachments}} +
        + {{#each content.attachments as |attachment|}} +
      • + {{attachment.file_name}} + {{#eck-delete-attachment value=attachment}} × {{/eck-delete-attachment}} +
      • + {{/each}} +
      {{/if}} -
    • \ No newline at end of file + + {{#eck-attachment value=content}} + + {{/eck-attachment}} +{{else}} + {{content.text}} + {{#if content.attachments}} + , attachments: + {{#each content.attachments as |attachment|}} + {{link-to-attachment attachment}} + {{/each}} + {{/if}} +
      + + {{#eck-delete-issue value=content}} + × + {{/eck-delete-issue}} +
      +{{/if}} \ No newline at end of file From 9bb3cb58aeab6af886710439ad73019d4964f100 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Mon, 3 Aug 2015 14:17:45 -0400 Subject: [PATCH 39/48] Undid changes to the link-to. --- tests/dummy/app/helpers/link-to-attachment.js | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/tests/dummy/app/helpers/link-to-attachment.js b/tests/dummy/app/helpers/link-to-attachment.js index 3f06796..9e83e2e 100644 --- a/tests/dummy/app/helpers/link-to-attachment.js +++ b/tests/dummy/app/helpers/link-to-attachment.js @@ -4,32 +4,7 @@ export default Ember.Helper.extend({ globals: Ember.inject.service(), compute(params) { var aTagTemplate = "%@", - url = Ember.String.fmt("%@/%@/%@", this.get("globals").get("host"), params[0].get("db"), params[0].get("id")), - paramsLoadingPromise = new Promise(function (resolve, reject) { - var count = 0, - repeat = function () { - if (count < 20) { - window.setTimeout(function () { - if (params[0].currentState.isLoading) { - count += 1; - repeat(); - } else { - resolve(Ember.String.htmlSafe(Ember.String.fmt(aTagTemplate, url, params[0].get("file_name")))); - } - }, 50); - } else { - reject(); - } - }; - repeat(); - }), - htmlString; - if (params[0].currentState.isLoading) { - return paramsLoadingPromise.then(function (string) { - return string; - }); - } else { - return Ember.String.htmlSafe(Ember.String.fmt(aTagTemplate, url, params[0].get("file_name"))); - } + url = Ember.String.fmt("%@/%@/%@", this.get("globals").get("host"), params[0].get("db"), params[0].get("id")); + return Ember.String.htmlSafe(Ember.String.fmt(aTagTemplate, url, params[0].get("file_name"))); } }); From a5a8a8c020aeb79cf1cd06611561cbd43a57d5c7 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Mon, 3 Aug 2015 14:18:43 -0400 Subject: [PATCH 40/48] Async helper. --- tests/dummy/app/helpers/link-to-attachment.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/dummy/app/helpers/link-to-attachment.js b/tests/dummy/app/helpers/link-to-attachment.js index 9e83e2e..f737e6d 100644 --- a/tests/dummy/app/helpers/link-to-attachment.js +++ b/tests/dummy/app/helpers/link-to-attachment.js @@ -4,7 +4,13 @@ export default Ember.Helper.extend({ globals: Ember.inject.service(), compute(params) { var aTagTemplate = "%@", - url = Ember.String.fmt("%@/%@/%@", this.get("globals").get("host"), params[0].get("db"), params[0].get("id")); + url = Ember.String.fmt("%@/%@/%@", this.get("globals").get("host"), params[0].get("db"), params[0].get("id")), + self = this; + if (params[0].get("isLoading")) { + params[0]._internalModel._loadingPromise.then(function () { + self.recompute(); + }); + } return Ember.String.htmlSafe(Ember.String.fmt(aTagTemplate, url, params[0].get("file_name"))); } }); From 34c46455cbc0231cb6a6be7945054b47ddad52a3 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Mon, 3 Aug 2015 14:49:31 -0400 Subject: [PATCH 41/48] Deleting attachments in dummy app. --- tests/dummy/app/components/eck-delete-attachment.js | 6 +++--- tests/dummy/app/components/eck-issue.js | 4 ++-- tests/dummy/app/controllers/index.js | 6 ++++-- tests/dummy/app/templates/components/eck-issue.hbs | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/dummy/app/components/eck-delete-attachment.js b/tests/dummy/app/components/eck-delete-attachment.js index e12c5e4..b3a56b7 100644 --- a/tests/dummy/app/components/eck-delete-attachment.js +++ b/tests/dummy/app/components/eck-delete-attachment.js @@ -6,13 +6,13 @@ export default Ember.Component.extend({ click: function (event) { event.preventDefault(); - this.send("deleteAttachment", this.get("value")); + this.send("deleteAttachment", this.get("value"), this.get("issue")); }, actions: { - deleteAttachment: function (value) { + deleteAttachment: function (value, issue) { this.set("action", "deleteAttachment"); - this.sendAction("action", value); + this.sendAction("action", value, issue); } } }); diff --git a/tests/dummy/app/components/eck-issue.js b/tests/dummy/app/components/eck-issue.js index 6eb901b..3cfee2d 100644 --- a/tests/dummy/app/components/eck-issue.js +++ b/tests/dummy/app/components/eck-issue.js @@ -39,9 +39,9 @@ export default ListGroupItemComponent.extend({ this.set("action", "addAttachment"); this.sendAction("action", files, content); }, - deleteAttachment: function (value) { + deleteAttachment: function (value, issue) { this.set("action", "deleteAttachment"); - this.sendAction("action", value); + this.sendAction("action", value, issue); } } }); diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js index 0dee0c3..a43baf7 100644 --- a/tests/dummy/app/controllers/index.js +++ b/tests/dummy/app/controllers/index.js @@ -65,9 +65,11 @@ export default Ember.Controller.extend({ }); }, - deleteAttachment: function (attachment) { + deleteAttachment: function (attachment, issue) { attachment.deleteRecord(); - attachment.save(); + attachment.save().then(function () { + issue.save(); + }); }, dropIssue: function (compController, compModel, thisModel) { diff --git a/tests/dummy/app/templates/components/eck-issue.hbs b/tests/dummy/app/templates/components/eck-issue.hbs index 24972e9..1d230bc 100644 --- a/tests/dummy/app/templates/components/eck-issue.hbs +++ b/tests/dummy/app/templates/components/eck-issue.hbs @@ -7,7 +7,7 @@ {{#each content.attachments as |attachment|}}
    • {{attachment.file_name}} - {{#eck-delete-attachment value=attachment}} × {{/eck-delete-attachment}} + {{#eck-delete-attachment value=attachment issue=content}} × {{/eck-delete-attachment}}
    • {{/each}}
    From f6e129a269395e3f003bbff454981a4d5dca96cc Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Mon, 3 Aug 2015 16:47:02 -0400 Subject: [PATCH 42/48] Latest bugfixes and deprecation removals. --- addon/adapters/document.js | 2 +- tests/dummy/app/adapters/application.js | 1 - tests/dummy/app/adapters/attachment.js | 1 - tests/dummy/app/helpers/link-to-attachment.js | 3 ++- tests/dummy/app/services/globals.js | 3 ++- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/addon/adapters/document.js b/addon/adapters/document.js index 06cf08d..264a978 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -247,7 +247,7 @@ export default DS.Adapter.extend(sharedStore, { _attachments = {}; snapshot._hasManyRelationships.attachments.forEach(function (item) { var attachment; - attachment = self.getData("attachment", item.get("id")); + attachment = self.getData("attachment", item.id); _attachments[attachment.file_name] = { content_type: attachment.content_type, digest: attachment.digest, diff --git a/tests/dummy/app/adapters/application.js b/tests/dummy/app/adapters/application.js index 30d949c..19b4407 100644 --- a/tests/dummy/app/adapters/application.js +++ b/tests/dummy/app/adapters/application.js @@ -1,4 +1,3 @@ -/* global App */ import DocumentAdapter from "ember-couch/adapters/document"; export default DocumentAdapter.extend({ diff --git a/tests/dummy/app/adapters/attachment.js b/tests/dummy/app/adapters/attachment.js index d92a4ac..24df009 100644 --- a/tests/dummy/app/adapters/attachment.js +++ b/tests/dummy/app/adapters/attachment.js @@ -1,4 +1,3 @@ -/* global App */ import AttachmentAdapter from "ember-couch/adapters/attachment"; export default AttachmentAdapter.extend({ diff --git a/tests/dummy/app/helpers/link-to-attachment.js b/tests/dummy/app/helpers/link-to-attachment.js index f737e6d..a597ef9 100644 --- a/tests/dummy/app/helpers/link-to-attachment.js +++ b/tests/dummy/app/helpers/link-to-attachment.js @@ -4,7 +4,8 @@ export default Ember.Helper.extend({ globals: Ember.inject.service(), compute(params) { var aTagTemplate = "%@", - url = Ember.String.fmt("%@/%@/%@", this.get("globals").get("host"), params[0].get("db"), params[0].get("id")), + globals = this.get("globals"), + url = Ember.String.fmt("%@/%@/%@", globals.get("host"), globals.get("db"), params[0].get("id")), self = this; if (params[0].get("isLoading")) { params[0]._internalModel._loadingPromise.then(function () { diff --git a/tests/dummy/app/services/globals.js b/tests/dummy/app/services/globals.js index e3fa9e5..2f684ca 100644 --- a/tests/dummy/app/services/globals.js +++ b/tests/dummy/app/services/globals.js @@ -2,5 +2,6 @@ import Ember from "ember"; export default Ember.Service.extend({ host: "http://localhost:5984", - boards: ["common", "intermediate", "advanced"] + boards: ["common", "intermediate", "advanced"], + db: "boards" }); From f2e15e8c5a6f145168c6b889bde3f0e9bb94f15b Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Tue, 4 Aug 2015 09:10:33 -0400 Subject: [PATCH 43/48] Fixes a JSCS error that was failing a test. --- tests/dummy/app/models/issue.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/dummy/app/models/issue.js b/tests/dummy/app/models/issue.js index f324cba..381b125 100644 --- a/tests/dummy/app/models/issue.js +++ b/tests/dummy/app/models/issue.js @@ -5,6 +5,8 @@ export default DS.Model.extend({ type: DS.attr("string", { defaultValue: "issue" }), - attachments: DS.hasMany("attachment", {async: true}), + attachments: DS.hasMany("attachment", { + async: true + }), rev: DS.attr("string") }); From cfc0c488e84a0d5ef60da0a11cee0eeca04994e9 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Tue, 4 Aug 2015 12:52:13 -0400 Subject: [PATCH 44/48] Final Alpha touches. --- addon/adapters/attachment.js | 20 ++--- addon/adapters/document.js | 77 +++++++++---------- addon/adapters/rev.js | 11 ++- addon/changes-feed.js | 41 +++++----- addon/mixins/shared-store.js | 10 ++- addon/serializers/attachment.js | 5 +- addon/serializers/document.js | 32 ++++---- bower.json | 8 +- tests/dummy/app/controllers/index.js | 5 +- tests/dummy/app/helpers/link-to-attachment.js | 8 +- tests/dummy/app/routes/index.js | 8 +- 11 files changed, 107 insertions(+), 118 deletions(-) diff --git a/addon/adapters/attachment.js b/addon/adapters/attachment.js index 492c32e..a43be90 100644 --- a/addon/adapters/attachment.js +++ b/addon/adapters/attachment.js @@ -31,15 +31,13 @@ export default DS.Adapter.extend(sharedStore, { }); }, createRecord: function (store, type, snapshot) { - var adapter, url; - url = Ember.String.fmt("%@/%@?rev=%@", this.buildURL(), snapshot.record.get("id"), snapshot.record.get("rev")); - adapter = this; + var adapter = this, + url = this.buildURL() + "/" + snapshot.record.get("id") + "?rev=" + snapshot.record.get("rev"); return new Ember.RSVP.Promise(function (resolve, reject) { - var data, request, + var data = {}, + request = new window.XMLHttpRequest(), self = this; - data = {}; data.context = adapter; - request = new window.XMLHttpRequest(); request.open("PUT", url, true); request.setRequestHeader("Content-Type", snapshot.attr("content_type")); adapter._updateUploadState(snapshot, request); @@ -68,9 +66,8 @@ export default DS.Adapter.extend(sharedStore, { }); }, _updateUploadState: function (snapshot, request) { - var view, + var view = snapshot._attributes.view, self = this; - view = snapshot._attributes.view; if (view) { view.startUpload(); request.onprogress = function (oEvent) { @@ -84,10 +81,9 @@ export default DS.Adapter.extend(sharedStore, { } }, buildURL: function () { - var host, namespace, url; - host = Ember.get(this, "host"); - namespace = Ember.get(this, "namespace"); - url = []; + var host = Ember.get(this, "host"), + namespace = Ember.get(this, "namespace"), + url = []; if (host) { url.push(host); } diff --git a/addon/adapters/document.js b/addon/adapters/document.js index 264a978..55349a0 100644 --- a/addon/adapters/document.js +++ b/addon/adapters/document.js @@ -7,10 +7,9 @@ export default DS.Adapter.extend(sharedStore, { customTypeLookup: false, typeViewName: "all", buildURL: function () { - var host, namespace, url; - host = Ember.get(this, "host"); - namespace = Ember.get(this, "namespace"); - url = []; + var host = Ember.get(this, "host"), + namespace = Ember.get(this, "namespace"), + url = []; if (host) { url.push(host); } @@ -25,14 +24,13 @@ export default DS.Adapter.extend(sharedStore, { return url; }, ajax: function (url, type, normalizeResponse, hash) { - return this._ajax(Ember.String.fmt("%@/%@", this.buildURL(), url || ""), type, normalizeResponse, hash); + return this._ajax(this.buildURL() + "/" + url || "", type, normalizeResponse, hash); }, _ajax: function (url, type, normalizeResponse, hash) { - var adapter; + var adapter = this; if (!hash) { hash = {}; } - adapter = this; return new Ember.RSVP.Promise(function (resolve, reject) { var headers; if (url.split("/").pop() === "") { @@ -56,8 +54,7 @@ export default DS.Adapter.extend(sharedStore, { } if (!hash.success) { hash.success = function (json) { - var _modelJson; - _modelJson = normalizeResponse.call(adapter, json); + var _modelJson = normalizeResponse.call(adapter, json); return Ember.run(null, resolve, _modelJson); }; } @@ -96,9 +93,8 @@ export default DS.Adapter.extend(sharedStore, { return this.findWithRev(store, type, id); } else { normalizeResponse = function (data) { - var _modelJson; + var _modelJson = {}; this._normalizeRevision(data); - _modelJson = {}; _modelJson[type.modelName] = data; return _modelJson; }; @@ -106,15 +102,14 @@ export default DS.Adapter.extend(sharedStore, { } }, findWithRev: function (store, type, id, hash) { - var normalizeResponse, url, _id, _ref, _rev; - _ref = id.split("/").slice(0, 2); - _id = _ref[0]; - _rev = _ref[1]; - url = Ember.String.fmt("%@?rev=%@", _id, _rev); + var normalizeResponse, + _ref = id.split("/").slice(0, 2), + _id = _ref[0], + _rev = _ref[1], + url = _id + "?rev=" + _rev; normalizeResponse = function (data) { - var _modelJson; + var _modelJson = {}; this._normalizeRevision(data); - _modelJson = {}; data._id = id; _modelJson[type.modelName] = data; return _modelJson; @@ -122,22 +117,19 @@ export default DS.Adapter.extend(sharedStore, { return this.ajax(url, "GET", normalizeResponse, hash); }, findManyWithRev: function (store, type, ids) { - var docs, hash, key, self, - _this = this; - key = Ember.String.pluralize(type.modelName); - self = this; - docs = {}; + var docs = {}, + hash = { + async: false + }, + key = Ember.String.pluralize(type.modelName), + self = this; docs[key] = []; - hash = { - async: false - }; ids.forEach(function (id) { - var url, _id, _ref, _rev; - _ref = id.split("/").slice(0, 2); - _id = _ref[0]; - _rev = _ref[1]; - url = Ember.String.fmt("%@?rev=%@", _id, _rev); - url = Ember.String.fmt("%@/%@", _this.buildURL(), url); + var _ref = id.split("/").slice(0, 2), + _id = _ref[0], + _rev = _ref[1], + url = _id + "?rev=" + _rev; + url = self.buildURL() + "/" + url; hash.url = url; hash.type = "GET"; hash.dataType = "json"; @@ -152,7 +144,8 @@ export default DS.Adapter.extend(sharedStore, { return docs; }, findMany: function (store, type, ids) { - var data, normalizeResponse; + var data, + normalizeResponse; if (this._checkForRevision(ids[0])) { return this.findManyWithRev(store, type, ids); } else { @@ -162,10 +155,10 @@ export default DS.Adapter.extend(sharedStore, { }; normalizeResponse = function (data) { var json, - _this = this; + self = this; json = {}; json[Ember.String.pluralize(type.modelName)] = data.rows.map(function (doc) { - return _this._normalizeRevision(doc); + return self._normalizeRevision(doc); }); return json; }; @@ -183,15 +176,15 @@ export default DS.Adapter.extend(sharedStore, { query.options.include_docs = true; normalizeResponse = function (data) { var json, - _this = this; + self = this; json = {}; json[designDoc] = data.rows.getEach("doc").map(function (doc) { - return _this._normalizeRevision(doc); + return self._normalizeRevision(doc); }); json.total_rows = data.total_rows; return json; }; - return this.ajax(Ember.String.fmt("_design/%@/_view/%@", designDoc, query.viewName), "GET", normalizeResponse, { + return this.ajax("_design/" + designDoc + "/_view/" + query.viewName, "GET", normalizeResponse, { context: this, data: query.options }); @@ -203,10 +196,10 @@ export default DS.Adapter.extend(sharedStore, { typeViewName = this.get("typeViewName"); normalizeResponse = function (data) { var json, - _this = this; + self = this; json = {}; json[[Ember.String.pluralize(type.modelName)]] = data.rows.getEach("doc").map(function (doc) { - return _this._normalizeRevision(doc); + return self._normalizeRevision(doc); }); return json; }; @@ -214,7 +207,7 @@ export default DS.Adapter.extend(sharedStore, { include_docs: true, key: "\"" + typeString + "\"" }; - return this.ajax(Ember.String.fmt("_design/%@/_view/%@", designDoc, typeViewName), "GET", normalizeResponse, { + return this.ajax("_design/" + designDoc + "/_view/" + typeViewName, "GET", normalizeResponse, { data: data }); }, @@ -239,7 +232,7 @@ export default DS.Adapter.extend(sharedStore, { return this._push(store, type, snapshot, json); }, deleteRecord: function (store, type, snapshot) { - return this.ajax(Ember.String.fmt("%@?rev=%@", snapshot.id, snapshot.attr("rev")), "DELETE", (function () {}), {}); + return this.ajax(snapshot.id + "?rev=" + snapshot.attr("rev"), "DELETE", (function () {}), {}); }, _updateAttachmnets: function (snapshot, json) { var _attachments, diff --git a/addon/adapters/rev.js b/addon/adapters/rev.js index e938e8c..38821e6 100644 --- a/addon/adapters/rev.js +++ b/addon/adapters/rev.js @@ -9,14 +9,14 @@ export default DS.Adapter.extend(sharedStore, { return this.findRecord(store, type, id); }, findRecord: function (store, type, id) { - return this.ajax(Ember.String.fmt("%@?revs_info=true", id.split("/")[0]), "GET", { + return this.ajax(id.split("/")[0] + "?revs_info=true", "GET", { context: this }, id); }, updateRecord: function (store, type, snapshot) {}, deleteRecord: function (store, type, snapshot) {}, ajax: function (url, type, hash, id) { - return this._ajax(Ember.String.fmt("%@/%@", this.buildURL(), url || ""), type, hash, id); + return this._ajax(this.buildURL() + "/" + url || "", type, hash, id); }, _ajax: function (url, type, hash, id) { hash.url = url; @@ -41,10 +41,9 @@ export default DS.Adapter.extend(sharedStore, { }); }, buildURL: function () { - var host, namespace, url; - host = Ember.get(this, "host"); - namespace = Ember.get(this, "namespace"); - url = []; + var host = Ember.get(this, "host"), + namespace = Ember.get(this, "namespace"), + url = []; if (host) { url.push(host); } diff --git a/addon/changes-feed.js b/addon/changes-feed.js index fd549a6..1e13880 100644 --- a/addon/changes-feed.js +++ b/addon/changes-feed.js @@ -15,15 +15,15 @@ export default Ember.ObjectProxy.extend({ return this._ajax.apply(this, arguments); }, fromTail: function (callback) { - var _this = this, - url = Ember.String.fmt("%@%@/_changes?descending=true&limit=1", this._buildUrl(), this.get("db")); + var self = this, + url = this._buildUrl() + this.get("db") + "/_changes?descending=true&limit=1"; return Ember.$.ajax({ url: url, dataType: "json", success: function (data) { - _this.set("since", data.last_seq); + self.set("since", data.last_seq); if (callback) { - return callback.call(_this); + return callback.call(self); } } }); @@ -36,51 +36,48 @@ export default Ember.ObjectProxy.extend({ this.set("stopTracking", false); return this.fromTail(callback); }, - _ajax: function (callback, self) { - var _this = this; + _ajax: function (callback, args) { + var self = this; return Ember.$.ajax({ type: "GET", url: this._makeRequestPath(), dataType: "json", success: function (data) { var _ref; - if (!_this.get("stopTracking")) { + if (!self.get("stopTracking")) { if ((data !== null ? (_ref = data.results) !== null ? _ref.length : void 0 : void 0) && callback) { - callback.call(self, data.results); + callback.call(args, data.results); } - return _this.set("since", data.last_seq); + return self.set("since", data.last_seq); } }, complete: function () { - if (!_this.get("stopTracking")) { + if (!self.get("stopTracking")) { return setTimeout((function () { - return _this._ajax(callback, self); + return self._ajax(callback, args); }), 1000); } } }); }, _buildUrl: function () { - var url; - url = this.get("host") || "/"; + var url = this.get("host") || "/"; if (url.substring(url.length - 1) !== "/") { url += "/"; } return url; }, _makeRequestPath: function () { - var feed, params; - feed = this.feed || "longpool"; - params = this._makeFeedParams(); - return Ember.String.fmt("%@%@/_changes?feed=%@%@", this._buildUrl(), this.get("db"), feed, params); + var feed = this.feed || "longpool", + params = this._makeFeedParams(); + return this._buildUrl() + this.get("db") + "/_changes?feed=" + feed + params; }, _makeFeedParams: function () { - var path, - _this = this; - path = ""; + var path = "", + self = this; ["include_docs", "limit", "descending", "heartbeat", "timeout", "filter", "filter_param", "style", "since"].forEach(function (param) { - if (_this.get(param)) { - return path += Ember.String.fmt("&%@=%@", param, _this.get(param)); + if (self.get(param)) { + return path += "&" + param + "=" + self.get(param); } }); return path; diff --git a/addon/mixins/shared-store.js b/addon/mixins/shared-store.js index 282f9ab..8bd7d5e 100644 --- a/addon/mixins/shared-store.js +++ b/addon/mixins/shared-store.js @@ -16,14 +16,16 @@ export default Ember.Mixin.create({ return delete _data[type + ":" + key]; }, mapRevIds: function (type, key) { - var _this = this; + var self = this; return this.get(type, key)._revs_info.map(function (_rev) { - return Ember.String.fmt("%@/%@", _this.get(type, key)._id, _rev.rev); + return self.get(type, key)._id + "/" + _rev.rev; }); }, stopAll: function () { - var k, v, _results, _data = this.get("_data"); - _results = []; + var k, + v, + _results = [], + _data = this.get("_data"); for (k in _data) { if (_data.hasOwnProperty(k)) { v = _data[k]; diff --git a/addon/serializers/attachment.js b/addon/serializers/attachment.js index 381eb78..3fef613 100644 --- a/addon/serializers/attachment.js +++ b/addon/serializers/attachment.js @@ -4,9 +4,8 @@ export default DS.RESTSerializer.extend({ isNewSerializerAPI: true, primaryKey: "id", normalize: function (type, hash) { - var rev, self; - self = this; - rev = hash._rev || hash.rev; + var self = this, + rev = hash._rev || hash.rev; this.store.find(hash.model_name, hash.doc_id).then(function (document) { if (document.get("rev") !== rev) { if (self.getIntRevision(document.get("rev")) < self.getIntRevision(rev)) { diff --git a/addon/serializers/document.js b/addon/serializers/document.js index c2fa4e0..eb99399 100644 --- a/addon/serializers/document.js +++ b/addon/serializers/document.js @@ -39,13 +39,16 @@ export default DS.RESTSerializer.extend(sharedStore, { return this._super(snapshot, options); }, addHistoryId: function (hash) { - hash.history = Ember.String.fmt("%@/history", hash.id); + hash.history = hash.id + "/history"; return hash.history; }, normalizeAttachments: function (hash, type) { - var attachment, k, key, v, _attachments, attachments; - attachments = hash.doc && hash.doc._attachments || hash._attachments || hash.attachments; - _attachments = []; + var attachment, + k, + key, + v, + _attachments = [], + attachments = hash.doc && hash.doc._attachments || hash._attachments || hash.attachments; for (k in attachments) { if (attachments.hasOwnProperty(k)) { v = attachments[k]; @@ -81,9 +84,8 @@ export default DS.RESTSerializer.extend(sharedStore, { return hash.id; }, normalizeRelationships: function (type, hash) { - var key, payloadKey; - payloadKey = void 0; - key = void 0; + var key = void 0, + payloadKey = void 0; if (this.keyForRelationship) { return type.eachRelationship((function (key, relationship) { payloadKey = this.keyForRelationship(key, relationship.kind); @@ -96,7 +98,6 @@ export default DS.RESTSerializer.extend(sharedStore, { } }, normalizeDoc: function (hash) { - var k; if (hash.doc) { Ember.$.each(hash.doc, function (k, v) { hash[k] = v; @@ -106,10 +107,9 @@ export default DS.RESTSerializer.extend(sharedStore, { return hash; }, serializeBelongsTo: function (snapshot, json, relationship) { - var attribute, belongsTo, key; - attribute = relationship.options.attribute || "id"; - key = relationship.key; - belongsTo = snapshot.belongsTo(key); + var attribute = relationship.options.attribute || "id", + belongsTo = relationship.key, + key = snapshot.belongsTo(key); if (Ember.isNone(belongsTo)) { return; } @@ -120,10 +120,10 @@ export default DS.RESTSerializer.extend(sharedStore, { } }, serializeHasMany: function (snapshot, json, relationship) { - var attribute, key, relationshipType, keyArray; - attribute = relationship.options.attribute || "id"; - key = relationship.key; - relationshipType = snapshot.type.determineRelationshipType(relationship, this.get("store")); + var attribute = relationship.options.attribute || "id", + key = relationship.key, + relationshipType = snapshot.type.determineRelationshipType(relationship, this.get("store")), + keyArray; switch (relationshipType) { case "manyToNone": case "manyToMany": diff --git a/bower.json b/bower.json index de38ac3..46e16f1 100644 --- a/bower.json +++ b/bower.json @@ -2,10 +2,10 @@ "name": "ember-couch", "private": true, "dependencies": { - "ember": "^1.13.5", + "ember": "components/ember#canary", "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", - "ember-data": "1.13.6", + "ember-data": "components/ember-data#canary", "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", "ember-qunit": "0.4.1", "ember-qunit-notifications": "0.0.7", @@ -17,5 +17,9 @@ "devDependencies": { "bootstrap": "~3.3.5", "github-fork-ribbon-css": "~0.1.1" + }, + "resolutions": { + "ember": "canary", + "ember-data": "canary" } } diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js index a43baf7..a11e15b 100644 --- a/tests/dummy/app/controllers/index.js +++ b/tests/dummy/app/controllers/index.js @@ -41,7 +41,7 @@ export default Ember.Controller.extend({ _addAttachment: function (count, files, size, model, self) { var file = files[count], - attachmentId = Ember.String.fmt("%@/%@", model.id, file.name), + attachmentId = model.id + "/" + file.name, params = { doc_id: model.id, model_name: model._internalModel.modelName, @@ -52,9 +52,8 @@ export default Ember.Controller.extend({ length: file.size, file_name: file.name }, - attachment; + attachment = self.get("store").createRecord("attachment", params); - attachment = self.get("store").createRecord("attachment", params); attachment.save().then(function () { model.get("attachments").pushObject(attachment); model.reload(); diff --git a/tests/dummy/app/helpers/link-to-attachment.js b/tests/dummy/app/helpers/link-to-attachment.js index a597ef9..ec930ad 100644 --- a/tests/dummy/app/helpers/link-to-attachment.js +++ b/tests/dummy/app/helpers/link-to-attachment.js @@ -3,15 +3,15 @@ import Ember from "ember"; export default Ember.Helper.extend({ globals: Ember.inject.service(), compute(params) { - var aTagTemplate = "%@", - globals = this.get("globals"), - url = Ember.String.fmt("%@/%@/%@", globals.get("host"), globals.get("db"), params[0].get("id")), + var globals = this.get("globals"), + url = globals.get("host") + "/" + globals.get("db") + "/" + params[0].get("id"), self = this; if (params[0].get("isLoading")) { params[0]._internalModel._loadingPromise.then(function () { self.recompute(); }); } - return Ember.String.htmlSafe(Ember.String.fmt(aTagTemplate, url, params[0].get("file_name"))); + return Ember.String.htmlSafe("" + params[0].get("file_name") + ""); } }); + diff --git a/tests/dummy/app/routes/index.js b/tests/dummy/app/routes/index.js index 4b6faca..c6b385a 100644 --- a/tests/dummy/app/routes/index.js +++ b/tests/dummy/app/routes/index.js @@ -67,9 +67,9 @@ export default Ember.Route.extend({ _handlePositionChanges: function (data) { var self = this; data.forEach(function (obj) { - var position = self.controllerFor(obj.doc._id).get("position"); + var position = self.controllerFor(obj.id || obj.doc && obj.doc._id).get("position"); // we should reload particular postion model in case of update is received from another user - if (position.get("_data.rev") !== obj.doc._rev) { + if (position.get("rev") !== (obj.rev || obj.doc && obj.doc._rev)) { position.reload(); } }); @@ -99,9 +99,9 @@ export default Ember.Route.extend({ // apply received updates data.forEach(function (obj) { var issue = self.get("store").peekAll("issue").toArray().find(function (i) { - return i.get("id") === obj.doc._id; + return i.get("id") === (obj.id || obj.doc && obj.doc._id); }); - if (issue !== undefined && issue.get("_data.rev") !== obj.doc._rev) { + if (issue !== undefined && issue.get("rev") !== (obj.rev || obj.doc && obj.doc._rev)) { issue.reload(); } }); From c90ac3ae0af0291c4dd4b3abf46cfb2f5ccfe303 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Tue, 4 Aug 2015 13:27:55 -0400 Subject: [PATCH 45/48] Updated the README --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c93d3a1..f0609b7 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,26 @@ [![Build Status](https://travis-ci.org/ValidUSA/ember-couch.svg?branch=2.0.0-alpha)](https://travis-ci.org/ValidUSA/ember-couch)[![License](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE) -#### ember-couch +# Ember Couch An `ember-data` kit for Apache CouchDB. A collection of adapters to work with CouchDB documents, attachments, revisions, changes feed. Based off of [ember-couchdb-kit by Aleksey Zatvobor](https://github.com/Zatvobor/ember-couchdb-kit). +## Version +This addon is tested to work with versions of Ember and Ember-Data are: +* Ember 1.13.5, Ember-Data 1.13.6 + +## Installation +In your command prompt use: +`ember install ember-couch` + +## Features +Some notable features: +* natural `findRecord/createRecord/deleteRecord` functions; +* document's attachements designed as `hasMany` relationship; +* document's revisions designed as `belongsTo` and `hasMany` relationships; +* ability to work with `/_changes` feeds; + +For other features have a look at our dummy app located in +`tests/dummy/app` #### Contribution From f04ee823fea92254d8daaef316f73ac0b6fbc22c Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Tue, 4 Aug 2015 15:08:17 -0400 Subject: [PATCH 46/48] More changes to the README. --- README.md | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f0609b7..8b64166 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,53 @@ # Ember Couch -An `ember-data` kit for Apache CouchDB. A collection of adapters to work with CouchDB documents, attachments, revisions, changes feed. Based off of [ember-couchdb-kit by Aleksey Zatvobor](https://github.com/Zatvobor/ember-couchdb-kit). +An `ember-data` kit for Apache CouchDB. A collection of adapters to work with CouchDB documents, attachments, revisions, and the changes feed. Based off of [ember-couchdb-kit by Aleksey Zatvobor](https://github.com/Zatvobor/ember-couchdb-kit). ## Version -This addon is tested to work with versions of Ember and Ember-Data are: -* Ember 1.13.5, Ember-Data 1.13.6 - -## Installation -In your command prompt use: -`ember install ember-couch` +Version 0.0.1 of this addon is tested to work with Ember 1.13.6 and Ember Data 1.13.7. + +## Installation and Setup + ember install ember-couch + +In your adapters and serializers you must import then extend the adapter and serializer you wish to use from ember-couch. There are 3 adapters you can extend and 3 serializers you can extend. They are: +### Adapters +* DocumentAdapter +* AttachmentAdapter +* RevAdapter (experimental) +### Serializers +* DocumentSerializer +* AttachmentSerializer +* RevSerializer (experimental) + +Example adapter: +```js +import { DocumentAdapter } from 'ember-couch'; + +export default DocumentAdapter.extend({ + host: 'localhost:5984', + db: 'boards' +}); +``` +Example serializer: +```js +import { DocumentSerializer } from 'ember-couch'; + +export default DocumentSerializer.extend(); +``` + +If you would like to work with the changes feed, just add this statement to the top of your route: +```js +import { ChangesFeed } from "ember-couch"; +``` ## Features Some notable features: * natural `findRecord/createRecord/deleteRecord` functions; -* document's attachements designed as `hasMany` relationship; +* document's attachments designed as `hasMany` relationship; * document's revisions designed as `belongsTo` and `hasMany` relationships; * ability to work with `/_changes` feeds; -For other features have a look at our dummy app located in +For other features have a look at our example app located in `tests/dummy/app` #### Contribution From 2b8c7172fd2c028d02c897b810f9c222fbedf5f5 Mon Sep 17 00:00:00 2001 From: Sean McCrory Date: Tue, 4 Aug 2015 15:13:55 -0400 Subject: [PATCH 47/48] Fixed a formatting issue. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8b64166..709888f 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ In your adapters and serializers you must import then extend the adapter and ser * DocumentAdapter * AttachmentAdapter * RevAdapter (experimental) + ### Serializers * DocumentSerializer * AttachmentSerializer From 7bf64729c65b8f85db0f290b39ecde0a8622ccf9 Mon Sep 17 00:00:00 2001 From: Justin Daniel Date: Tue, 4 Aug 2015 15:17:26 -0400 Subject: [PATCH 48/48] Package.json changes --- package.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index bd06cbc..fd2360f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ember-couch", - "version": "2.0.0-alpha", + "version": "0.0.1", "description": "An Ember.js adapter for Apache CouchDB", "directories": { "doc": "doc", @@ -39,14 +39,15 @@ "ember-disable-prototype-extensions": "^1.0.0", "ember-disable-proxy-controllers": "^1.0.0", "ember-export-application-global": "^1.0.2", - "ember-try": "0.0.6", - "ember-watson": "0.6.0" + "ember-try": "0.0.6" }, "keywords": [ "ember-addon", "couch db", "couchdb", - "couch" + "couch", + "adapter", + "serializer" ], "dependencies": { "ember-cli-babel": "^5.0.0"