From c0397ae5d404c412672663b1ccd34059a804dc90 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 27 Aug 2013 15:54:22 +0200 Subject: [PATCH 01/33] Fix rendering on firefox Rendering on firefox was broken due to the difference in time when DOM loads - without deferReadiness it was sometimes failing to add ember-application element to body --- assets/scripts/travis.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/scripts/travis.coffee b/assets/scripts/travis.coffee index 5db8ece5..7f675729 100644 --- a/assets/scripts/travis.coffee +++ b/assets/scripts/travis.coffee @@ -64,6 +64,9 @@ window.Travis = TravisApplication.create( LOG_TRANSITIONS: true ) +unless window.testMode + Travis.deferReadiness() + $.extend Travis, run: -> Travis.advanceReadiness() # bc, remove once merged to master From b56fa7b9dfeb7a34807fe02156dc63697f32ab3c Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 27 Aug 2013 16:11:52 +0200 Subject: [PATCH 02/33] Fix getting hooks on profile page Hooks were sometimes not loaded, because user property on ProfileController was not available. This commit tries one additional way to get a login - Travis.lookup with controller:currentUser. --- assets/scripts/app/controllers/profile.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/scripts/app/controllers/profile.coffee b/assets/scripts/app/controllers/profile.coffee index 02800623..d7af56d4 100644 --- a/assets/scripts/app/controllers/profile.coffee +++ b/assets/scripts/app/controllers/profile.coffee @@ -35,7 +35,8 @@ Travis.ProfileController = Travis.Controller.extend @reloadHooks() reloadHooks: -> - @set('allHooks', Travis.Hook.find(all: true, owner_name: @get('params.login') || @get('user.login'))) + # TODO: figure out why user is not available sometimes + @set('allHooks', Travis.Hook.find(all: true, owner_name: @get('params.login') || @get('user.login') || Travis.lookup('controller:currentUser').get('login'))) hooks: (-> @get('allHooks').filter (hook) -> hook.get('admin') From 08d17fdf2b0ec31aeb2b5c265c4b20c8f4162508 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 27 Aug 2013 18:22:54 +0200 Subject: [PATCH 03/33] Check also for superclass of Travis.AuthRoute Ember.js creates subclasses of given class in container, so we need to check also superclass --- assets/scripts/app/routes.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts/app/routes.coffee b/assets/scripts/app/routes.coffee index cea215ed..2871fbba 100644 --- a/assets/scripts/app/routes.coffee +++ b/assets/scripts/app/routes.coffee @@ -46,7 +46,7 @@ Ember.Route.reopen Travis.auth.set('afterSignInTransition', null) transition.retry() else - @transitionTo('index.current') if @constructor == Travis.AuthRoute + @transitionTo('index.current') if @constructor == Travis.AuthRoute || @constructor.superclass == Travis.AuthRoute afterSignOut: -> @transitionTo('index.current') From 34ae33f0874c4d4d9faafba1ec9e8b45ce6495c3 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 27 Aug 2013 23:26:04 +0200 Subject: [PATCH 04/33] Fix filtering for queue and running jobs --- assets/scripts/app/models/job.coffee | 18 ++++++++++++++---- assets/scripts/vendor/ember-model.js | 10 ++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/assets/scripts/app/models/job.coffee b/assets/scripts/app/models/job.coffee index 5b588772..fc7df98a 100644 --- a/assets/scripts/app/models/job.coffee +++ b/assets/scripts/app/models/job.coffee @@ -104,8 +104,7 @@ require 'travis/model' @Travis.Job.reopenClass queued: (queue) -> - @find() - Ember.FilteredRecordArray.create( + filtered = Ember.FilteredRecordArray.create( modelClass: Travis.Job filterFunction: (job) -> queued = ['created', 'queued'].indexOf(job.get('state')) != -1 @@ -115,13 +114,24 @@ require 'travis/model' filterProperties: ['state', 'queue'] ) + @fetch(state: 'started').then (array) -> + filtered.updateFilter() + filtered.set('isLoaded', true) + + filtered + running: -> - @find(state: 'started') - Ember.FilteredRecordArray.create( + filtered = Ember.FilteredRecordArray.create( modelClass: Travis.Job filterFunction: (job) -> job.get('state') == 'started' filterProperties: ['state'] ) + @fetch(state: 'started').then (array) -> + filtered.updateFilter() + filtered.set('isLoaded', true) + + filtered + diff --git a/assets/scripts/vendor/ember-model.js b/assets/scripts/vendor/ember-model.js index 4bc88305..8a67eefc 100644 --- a/assets/scripts/vendor/ember-model.js +++ b/assets/scripts/vendor/ember-model.js @@ -186,6 +186,8 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ get(this, 'modelClass').forEachCachedRecord(function(record) { if (self.filterFunction(record)) { results.push(record); + } else { + results.removeObject(record); } }); this.set('content', Ember.A(results)); @@ -193,8 +195,12 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ updateFilterForRecord: function(record) { var results = get(this, 'content'); - if (this.filterFunction(record) && !results.contains(record)) { - results.pushObject(record); + if (this.filterFunction(record)) { + if(!results.contains(record)) { + results.pushObject(record); + } + } else { + results.removeObject(record); } }, From 18ba86eca0cd0f9dfe3a9884b1a21004d348b0c4 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 28 Aug 2013 14:17:26 +0200 Subject: [PATCH 05/33] Remove stuff not used in travis-web anymore --- assets/scripts/app/controllers.coffee | 2 - .../app/controllers/running_jobs.coffee | 103 ------------------ assets/scripts/app/controllers/sidebar.coffee | 41 ------- assets/scripts/app/models/job.coffee | 9 +- assets/scripts/app/templates/jobs/running.hbs | 7 -- .../app/templates/jobs/running/group.hbs | 15 --- assets/scripts/app/views.coffee | 1 - assets/scripts/app/views/sidebar.coffee | 87 --------------- assets/scripts/vendor/ember-model.js | 4 + assets/styles/right.sass | 19 ---- 10 files changed, 7 insertions(+), 281 deletions(-) delete mode 100644 assets/scripts/app/controllers/running_jobs.coffee delete mode 100644 assets/scripts/app/controllers/sidebar.coffee delete mode 100644 assets/scripts/app/templates/jobs/running.hbs delete mode 100644 assets/scripts/app/templates/jobs/running/group.hbs delete mode 100644 assets/scripts/app/views/sidebar.coffee diff --git a/assets/scripts/app/controllers.coffee b/assets/scripts/app/controllers.coffee index 85a32233..f1b4f520 100644 --- a/assets/scripts/app/controllers.coffee +++ b/assets/scripts/app/controllers.coffee @@ -57,8 +57,6 @@ require 'controllers/job' require 'controllers/profile' require 'controllers/repos' require 'controllers/repo' -#require 'controllers/running_jobs' -#require 'controllers/sidebar' require 'controllers/stats' require 'controllers/current_user' require 'controllers/account_index' diff --git a/assets/scripts/app/controllers/running_jobs.coffee b/assets/scripts/app/controllers/running_jobs.coffee deleted file mode 100644 index 4d81a0e8..00000000 --- a/assets/scripts/app/controllers/running_jobs.coffee +++ /dev/null @@ -1,103 +0,0 @@ -Travis.RunningJobsController = Em.ArrayProxy.extend - Group: Em.Object.extend - slug: (-> @get('jobs.firstObject.repoSlug') ).property('jobs.firstObject.repoSlug') - - init: -> - @_super.apply this, arguments - @set 'jobs', [] - - @set 'sortedJobs', Em.ArrayProxy.extend(Em.SortableMixin, - sortProperties: ['number'] - ).create(content: @get('jobs')) - - willDestroy: -> - @get('sortedJobs').destroy() - - add: (job) -> - @get('jobs').pushObject(job) unless @get('jobs').contains job - @attach() - - remove: (job) -> - @get('jobs').removeObject(job) - @clean() - - attach: -> - @get('parent').addGroup(this) - - clean: -> - @get('parent').removeGroup(this) if @get('isEmpty') - - isEmpty: (-> - @get('jobs.length') == 0 - ).property('jobs.length') - - groups: [] - sortedGroups: (-> - Em.ArrayProxy.extend(Em.SortableMixin, - sortProperties: ['slug'] - ).create(content: @get('groups')) - ).property() - - groupsBySlug: {} - - init: -> - @_super.apply this, arguments - - jobs = Travis.Job.running() - @set 'content', jobs - @addedJobs jobs - - contentArrayWillChange: (array, index, removedCount, addedCount) -> - @_super.apply this, arguments - - if removedCount - @removedJobs array.slice(index, index + removedCount) - - contentArrayDidChange: (array, index, removedCount, addedCount) -> - @_super.apply this, arguments - - if addedCount - @addedJobs array.slice(index, index + addedCount) - - addedJobs: (jobs) -> - self = this - jobs.forEach (job) -> - self.waitForSlug(job, 'addJob') - - removedJobs: (jobs) -> - self = this - jobs.forEach (job) -> - self.waitForSlug(job, 'removeJob') - - addJob: (job) -> - slug = job.get('repoSlug') - group = @groupForSlug(slug) - group.add(job) - - removeJob: (job) -> - slug = job.get('repoSlug') - group = @groupForSlug(slug) - group.remove(job) - - waitForSlug: (job, callbackName) -> - callback = @[callbackName] - self = this - if job.get('repoSlug')? - callback.call self, job - else - observer = -> - if job.get('repoSlug')? - callback.call self, job - job.removeObserver 'repoSlug', observer - job.addObserver 'repoSlug', observer - - groupForSlug: (slug) -> - @groupsBySlug[slug] ||= @Group.create(slug: slug, parent: this) - - addGroup: (group) -> - @get('groups').pushObject group unless @get('groups').contains group - - removeGroup: (group) -> - @get('groups').removeObject group - delete @groupsBySlug[group.get('slug')] - group.destroy() diff --git a/assets/scripts/app/controllers/sidebar.coffee b/assets/scripts/app/controllers/sidebar.coffee deleted file mode 100644 index 4673e14c..00000000 --- a/assets/scripts/app/controllers/sidebar.coffee +++ /dev/null @@ -1,41 +0,0 @@ -Travis.reopen - SidebarController: Em.ArrayController.extend - needs: ['runningJobs'] - jobsBinding: 'controllers.runningJobs' - - init: -> - @_super.apply this, arguments - - QueuesController: Em.ArrayController.extend - init: -> - @_super.apply this, arguments - - queues = for queue in Travis.QUEUES - Travis.LimitedArray.create - content: Travis.Job.queued(queue.name), limit: 20 - id: "queue_#{queue.name}" - name: queue.display - @set 'content', queues - - showAll: (queue) -> - queue.showAll() - - WorkersController: Em.ArrayController.extend - init: -> - @_super.apply this, arguments - @set 'content', Travis.Worker.find() - - groups: (-> - if content = @get 'arrangedContent' - groups = {} - for worker in content.toArray() - host = worker.get('host') - unless groups[host] - groups[host] = Em.ArrayProxy.extend(Em.SortableMixin, - content: [], - sortProperties: ['nameForSort'] - ).create() - groups[host].pushObject(worker) - - $.values(groups) - ).property('length') diff --git a/assets/scripts/app/models/job.coffee b/assets/scripts/app/models/job.coffee index fc7df98a..cbda6284 100644 --- a/assets/scripts/app/models/job.coffee +++ b/assets/scripts/app/models/job.coffee @@ -103,18 +103,15 @@ require 'travis/model' ).property('state') @Travis.Job.reopenClass - queued: (queue) -> + queued: -> filtered = Ember.FilteredRecordArray.create( modelClass: Travis.Job filterFunction: (job) -> - queued = ['created', 'queued'].indexOf(job.get('state')) != -1 - # TODO: why queue is sometimes just common instead of build.common? - queued && (!queue || job.get('queue') == "builds.#{queue}" || job.get('queue') == queue) - + ['created', 'queued'].indexOf(job.get('state')) != -1 filterProperties: ['state', 'queue'] ) - @fetch(state: 'started').then (array) -> + @fetch().then (array) -> filtered.updateFilter() filtered.set('isLoaded', true) diff --git a/assets/scripts/app/templates/jobs/running.hbs b/assets/scripts/app/templates/jobs/running.hbs deleted file mode 100644 index 6be98a9a..00000000 --- a/assets/scripts/app/templates/jobs/running.hbs +++ /dev/null @@ -1,7 +0,0 @@ -
    - {{#each group in view.groups}} - {{view view.GroupView groupBinding="group"}} - {{else}} - {{t no_job}} - {{/each}} -
diff --git a/assets/scripts/app/templates/jobs/running/group.hbs b/assets/scripts/app/templates/jobs/running/group.hbs deleted file mode 100644 index eef5813f..00000000 --- a/assets/scripts/app/templates/jobs/running/group.hbs +++ /dev/null @@ -1,15 +0,0 @@ - - {{slug}} ({{jobs.length}}) - - -
    - {{#each job in sortedJobs}} -
  • - {{#if job.repo.slug}} - {{#linkTo "job" job.repo job}} - #{{job.number}} - {{/linkTo}} - {{/if}} -
  • - {{/each}} -
diff --git a/assets/scripts/app/views.coffee b/assets/scripts/app/views.coffee index 1d61aea1..ae5928c1 100644 --- a/assets/scripts/app/views.coffee +++ b/assets/scripts/app/views.coffee @@ -54,7 +54,6 @@ require 'views/job' require 'views/log' require 'views/repo' require 'views/profile' -require 'views/sidebar' require 'views/stats' require 'views/signin' require 'views/top' diff --git a/assets/scripts/app/views/sidebar.coffee b/assets/scripts/app/views/sidebar.coffee deleted file mode 100644 index 5fe8bc26..00000000 --- a/assets/scripts/app/views/sidebar.coffee +++ /dev/null @@ -1,87 +0,0 @@ -@Travis.reopen - SidebarView: Travis.View.extend - templateName: 'layouts/sidebar' - - didInsertElement: -> - @_super.apply this, arguments - - #@activate('jobs') - - activate: (name) -> - console.log - return if @get('activeTab') == name - @set('activeTab', name) - @connectOutlet 'pane', Travis.SidebarView["#{name.capitalize()}View"].create(controller: @get('controller')) - - classQueues: (-> - 'active' if @get('activeTab') == 'queues' - ).property('activeTab') - - classWorkers: (-> - 'active' if @get('activeTab') == 'workers' - ).property('activeTab') - - classJobs: (-> - 'active' if @get('activeTab') == 'jobs' - ).property('activeTab') - - QueuesView: Em.View.extend - templateName: 'queues/list' - init: -> - @_super.apply this, arguments - @set 'controller', @get('controller').container.lookup('controller:queues') - - WorkersView: Travis.View.extend - toggleWorkers: -> - handle = $(event.target).toggleClass('open') - if handle.hasClass('open') - $('#workers li').addClass('open') - else - $('#workers li').removeClass('open') - - WorkersListView: Travis.View.extend - toggle: -> - this.$().find('> li').toggleClass('open') - - WorkersItemView: Travis.View.extend - classNameBindings: ['worker.state'] - - display: (-> - name = (@get('worker.name') || '').replace('travis-', '') - state = @get('worker.state') - - if state == 'working' - "#{name}: #{@get('worker.repoSlug')} ##{@get('worker.jobNumber')}".htmlSafe() - else - "#{name}: #{state}" - ).property('worker.state') - - QueueItemView: Travis.View.extend - tagName: 'li' - -Travis.SidebarView.reopenClass - WorkersView: Em.View.extend - templateName: 'workers/list' - init: -> - @_super.apply this, arguments - @set 'controller', @get('controller').container.lookup('controller:workers') - - JobsView: Em.View.extend - templateName: 'jobs/running' - elementId: 'running-jobs' - init: -> - @_super.apply this, arguments - @set 'controller', @get('controller').container.lookup('controller:runningJobs') - - groupsBinding: 'controller.sortedGroups' - jobsBinding: 'controller' - - GroupView: Em.View.extend - templateName: 'jobs/running/group' - tagName: 'li' - contextBinding: 'group' - expanded: false - classNameBindings: ['expanded'] - classNames: ['group'] - toggle: -> - @toggleProperty('expanded') diff --git a/assets/scripts/vendor/ember-model.js b/assets/scripts/vendor/ember-model.js index 8a67eefc..a5eea67b 100644 --- a/assets/scripts/vendor/ember-model.js +++ b/assets/scripts/vendor/ember-model.js @@ -438,6 +438,10 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { init: function() { this._createReference(); this._super(); + + this.one('didLoad', function() { + this.constructor.addToRecordArrays(this); + }); }, _createReference: function() { diff --git a/assets/styles/right.sass b/assets/styles/right.sass index 4d40b06d..a1049a9a 100644 --- a/assets/styles/right.sass +++ b/assets/styles/right.sass @@ -41,22 +41,3 @@ #right .show-more-jobs text-decoration: underline cursor: pointer - -#running-jobs - .jobs - display: none - margin: 5px 0 5px - .job - list-style-type: disc - list-style-position: inside - .expanded .jobs - display: block - .slug - max-width: 150px - overflow: hidden - white-space: nowrap - text-overflow: ellipsis - display: inline-block - display: -moz-inline-stack - .group a - cursor: pointer From fb120af69eebbd92d1f0b06e8ca4c9a55bb01185 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 29 Aug 2013 01:01:03 +0200 Subject: [PATCH 06/33] Use pullRequestNumber from build, not from commit --- assets/scripts/app/models/build.coffee | 2 +- assets/scripts/app/models/commit.coffee | 1 - assets/scripts/app/views/build.coffee | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/assets/scripts/app/models/build.coffee b/assets/scripts/app/models/build.coffee index 3f9fa2d3..4f74436c 100644 --- a/assets/scripts/app/models/build.coffee +++ b/assets/scripts/app/models/build.coffee @@ -25,7 +25,7 @@ require 'travis/model' ).property('_config') isPullRequest: (-> - @get('eventType') == 'pull_request' + @get('eventType') == 'pull_request' || @get('pullRequest') ).property('eventType') isMatrix: (-> diff --git a/assets/scripts/app/models/commit.coffee b/assets/scripts/app/models/commit.coffee index f756c4e1..bea75130 100644 --- a/assets/scripts/app/models/commit.coffee +++ b/assets/scripts/app/models/commit.coffee @@ -10,6 +10,5 @@ require 'travis/model' authorEmail: Ember.attr('string') committerName: Ember.attr('string') committerEmail: Ember.attr('string') - pullRequestNumber: Ember.attr('number') build: Ember.belongsTo('Travis.Build') diff --git a/assets/scripts/app/views/build.coffee b/assets/scripts/app/views/build.coffee index f0a79958..0fab33d0 100644 --- a/assets/scripts/app/views/build.coffee +++ b/assets/scripts/app/views/build.coffee @@ -41,8 +41,8 @@ Travis.reopen ).property('repo.slug', 'commit.sha') urlGithubPullRequest: (-> - Travis.Urls.githubPullRequest(@get('repo.slug'), @get('commit.pullRequestNumber')) - ).property('repo.slug', 'commit.pullRequestNumber') + Travis.Urls.githubPullRequest(@get('repo.slug'), @get('build.pullRequestNumber')) + ).property('repo.slug', 'build.pullRequestNumber') BuildView: Travis.View.extend templateName: 'builds/show' From 358b7cc0c0d3c5ce2ac98aa05643b77eb1d9cd90 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 29 Aug 2013 01:01:28 +0200 Subject: [PATCH 07/33] Don't add records to expandable array if they're already there --- assets/scripts/lib/travis/expandable_record_array.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/assets/scripts/lib/travis/expandable_record_array.coffee b/assets/scripts/lib/travis/expandable_record_array.coffee index 2d377986..778b9f6b 100644 --- a/assets/scripts/lib/travis/expandable_record_array.coffee +++ b/assets/scripts/lib/travis/expandable_record_array.coffee @@ -12,7 +12,7 @@ Travis.ExpandableRecordArray = Ember.RecordArray.extend array.removeObserver 'isLoaded', observer array.forEach (record) -> - self.pushObject record + self.pushObject(record) unless self.contains(record) self.set 'isLoading', false self.set 'isLoaded', true @@ -30,7 +30,8 @@ Travis.ExpandableRecordArray = Ember.RecordArray.extend addedObjects = array.slice index, index + addedCount for object in addedObjects if @get('filterWith').call this, object - @pushObject object + @pushObject(object) unless @contains(object) pushObject: (record) -> - @get('content').pushObject(record) + if content = @get('content') + content.pushObject(record) unless content.contains(record) From f82707b83221553d592adae91311a745800f0228 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 29 Aug 2013 01:15:46 +0200 Subject: [PATCH 08/33] Always run deferReadiness() --- assets/scripts/travis.coffee | 3 +-- public/spec.html | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/scripts/travis.coffee b/assets/scripts/travis.coffee index 7f675729..76d37396 100644 --- a/assets/scripts/travis.coffee +++ b/assets/scripts/travis.coffee @@ -64,8 +64,7 @@ window.Travis = TravisApplication.create( LOG_TRANSITIONS: true ) -unless window.testMode - Travis.deferReadiness() +Travis.deferReadiness() $.extend Travis, run: -> diff --git a/public/spec.html b/public/spec.html index a5d217cb..84cbaef2 100644 --- a/public/spec.html +++ b/public/spec.html @@ -53,6 +53,7 @@ From 27929faf6ff680e0e80687c75c33f6dcbe828e18 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 29 Aug 2013 01:15:57 +0200 Subject: [PATCH 09/33] Don't render sidebar --- assets/scripts/app/routes.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/scripts/app/routes.coffee b/assets/scripts/app/routes.coffee index 2871fbba..8b0f36e2 100644 --- a/assets/scripts/app/routes.coffee +++ b/assets/scripts/app/routes.coffee @@ -306,7 +306,6 @@ Travis.IndexRoute = Ember.Route.extend $('body').attr('id', 'home') @render 'repos', outlet: 'left' - @render 'sidebar', outlet: 'right' @render 'top', outlet: 'top' @render 'flash', outlet: 'flash' From 30d273a64834cd54bf47518859264508e99801c6 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 29 Aug 2013 01:19:35 +0200 Subject: [PATCH 10/33] Fix specs --- assets/scripts/spec/integration/event_spec.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/scripts/spec/integration/event_spec.coffee b/assets/scripts/spec/integration/event_spec.coffee index 79dbc332..9c2ba882 100644 --- a/assets/scripts/spec/integration/event_spec.coffee +++ b/assets/scripts/spec/integration/event_spec.coffee @@ -50,6 +50,9 @@ test "an event with a build adds a build to a builds list", -> message: 'commit message 3' commit: '1234567' state: 'failed' + pull_request: false + pull_request_number: null + pull_request_title: null Em.run -> Travis.receive 'build:started', payload From 14d05962490b66a861e1c10a2a8ec981ff30a54a Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 29 Aug 2013 03:36:01 +0200 Subject: [PATCH 11/33] Update travis-web-log --- assets/scripts/vendor/log.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts/vendor/log.js b/assets/scripts/vendor/log.js index 79b9a9c9..f50756a7 100644 --- a/assets/scripts/vendor/log.js +++ b/assets/scripts/vendor/log.js @@ -1 +1 @@ -minispade.register('log', "(function() {(function() {\n\n this.Log = function() {\n this.listeners = [];\n this.renderer = new Log.Renderer;\n this.children = new Log.Nodes(this);\n this.parts = {};\n this.folds = new Log.Folds;\n return this;\n };\n\n Log.extend = function(one, other) {\n var name;\n for (name in other) {\n one[name] = other[name];\n }\n return one;\n };\n\n Log.extend(Log, {\n DEBUG: true,\n SLICE: 500,\n TIMEOUT: 25,\n FOLD: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(options) {\n var listener, log, _i, _len, _ref;\n options || (options = {});\n log = new Log();\n if (options.limit) {\n log.listeners.push(log.limit = new Log.Limit(options.limit));\n }\n _ref = options.listeners || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n listener = _ref[_i];\n log.listeners.push(listener);\n }\n return log;\n }\n });\nminispade.require('log/nodes');\n\n Log.prototype = Log.extend(new Log.Node, {\n set: function(num, string) {\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n this.parts[num] = true;\n return Log.Part.create(this, num, string);\n }\n },\n insert: function(data, pos) {\n this.trigger('insert', data, pos);\n return this.renderer.insert(data, pos);\n },\n remove: function(node) {\n this.trigger('remove', node);\n return this.renderer.remove(node);\n },\n hide: function(node) {\n this.trigger('hide', node);\n return this.renderer.hide(node);\n },\n trigger: function() {\n var args, ix, listener, _i, _len, _ref, _results;\n args = [this].concat(Array.prototype.slice.apply(arguments));\n _ref = this.listeners;\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n listener = _ref[ix];\n _results.push(listener.notify.apply(listener, args));\n }\n return _results;\n }\n });\n\n Log.Listener = function() {};\n\n Log.extend(Log.Listener.prototype, {\n notify: function(log, event) {\n if (this[event]) {\n return this[event].apply(this, [log].concat(Array.prototype.slice.call(arguments, 2)));\n }\n }\n });\nminispade.require('log/folds');\nminispade.require('log/deansi');\nminispade.require('log/limit');\nminispade.require('log/renderer');\n\n}).call(this);\n\n})();\n//@ sourceURL=log");minispade.register('log.old/buffer', "(function() {(function() {\n\n Log.Buffer = function(log, options) {\n this.start = 0;\n this.log = log;\n this.parts = [];\n this.options = $.extend({\n interval: 100,\n timeout: 500\n }, options || {});\n this.schedule();\n return this;\n };\n\n $.extend(Log.Buffer.prototype, {\n set: function(num, string) {\n return this.parts[num] = {\n string: string,\n time: (new Date).getTime()\n };\n },\n flush: function() {\n var num, part, _i, _len, _ref;\n _ref = this.parts;\n for (num = _i = 0, _len = _ref.length; _i < _len; num = ++_i) {\n part = _ref[num];\n if (!this.parts.hasOwnProperty(num)) {\n continue;\n }\n if (!part) {\n break;\n }\n delete this.parts[num];\n this.log.set(num, part.string);\n }\n return this.schedule();\n },\n schedule: function() {\n var _this = this;\n return setTimeout((function() {\n return _this.flush();\n }), this.options.interval);\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/buffer");minispade.register('log.old/engine/chunks', "(function() {(function() {\n\n Log.Chunks = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Chunks.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Chunks.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Chunks.Part = function(engine, num, string) {\n var chunk, ix, line, type;\n this.engine = engine;\n this.num = num;\n this.chunks = (function() {\n var _i, _len, _ref, _results;\n _ref = string.split(/^/m);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n chunk = _ref[ix];\n line = chunk[chunk.length - 1].match(/\\r|\\n/);\n type = line ? 'Line' : 'Chunk';\n _results.push(new Log.Chunks[type](this, ix, chunk));\n }\n return _results;\n }).call(this);\n return this;\n };\n\n $.extend(Log.Chunks.Part.prototype, {\n insert: function() {\n var chunk, _i, _len, _ref, _results;\n _ref = this.chunks;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n chunk = _ref[_i];\n _results.push(chunk.insert());\n }\n return _results;\n },\n prev: function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.engine.parts[num -= 1];\n }\n return prev;\n },\n next: function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.engine.parts.length)) {\n next = this.engine.parts[num += 1];\n }\n return next;\n },\n trigger: function() {\n return this.engine.trigger.apply(this.engine, arguments);\n }\n });\n\n Log.Chunks.Chunk = function(part, num, string) {\n this.part = part;\n this.num = num;\n this.string = string;\n this.id = \"\" + (part != null ? part.num : void 0) + \"-\" + num;\n this.isFold = (string != null ? string.indexOf('fold') : void 0) !== -1;\n if (string) {\n this.nodes = this.parse();\n }\n return this;\n };\n\n $.extend(Log.Chunks.Chunk.prototype, {\n parse: function() {\n return [\n {\n type: 'span',\n id: \"\" + this.id + \"-0\",\n text: this.string\n }\n ];\n },\n insert: function() {\n var next, prev;\n if ((next = this.next()) && next.isLine) {\n return this.trigger('insert', this.nodes, {\n before: next.nodes[0].nodes[0].id\n });\n } else if (next) {\n return this.trigger('insert', this.nodes, {\n before: next.nodes[0].id\n });\n } else if ((prev = this.prev()) && !prev.isLine) {\n return this.trigger('insert', this.nodes, {\n after: prev.nodes[prev.nodes.length - 1].id\n });\n } else {\n return this.trigger('insert', [\n {\n type: 'paragraph',\n id: this.id,\n nodes: this.nodes\n }\n ], {\n before: void 0\n });\n }\n },\n remove: function() {\n var node, _i, _len, _ref, _results;\n _ref = this.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n this.trigger('remove', this.id);\n if (node.nodes) {\n _results.push((function() {\n var _j, _len1, _ref1, _results1;\n _ref1 = node.nodes;\n _results1 = [];\n for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {\n node = _ref1[_j];\n _results1.push(this.trigger('remove', node.id));\n }\n return _results1;\n }).call(this));\n } else {\n _results.push(void 0);\n }\n }\n return _results;\n },\n reinsert: function() {\n this.remove();\n return this.insert();\n },\n prevLine: function() {\n var prev;\n prev = this.prev();\n while (prev && !prev.isLine) {\n prev = prev.prev();\n }\n return prev;\n },\n nextLine: function() {\n var next;\n next = this.next();\n while (next && !next.isLine) {\n next = next.next();\n }\n return next;\n },\n prev: function() {\n var chunk, _ref;\n chunk = this.part.chunks[this.num - 1];\n return chunk || ((_ref = this.part.prev()) != null ? _ref.chunks.slice(-1)[0] : void 0);\n },\n next: function() {\n var chunk, _ref;\n chunk = this.part.chunks[this.num + 1];\n return chunk || ((_ref = this.part.next()) != null ? _ref.chunks[0] : void 0);\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Chunks.Line = function(part, num, string) {\n Log.Chunks.Chunk.call(this, part, num, string.slice(0, string.length - 1));\n this.isLine = true;\n return this;\n };\n\n Log.Chunks.Line.prototype = $.extend(new Log.Chunks.Chunk, {\n parse: function() {\n return [\n {\n type: 'paragraph',\n id: this.id,\n nodes: [\n {\n type: 'span',\n id: \"\" + this.id + \"-0\",\n text: this.string\n }\n ]\n }\n ];\n },\n insert: function() {\n var next, prev;\n if ((prev = this.prev()) && !prev.isLine) {\n this.trigger('insert', this.nodes[0].nodes, {\n after: prev.nodes[0].id\n });\n document.getElementById(this.nodes[0].nodes[0].id).parentNode.setAttribute('id', this.id);\n if (this.isLine && (next = this.next())) {\n return next.reinsert();\n }\n } else if (prev) {\n return this.trigger('insert', this.nodes, {\n after: prev.id\n });\n } else if (next = this.nextLine()) {\n return this.trigger('insert', this.nodes, {\n before: next.id\n });\n } else {\n return this.trigger('insert', this.nodes, {\n before: void 0\n });\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/chunks");minispade.register('log.old/engine/dom', "(function() {(function() {\n\n Log.Dom = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Dom.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Dom.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Dom.Part = function(engine, num, string) {\n this.engine = engine;\n this.num = num;\n this.string = string.replace(/\\r\\n/gm, '\\n');\n this.nodes = new Log.Dom.Nodes(this);\n return this;\n };\n\n $.extend(Log.Dom.Part.prototype, {\n SLICE: 500,\n insert: function() {\n var ix, lines, next, slices,\n _this = this;\n lines = this.string.split(/^/gm) || [];\n slices = ((function() {\n var _results;\n _results = [];\n while (lines.length > 0) {\n _results.push(lines.splice(0, this.SLICE));\n }\n return _results;\n }).call(this));\n ix = -1;\n next = function() {\n _this.insertSlice(slices.shift(), ix += 1);\n if (slices.length !== 0) {\n return setTimeout(next, 50);\n }\n };\n return next();\n },\n insertSlice: function(lines, start) {\n var ix, line, node, _i, _len, _ref, _ref1, _results;\n _ref = lines || [];\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n if ((_ref1 = this.engine.log.limit) != null ? _ref1.limited : void 0) {\n break;\n }\n node = Log.Dom.Node.create(this, start * this.SLICE + ix, line);\n _results.push(this.nodes.insert(node));\n }\n return _results;\n },\n remove: function() {\n return delete this.engine.parts[this.num];\n },\n trigger: function() {\n return this.engine.trigger.apply(this.engine, arguments);\n }\n });\n\n Log.Dom.Part.prototype.__defineGetter__('prev', function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.engine.parts[num -= 1];\n }\n return prev;\n });\n\n Log.Dom.Part.prototype.__defineGetter__('next', function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.engine.parts.length)) {\n next = this.engine.parts[num += 1];\n }\n return next;\n });\n\n Log.Dom.Nodes = function(part) {\n this.part = part;\n this.nodes = [];\n return this;\n };\n\n $.extend(Log.Dom.Nodes.prototype, {\n at: function(ix) {\n return this.nodes[ix];\n },\n insert: function(node) {\n this.nodes[node.num] = node;\n return node.insert();\n },\n remove: function(node) {\n this.nodes.splice(node.num, 1);\n if (this.nodes.length === 0) {\n return this.part.remove();\n }\n }\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('length', function() {\n return this.nodes.length;\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('first', function() {\n return this.nodes[0];\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('last', function() {\n return this.nodes[this.nodes.length - 1];\n });\n\n Log.Dom.Node = function() {};\n\n $.extend(Log.Dom.Node, {\n FOLDS_PATTERN: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(part, num, string) {\n var fold;\n if (fold = string.match(this.FOLDS_PATTERN)) {\n return new Log.Dom.Fold(part, num, fold[1], fold[2]);\n } else {\n return new Log.Dom.Paragraph(part, num, string);\n }\n },\n reinsert: function(nodes) {\n var node, _i, _j, _len, _len1;\n console.log(\"reinsert: \" + (nodes.map(function(node) {\n return node.id;\n }).join(', ')));\n for (_i = 0, _len = nodes.length; _i < _len; _i++) {\n node = nodes[_i];\n node.remove();\n }\n for (_j = 0, _len1 = nodes.length; _j < _len1; _j++) {\n node = nodes[_j];\n node.part.nodes.insert(node);\n }\n return console.log(document.firstChild.innerHTML.replace(/<\\/p>/gm, '

\\n') + '\\n');\n }\n });\n\n Log.Dom.Node.prototype.__defineGetter__('prev', function() {\n var num, prev, _ref;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.part.nodes.at(num -= 1);\n }\n return prev || ((_ref = this.part.prev) != null ? _ref.nodes.last : void 0);\n });\n\n Log.Dom.Node.prototype.__defineGetter__('next', function() {\n var next, num, _ref;\n num = this.num;\n while (!(next || num >= this.part.nodes.length)) {\n next = this.part.nodes.at(num += 1);\n }\n return next || ((_ref = this.part.next) != null ? _ref.nodes.first : void 0);\n });\n\n Log.Dom.Fold = function(part, num, event, name) {\n this.part = part;\n this.ends = true;\n this.fold = true;\n this.num = num;\n this.id = \"fold-\" + event + \"-\" + name;\n this.data = {\n type: 'fold',\n id: this.id,\n event: event,\n name: name\n };\n return this;\n };\n\n Log.Dom.Fold.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n return fail;\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Dom.Paragraph = function(part, num, string) {\n var span, _ref;\n this.part = part;\n this.num = num;\n this.ends = !!((_ref = string[string.length - 1]) != null ? _ref.match(/\\n/) : void 0);\n this.spans = new Log.Dom.Spans(this, string.replace(/\\n$/, ''));\n this.data = {\n type: 'paragraph',\n num: this.part.num,\n hidden: this.hidden,\n nodes: (function() {\n var _i, _len, _ref1, _results;\n _ref1 = this.spans.content;\n _results = [];\n for (_i = 0, _len = _ref1.length; _i < _len; _i++) {\n span = _ref1[_i];\n _results.push(span.data);\n }\n return _results;\n }).call(this)\n };\n return this;\n };\n\n Log.Dom.Paragraph.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n var next, prev, span, _i, _j, _len, _len1, _ref, _ref1, _ref2, _results;\n if ((prev = this.prev) && !prev.ends && !prev.fold) {\n if (Log.DEBUG) {\n console.log(\"P.1 - move \" + this.id + \"'s spans into prev\");\n }\n _ref = this.spans.content;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n prev.spans.append(span);\n }\n return this.part.nodes.remove(this);\n } else if ((next = this.next) && !this.ends && !next.fold) {\n if (Log.DEBUG) {\n console.log(\"P.2 - move \" + this.id + \"'s spans into next\");\n }\n _ref1 = this.spans.reverse();\n _results = [];\n for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {\n span = _ref1[_j];\n _results.push(next.prepend(span));\n }\n return _results;\n } else if ((prev != null ? prev.fold : void 0) && (prev != null ? (_ref2 = prev.element.getAttribute('class')) != null ? _ref2.match(' fold') : void 0 : void 0)) {\n if (Log.DEBUG) {\n console.log(\"P.3 - append \" + this.id + \" to fold \" + prev.id);\n }\n return this.element = this.trigger('insert', this.data, {\n after: prev.element.firstChild\n });\n } else if (prev) {\n if (Log.DEBUG) {\n console.log(\"P.4 - insert \" + this.id + \" after the parentNode of the last node of prev, id \" + prev.id);\n }\n return this.element = this.trigger('insert', this.data, {\n after: prev.element\n });\n } else if (next) {\n if (Log.DEBUG) {\n console.log(\"P.5 - insert \" + this.id + \" before the parentNode of the first node of next, id \" + next.id);\n }\n return this.element = this.trigger('insert', this.data, {\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"P.6 - insert \" + this.id + \" at the beginning of #log\");\n }\n return this.element = this.trigger('insert', this.data);\n }\n },\n remove: function() {\n var element, span, _i, _len, _ref;\n element = this.element;\n _ref = this.spans;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n this.trigger('remove', span.element);\n }\n if (!(element.childNodes.length > 1)) {\n this.trigger('remove', element);\n }\n return this.part.nodes.remove(this);\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('id', function() {\n return \"\" + this.part.num + \"-\" + this.num;\n });\n\n Log.Dom.Paragraph.prototype.__defineSetter__('element', function(element) {\n var child, span, _i, _len, _ref, _results;\n child = element.firstChild;\n _ref = this.spans.content;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n _results.push(span.element = child = child.nextSibling);\n }\n return _results;\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('element', function() {\n return this.spans.first.element.parentNode;\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('tail', function() {\n var next, parent, tail, _ref;\n parent = this.element.parentNode;\n next = this;\n tail = [];\n while ((next = next.next) && !next.fold && ((_ref = next.element) != null ? _ref.parentNode : void 0) === parent) {\n tail.push(next);\n }\n return tail;\n });\n\n Log.Dom.Spans = function(parent, string) {\n this.parent = parent;\n this.content = this.parse(parent, string);\n return this;\n };\n\n $.extend(Log.Dom.Spans.prototype, {\n parse: function(parent, string) {\n var ix, span, _i, _len, _ref, _results;\n _ref = Log.Deansi.apply(string);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n span = _ref[ix];\n _results.push(new Log.Dom.Span(parent, ix, span));\n }\n return _results;\n },\n at: function(ix) {\n return this.content[ix];\n },\n indexOf: function(span) {\n return this.content.indexOf(span);\n },\n append: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.push(span);\n return span.insert();\n },\n prepend: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.unshift(span);\n return span.insert();\n },\n reverse: function() {\n return this.content.reverse();\n },\n remove: function(span) {\n return this.content.splice(this.content.indexOf(span), 1);\n }\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('last', function() {\n return this.content[this.length - 1];\n });\n\n Log.Dom.Span = function(parent, num, data) {\n this.parent = parent;\n this.num = num;\n this.id = \"\" + parent.id + \"-\" + num;\n this.data = $.extend(data, {\n id: this.id\n });\n if (data.text.match(/\\r/)) {\n this.hidden = true;\n }\n this.data.text = data.text.replace(/^.*\\r/gm, '');\n if (this.hidden) {\n this.data[\"class\"] = ['hidden'];\n }\n return this;\n };\n\n $.extend(Log.Dom.Span.prototype, {\n insert: function() {\n var next, prev;\n if (prev = this.prev) {\n if (Log.DEBUG) {\n console.log(\"S.1 - insert \" + this.id + \" after prev \" + prev.id);\n }\n return this.insertAt({\n after: prev.element\n });\n } else if (next = this.next) {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" before next \" + next.id);\n }\n return this.insertAt({\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" into parent \" + this.parent.id);\n }\n return this.insertAt({\n into: this.parent.element\n });\n }\n },\n insertAt: function(pos) {\n var span, _i, _len, _ref, _results;\n this.element = this.trigger('insert', this.data, pos);\n if (this.hidden) {\n _ref = this.head;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n _results.push(span.hide());\n }\n return _results;\n } else {\n if (this.tail.some(function(span) {\n return span.hidden;\n })) {\n return this.hide();\n }\n }\n },\n hide: function() {\n if (!this.hidden) {\n this.trigger('hide', this.id);\n }\n return this.hidden = true;\n },\n siblings: function(direction) {\n var siblings, span, _ref;\n siblings = [];\n span = this;\n while ((span = span[direction]) && ((_ref = span.element) != null ? _ref.parentNode : void 0) === this.element.parentNode) {\n siblings.unshift(span);\n }\n return siblings;\n },\n trigger: function() {\n return this.parent.trigger.apply(this.parent, arguments);\n }\n });\n\n Log.Dom.Span.prototype.__defineGetter__('head', function() {\n return this.siblings('prev');\n });\n\n Log.Dom.Span.prototype.__defineGetter__('tail', function() {\n return this.siblings('next');\n });\n\n Log.Dom.Span.prototype.__defineGetter__('prev', function() {\n var span, _ref, _ref1;\n span = this.parent.spans.at(this.parent.spans.indexOf(this) - 1);\n return span || ((_ref = this.parent.prev) != null ? (_ref1 = _ref.spans) != null ? _ref1.last : void 0 : void 0);\n });\n\n Log.Dom.Span.prototype.__defineGetter__('next', function() {\n var span, _ref, _ref1;\n span = this.parent.spans.at(this.parent.spans.indexOf(this) + 1);\n return span || ((_ref = this.parent.next) != null ? (_ref1 = _ref.spans) != null ? _ref1.first : void 0 : void 0);\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/dom");minispade.register('log.old/engine/dom2', "(function() {(function() {\n\n Log.Dom = function(log) {\n this.log = log;\n this.parts = [];\n this.nodes = new Log.Dom.Nodes(this);\n return this;\n };\n\n $.extend(Log.Dom.prototype, {\n set: function(num, string) {\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n this.parts[num] = true;\n return this.insert(num, string);\n }\n },\n SLICE: 500,\n insert: function(num, string) {\n var ix, lines, next, slices,\n _this = this;\n lines = string.split(/^/gm) || [];\n slices = ((function() {\n var _results;\n _results = [];\n while (lines.length > 0) {\n _results.push(lines.splice(0, this.SLICE));\n }\n return _results;\n }).call(this));\n ix = -1;\n next = function() {\n _this.insertSlice(num, slices.shift(), ix += 1);\n if (slices.length !== 0) {\n return setTimeout(next, 50);\n }\n };\n return next();\n },\n insertSlice: function(num, lines, start) {\n var ix, line, _i, _len, _ref, _ref1, _results;\n _ref = lines || [];\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n if ((_ref1 = this.log.limit) != null ? _ref1.limited : void 0) {\n break;\n }\n _results.push(this.nodes.insert(Log.Dom.Node.create(this, [num, start * this.SLICE + ix], line)));\n }\n return _results;\n }\n });\n\n Log.Dom.Nodes = function(parent) {\n this.parent = parent;\n this.content = [];\n return this;\n };\n\n $.extend(Log.Dom.Nodes.prototype, {\n at: function(ix) {\n return this.content[ix];\n },\n insert: function(node) {\n return this.content[node.num] = node;\n },\n remove: function(node) {\n return this.content.splice(this.content.indexOf(node), 1);\n }\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('length', function() {\n return this.content.length;\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('last', function() {\n return this.content[this.content.length - 1];\n });\n\n Log.Dom.Node = function() {};\n\n $.extend(Log.Dom.Node, {\n FOLDS_PATTERN: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(parent, ids, string) {\n var fold;\n if (fold = string.match(this.FOLDS_PATTERN)) {\n return new Log.Dom.Fold(parent, ids, fold[1], fold[2]);\n } else {\n return new Log.Dom.Paragraph(parent, ids, string);\n }\n }\n });\n\n Log.Dom.Node.prototype.__defineGetter__('prev', function() {\n var num, prev, _ref;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.parent.nodes.at(num -= 1);\n }\n return prev || ((_ref = this.parent.prev) != null ? _ref.nodes.last : void 0);\n });\n\n Log.Dom.Node.prototype.__defineGetter__('next', function() {\n var next, num, _ref;\n num = this.num;\n while (!(next || num >= this.parent.nodes.length)) {\n next = this.parent.nodes.at(num += 1);\n }\n return next || ((_ref = this.parent.next) != null ? _ref.nodes.first : void 0);\n });\n\n Log.Dom.Paragraph = function(parent, ids, string) {\n var _ref;\n this.parent = parent;\n this.ids = ids;\n this.id = ids.join('-');\n this.ends = !!((_ref = string[string.length - 1]) != null ? _ref.match(/\\n/) : void 0);\n this.spans = new Log.Dom.Spans(this, ids, string.replace(/\\n$/, ''));\n return this;\n };\n\n Log.Dom.Paragraph.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n var next, prev,\n _this = this;\n if ((prev = this.prev) && !prev.ends && !prev.fold) {\n if (Log.DEBUG) {\n console.log(\"P.1 - move \" + this.id + \"'s spans into prev (\" + prev.id + \")\");\n }\n this.spans.content.slice().forEach(function(span) {\n return prev.spans.insert(span);\n });\n return this.remove();\n } else if ((next = this.next) && !this.ends && !next.fold) {\n if (Log.DEBUG) {\n console.log(\"P.2 - move next's (\" + next.id + \") spans into \" + this.id);\n }\n this.next.spans.content.slice().forEach(function(span) {\n return _this.spans.insert(span);\n });\n return next.remove();\n }\n },\n remove: function() {\n return this.parent.nodes.remove(this);\n }\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('id', function() {\n return \"\" + this.parent.num + \"-\" + this.num;\n });\n\n Log.Dom.Spans = function(parent, ids, string) {\n this.parent = parent;\n this.content = this.parse(parent, ids, string);\n return this;\n };\n\n $.extend(Log.Dom.Spans.prototype, {\n parse: function(parent, ids, string) {\n var ix, span, _i, _len, _ref, _results;\n _ref = Log.Deansi.apply(string);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n span = _ref[ix];\n _results.push(new Log.Dom.Span(parent, ids.concat([ix]), span));\n }\n return _results;\n },\n at: function(ix) {\n return this.content[ix];\n },\n indexOf: function(span) {\n return this.content.indexOf(span);\n },\n insert: function(span) {\n var head, ix, prev;\n console.log('insert', span.id);\n span.parent.spans.remove(span);\n span.parent = this.parent;\n head = this.content.filter(function(s) {\n return s.id < span.id;\n });\n if (prev = head[head.length - 1]) {\n ix = this.content.indexOf(prev) + 1;\n }\n return this.content.splice(ix || 0, 0, span);\n },\n prepend: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.unshift(span);\n return span.insert();\n },\n remove: function(span) {\n return this.content.splice(this.content.indexof(span), 1);\n }\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('last', function() {\n return this.content[this.content.length - 1];\n });\n\n Log.Dom.Span = function(parent, ids, data) {\n this.parent = parent;\n this.ids = ids;\n this.id = ids.join('-');\n this.data = $.extend(data, {\n id: this.id\n });\n if (data.text.match(/\\r/)) {\n this.hidden = true;\n }\n this.data.text = data.text.replace(/^.*\\r/gm, '');\n if (this.hidden) {\n this.data[\"class\"] = ['hidden'];\n }\n return this;\n };\n\n $.extend(Log.Dom.Span.prototype, {\n insert: function() {\n var next, prev;\n if (prev = this.prev) {\n if (Log.DEBUG) {\n console.log(\"S.1 - insert \" + this.id + \" after prev \" + prev.id);\n }\n return this.insertAt({\n after: prev.element\n });\n } else if (next = this.next) {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" before next \" + next.id);\n }\n return this.insertAt({\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" into parent \" + this.parent.id);\n }\n return this.insertAt({\n into: this.parent.element\n });\n }\n },\n insertAt: function(pos) {\n return this.element = this.trigger('insert', this.data, pos);\n }\n });\n\n Log.Dom.Span.prototype.__defineGetter__('prev', function() {\n var span, _ref, _ref1;\n console.log(this.num);\n span = this.parent.spans.at(this.num - 1);\n return span || ((_ref = this.parent.prev) != null ? (_ref1 = _ref.spans) != null ? _ref1.last : void 0 : void 0);\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/dom2");minispade.register('log.old/engine/live', "(function() {(function() {\n\n Log.Live = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Live.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Live.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Live.Part = function(log, num, string) {\n var ix, line;\n this.log = log;\n this.num = num;\n this.lines = (function() {\n var _i, _len, _ref, _results;\n _ref = string.split(/^/m);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n _results.push(new Log.Live.Line(this, ix, line));\n }\n return _results;\n }).call(this);\n return this;\n };\n\n $.extend(Log.Live.Part.prototype, {\n insert: function() {\n return new Log.Live.Context(this.log, this).insert();\n },\n head: function() {\n var head, line;\n head = [];\n line = this.lines[0];\n while ((line = line != null ? line.prev() : void 0) && !line.isNewline()) {\n head.unshift(line);\n }\n return head;\n },\n tail: function() {\n var line, tail;\n tail = [];\n line = this.lines[this.lines.length - 1];\n while (line = line != null ? line.next() : void 0) {\n tail.push(line);\n if (line != null ? line.isNewline() : void 0) {\n break;\n }\n }\n return tail;\n },\n prev: function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.log.parts[num -= 1];\n }\n return prev;\n },\n next: function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.log.parts.length)) {\n next = this.log.parts[num += 1];\n }\n return next;\n }\n });\n\n Log.Live.Line = function(part, num, string) {\n this.part = part;\n this.num = num;\n this.id = \"\" + part.num + \"-\" + num;\n this.string = string;\n return this;\n };\n\n $.extend(Log.Live.Line.prototype, {\n prev: function() {\n var line, _ref;\n line = this.part.lines[this.num - 1];\n return line || ((_ref = this.part.prev()) != null ? _ref.lines.slice(-1)[0] : void 0);\n },\n next: function() {\n var line, _ref;\n line = this.part.lines[this.num + 1];\n return line || ((_ref = this.part.next()) != null ? _ref.lines[0] : void 0);\n },\n isNewline: function() {\n return this.string[this.string.length - 1] === \"\\n\";\n },\n isFold: function() {\n return this.string.indexOf('fold') !== -1;\n },\n clone: function() {\n return new Log.Live.Line(this.part, this.num, this.string);\n }\n });\n\n Log.Live.Context = function(log, part) {\n this.log = log;\n this.part = part;\n this.head = part.head();\n this.tail = part.tail();\n this.lines = this.join(this.head.concat(part.lines).concat(this.tail));\n return this;\n };\n\n $.extend(Log.Live.Context.prototype, {\n insert: function() {\n var ids;\n ids = this.head.concat(this.tail).map(function(line) {\n return line.id;\n });\n if (ids.length !== 0) {\n this.log.trigger('remove', ids);\n }\n return this.log.trigger('insert', this.after(), this.nodes());\n },\n nodes: function() {\n var _this = this;\n return this.lines.map(function(line) {\n var fold, string;\n string = line.string;\n if (fold = _this.defold(string)) {\n return $.extend(fold, {\n id: line.id\n });\n } else {\n return {\n id: line.id,\n nodes: _this.deansi(string)\n };\n }\n });\n },\n join: function(all) {\n var line, lines;\n lines = [];\n while (line = all.pop()) {\n if (lines.length === 0 || line.isNewline()) {\n lines.unshift(line.clone());\n } else {\n lines[0].string = line.string + lines[0].string;\n }\n }\n return lines;\n },\n after: function() {\n var line, _ref;\n line = (_ref = this.part.lines[0]) != null ? _ref.prev() : void 0;\n while (line && !line.isNewline() && !line.isFold()) {\n line = line.prev();\n }\n return line != null ? line.id : void 0;\n },\n defold: function(string) {\n var matches;\n if (matches = string.match(/fold:(start|end):([\\w_\\-\\.]+)/)) {\n return {\n type: 'fold',\n event: matches[1],\n name: matches[2]\n };\n }\n },\n deansi: function(string) {\n return Log.Deansi.apply(string);\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/live");minispade.register('log.old/folds', "(function() {(function() {\n\n Log.Folds = function() {\n this.folds = {};\n return this;\n };\n\n Log.Folds.prototype = $.extend(new Log.Listener, {\n insert: function(log, data, pos) {\n var fold, _base, _name;\n if (data.type === 'fold') {\n fold = (_base = this.folds)[_name = data.name] || (_base[_name] = new Log.Folds.Fold);\n fold.receive(data);\n }\n return true;\n }\n });\n\n Log.Folds.Fold = function() {\n return this;\n };\n\n $.extend(Log.Folds.Fold.prototype, {\n receive: function(data) {\n this[data.event] = data.id;\n if (this.start && this.end && !this.active) {\n return this.activate();\n }\n },\n activate: function() {\n var node, _i, _len, _ref;\n console.log(\"F - activate \" + this.start);\n console.log(document.firstChild.innerHTML.replace(/

rcv \" + num + \" \" + (JSON.stringify(string)) + \"\");\n },\n insert: function(log, after, datas) {\n return this.log(\"ins \" + (datas.map(function(data) {\n return data.id;\n }).join(', ')) + \", after: \" + (after || '?') + \", \" + (JSON.stringify(datas)));\n },\n remove: function(log, id) {\n return this.log(\"rem \" + id);\n },\n log: function(line) {\n return $('#events').append(\"\" + line + \"\\n\");\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/instrument");minispade.register('log.old/limit', "(function() {(function() {\n\n Log.Limit = function(max_lines) {\n this.max_lines = max_lines || 1000;\n return this;\n };\n\n Log.Limit.prototype = $.extend(new Log.Listener, {\n count: 0,\n insert: function(log, line, pos) {\n if (line.type === 'paragraph' && !line.hidden) {\n return this.count += 1;\n }\n }\n });\n\n Log.Limit.prototype.__defineGetter__('limited', function() {\n return this.count >= this.max_lines;\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/limit");minispade.register('log.old/renderer/inner_html', "(function() {(function() {\n\n Log.InnerHtmlRenderer = function() {\n this.frag = document.createDocumentFragment();\n this.div = document.createElement('div');\n return this;\n };\n\n Log.InnerHtmlRenderer.prototype = $.extend(new Log.Listener, {\n remove: function(log, ids) {\n var id, node, _i, _len, _ref, _results;\n _results = [];\n for (_i = 0, _len = ids.length; _i < _len; _i++) {\n id = ids[_i];\n node = document.getElementById(id);\n if (node && !((_ref = node.getAttribute('class')) != null ? _ref.match(/fold/) : void 0)) {\n _results.push(node.parentNode.removeChild(node));\n } else {\n _results.push(void 0);\n }\n }\n return _results;\n },\n insert: function(log, after, nodes) {\n var html;\n log = document.getElementById('log');\n html = this.render(nodes);\n if (log.childNodes.length === 0) {\n return log.innerHTML = html;\n } else if (after) {\n after = document.getElementById(after);\n return this.insertAfter(this.fragmentFrom(html), after);\n } else {\n log = document.getElementById('log');\n return log.insertBefore(this.fragmentFrom(html), log.firstChild);\n }\n },\n render: function(nodes) {\n var node;\n return ((function() {\n var _i, _len, _results;\n _results = [];\n for (_i = 0, _len = nodes.length; _i < _len; _i++) {\n node = nodes[_i];\n _results.push(this.renderNode(node));\n }\n return _results;\n }).call(this)).join('');\n },\n renderNode: function(node) {\n var type;\n node.type || (node.type = 'paragraph');\n type = node.type[0].toUpperCase() + node.type.slice(1);\n return this[\"render\" + type](node) || '';\n },\n renderParagraph: function(node) {\n var html, style;\n if (node.hidden) {\n style = ' style=\"display:none\"';\n }\n html = \"

\";\n html += ((function() {\n var _i, _len, _ref, _results;\n _ref = node.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n _results.push(this.renderNode(node));\n }\n return _results;\n }).call(this)).join('');\n return html + '

';\n },\n renderFold: function(node) {\n if (!document.getElementById(node.id)) {\n return \"
\";\n }\n },\n renderSpan: function(node) {\n return \"\" + (this.clean(node.text)) + \"\";\n },\n renderText: function(node) {\n return this.clean(node.text);\n },\n clean: function(text) {\n return text.replace(/\\n/gm, '');\n },\n fragmentFrom: function(html) {\n var div, frag, node;\n frag = this.frag.cloneNode();\n div = this.div.cloneNode();\n div.innerHTML = html;\n while (node = div.firstChild) {\n frag.appendChild(node);\n }\n return frag;\n },\n insertAfter: function(node, after) {\n if (after.nextSibling) {\n return after.parentNode.insertBefore(node, after.nextSibling);\n } else {\n return after.parentNode.appendChild(node);\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/renderer/inner_html");minispade.register('log.old/renderer/jquery', "(function() {(function() {\n\n Log.JqueryRenderer = function() {};\n\n Log.JqueryRenderer.prototype = $.extend(new Log.Listener, {\n remove: function(log, ids) {\n var id, _i, _len, _results;\n _results = [];\n for (_i = 0, _len = ids.length; _i < _len; _i++) {\n id = ids[_i];\n _results.push($(\"#log #\" + id).remove());\n }\n return _results;\n },\n insert: function(log, after, datas) {\n var html,\n _this = this;\n html = datas.map(function(data) {\n return _this.render(data);\n });\n return after && $(\"#log #\" + after).after(html) || $('#log').prepend(html);\n },\n render: function(data) {\n var node, nodes, text;\n nodes = (function() {\n var _i, _len, _ref, _results;\n _ref = data.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n text = node.text.replace(/\\n/gm, '');\n if (node.type === 'span') {\n text = \"\" + text + \"\";\n }\n _results.push(\"

\" + text + \"

\");\n }\n return _results;\n }).call(this);\n return nodes.join(\"\\n\");\n },\n style: function(data) {\n return data.hidden && 'display: none;' || '';\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/renderer/jquery");minispade.register('log/deansi', "(function() {(function() {\n\n Log.Deansi = {\n CLEAR_ANSI: /(\\e|\\033)\\[K|(\\e|\\033)\\[K|(\\e|\\033)\\[\\d+G|(\\e|\\033)\\(B|(\\e|\\033)M|(\\e|\\033)\\[\\?25(l|h)/gm,\n apply: function(string) {\n var nodes,\n _this = this;\n if (!string) {\n return [];\n }\n string = string.replace(this.CLEAR_ANSI, '');\n nodes = ansiparse(string).map(function(part) {\n return _this.node(part);\n });\n if (nodes.length === 0) {\n nodes.push(this.node({\n text: ''\n }));\n }\n return nodes;\n },\n node: function(part) {\n var classes, node;\n node = {\n type: 'span',\n text: part.text\n };\n if (classes = this.classes(part)) {\n node[\"class\"] = classes.join(' ');\n }\n return node;\n },\n classes: function(part) {\n var result;\n result = [];\n result = result.concat(this.colors(part));\n if (result.length > 0) {\n return result;\n }\n },\n colors: function(part) {\n var colors;\n colors = [];\n if (part.foreground) {\n colors.push(part.foreground);\n }\n if (part.background) {\n colors.push(\"bg-\" + part.background);\n }\n if (part.bold) {\n colors.push('bold');\n }\n if (part.italic) {\n colors.push('italic');\n }\n return colors;\n },\n hidden: function(part) {\n if (part.text.match(/\\r/)) {\n part.text = part.text.replace(/^.*\\r/gm, '');\n return true;\n }\n }\n };\n\n}).call(this);\n\n})();\n//@ sourceURL=log/deansi");minispade.register('log/folds', "(function() {(function() {\n\n Log.Folds = function() {\n this.folds = {};\n return this;\n };\n\n Log.extend(Log.Folds.prototype, {\n add: function(data) {\n var fold, _base, _name;\n fold = (_base = this.folds)[_name = data.name] || (_base[_name] = new Log.Folds.Fold);\n fold.receive(data);\n return fold.active;\n }\n });\n\n Log.Folds.Fold = function() {\n return this;\n };\n\n Log.extend(Log.Folds.Fold.prototype, {\n receive: function(data) {\n this[data.event] = data.id;\n if (this.start && this.end && !this.active) {\n return this.activate();\n }\n },\n activate: function() {\n var node, _i, _len, _ref;\n if (Log.DEBUG) {\n console.log(\"F - activate \" + this.start);\n }\n _ref = this.nodes;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n this.fold.appendChild(node);\n }\n this.fold.setAttribute('class', this.classes());\n return this.active = true;\n },\n classes: function() {\n var classes;\n classes = this.fold.getAttribute('class').split(' ');\n classes.push('fold');\n if (this.fold.childNodes.length > 2) {\n classes.push('active');\n }\n return classes.join(' ');\n }\n });\n\n Object.defineProperty(Log.Folds.Fold.prototype, 'fold', {\n get: function() {\n return this._fold || (this._fold = document.getElementById(this.start));\n }\n });\n\n Object.defineProperty(Log.Folds.Fold.prototype, 'nodes', {\n get: function() {\n var node, nodes;\n node = this.fold;\n nodes = [];\n while ((node = node.nextSibling) && node.id !== this.end) {\n nodes.push(node);\n }\n return nodes;\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/folds");minispade.register('log/limit', "(function() {(function() {\n\n Log.Limit = function(max_lines) {\n this.max_lines = max_lines || 1000;\n return this;\n };\n\n Log.Limit.prototype = Log.extend(new Log.Listener, {\n count: 0,\n insert: function(log, node, pos) {\n if (node.type === 'paragraph' && !node.hidden) {\n return this.count += 1;\n }\n }\n });\n\n Object.defineProperty(Log.Limit.prototype, 'limited', {\n get: function() {\n return this.count >= this.max_lines;\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/limit");minispade.register('log/nodes', "(function() {(function() {\n\n Log.Node = function(id, num) {\n this.id = id;\n this.num = num;\n this.key = Log.Node.key(this.id);\n this.children = new Log.Nodes(this);\n return this;\n };\n\n Log.extend(Log.Node, {\n key: function(id) {\n if (id) {\n return id.split('-').map(function(i) {\n return '000000'.concat(i).slice(-6);\n }).join('');\n }\n }\n });\n\n Log.extend(Log.Node.prototype, {\n addChild: function(node) {\n return this.children.add(node);\n },\n remove: function() {\n this.log.remove(this.element);\n return this.parent.children.remove(this);\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'log', {\n get: function() {\n var _ref;\n return this._log || (this._log = ((_ref = this.parent) != null ? _ref.log : void 0) || this.parent);\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'firstChild', {\n get: function() {\n return this.children.first;\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'lastChild', {\n get: function() {\n return this.children.last;\n }\n });\n\n Log.Nodes = function(parent) {\n if (parent) {\n this.parent = parent;\n }\n this.items = [];\n this.index = {};\n return this;\n };\n\n Log.extend(Log.Nodes.prototype, {\n add: function(item) {\n var ix, next, prev, _ref, _ref1;\n ix = this.position(item) || 0;\n this.items.splice(ix, 0, item);\n if (this.parent) {\n item.parent = this.parent;\n }\n prev = function(item) {\n while (item && !item.children.last) {\n item = item.prev;\n }\n return item != null ? item.children.last : void 0;\n };\n next = function(item) {\n while (item && !item.children.first) {\n item = item.next;\n }\n return item != null ? item.children.first : void 0;\n };\n if (item.prev = this.items[ix - 1] || prev((_ref = this.parent) != null ? _ref.prev : void 0)) {\n item.prev.next = item;\n }\n if (item.next = this.items[ix + 1] || next((_ref1 = this.parent) != null ? _ref1.next : void 0)) {\n item.next.prev = item;\n }\n return item;\n },\n remove: function(item) {\n this.items.splice(this.items.indexOf(item), 1);\n if (item.next) {\n item.next.prev = item.prev;\n }\n if (item.prev) {\n item.prev.next = item.next;\n }\n if (this.items.length === 0) {\n return this.parent.remove();\n }\n },\n position: function(item) {\n var ix, _i, _ref;\n for (ix = _i = _ref = this.items.length - 1; _i >= 0; ix = _i += -1) {\n if (this.items[ix].key < item.key) {\n return ix + 1;\n }\n }\n },\n indexOf: function() {\n return this.items.indexOf.apply(this.items, arguments);\n },\n slice: function() {\n return this.items.slice.apply(this.items, arguments);\n },\n each: function(func) {\n return this.items.slice().forEach(func);\n },\n map: function(func) {\n return this.items.map(func);\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'first', {\n get: function() {\n return this.items[0];\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'last', {\n get: function() {\n return this.items[this.length - 1];\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'length', {\n get: function() {\n return this.items.length;\n }\n });\n\n Log.Part = function(id, num, string) {\n Log.Node.apply(this, arguments);\n this.string = string || '';\n this.string = this.string.replace(/\\033\\[1000D/gm, '\\r');\n this.string = this.string.replace(/\\r+\\n/gm, '\\n');\n this.strings = this.string.split(/^/gm) || [];\n this.slices = ((function() {\n var _results;\n _results = [];\n while (this.strings.length > 0) {\n _results.push(this.strings.splice(0, Log.SLICE));\n }\n return _results;\n }).call(this));\n return this;\n };\n\n Log.extend(Log.Part, {\n create: function(log, num, string) {\n var part;\n part = new Log.Part(num.toString(), num, string);\n log.addChild(part);\n return part.process(0, -1);\n }\n });\n\n Log.Part.prototype = Log.extend(new Log.Node, {\n remove: function() {},\n process: function(slice, num) {\n var node, span, spans, string, _i, _j, _len, _len1, _ref, _ref1, _ref2,\n _this = this;\n _ref = this.slices[slice] || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n string = _ref[_i];\n if ((_ref1 = this.log.limit) != null ? _ref1.limited : void 0) {\n return;\n }\n spans = [];\n _ref2 = Log.Deansi.apply(string);\n for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {\n node = _ref2[_j];\n span = Log.Span.create(this, \"\" + this.id + \"-\" + (num += 1), num, node.text, node[\"class\"]);\n span.render();\n spans.push(span);\n }\n if (spans[0] && spans[0].line.cr) {\n spans[0].line.clear();\n }\n }\n if (!(slice >= this.slices.length - 1)) {\n return setTimeout((function() {\n return _this.process(slice + 1, num);\n }), Log.TIMEOUT);\n }\n }\n });\n\n Log.Span = function(id, num, text, classes) {\n var fold, _ref;\n Log.Node.apply(this, arguments);\n if (fold = text.match(Log.FOLD)) {\n this.fold = true;\n this.event = fold[1];\n this.text = this.name = fold[2];\n } else {\n this.text = text.replace(/.*\\r/gm, '').replace(/\\n$/, '');\n this.nl = !!((_ref = text[text.length - 1]) != null ? _ref.match(/\\n/) : void 0);\n this.cr = !!text.match(/\\r/);\n this[\"class\"] = this.cr && ['clears'] || classes;\n }\n return this;\n };\n\n Log.extend(Log.Span, {\n create: function(parent, id, num, text, classes) {\n var span;\n span = new Log.Span(id, num, text, classes);\n parent.addChild(span);\n return span;\n },\n render: function(parent, id, num, text, classes) {\n var span;\n span = this.create(parent, id, num, text, classes);\n return span.render();\n }\n });\n\n Log.Span.prototype = Log.extend(new Log.Node, {\n render: function() {\n var tail;\n if (!this.fold && this.prev && !this.prev.fold && !this.prev.nl) {\n if (Log.DEBUG) {\n console.log(\"S.1 insert \" + this.id + \" after prev \" + this.prev.id);\n }\n this.log.insert(this.data, {\n after: this.prev.element\n });\n this.line = this.prev.line;\n } else if (!this.fold && this.next && !this.next.fold) {\n if (Log.DEBUG) {\n console.log(\"S.2 insert \" + this.id + \" before next \" + this.next.id);\n }\n this.log.insert(this.data, {\n before: this.next.element\n });\n this.line = this.next.line;\n } else {\n this.line = Log.Line.create(this.log, [this]);\n this.line.render();\n }\n if (this.nl && (tail = this.tail).length > 0) {\n return this.split(tail);\n }\n },\n remove: function() {\n Log.Node.prototype.remove.apply(this);\n if (this.line) {\n return this.line.remove(this);\n }\n },\n split: function(spans) {\n var line, span, _i, _len;\n if (Log.DEBUG) {\n console.log(\"S.3 split [\" + (spans.map(function(span) {\n return span.id;\n }).join(', ')) + \"]\");\n }\n for (_i = 0, _len = spans.length; _i < _len; _i++) {\n span = spans[_i];\n this.log.remove(span.element);\n }\n line = Log.Line.create(this.log, spans);\n line.render();\n if (line.cr) {\n return line.clear();\n }\n },\n clear: function() {\n if (this.prev && this.isSibling(this.prev) && this.isSequence(this.prev)) {\n this.prev.clear();\n return this.prev.remove();\n }\n },\n isSequence: function(other) {\n return this.parent.num - other.parent.num === this.log.children.indexOf(this.parent) - this.log.children.indexOf(other.parent);\n },\n isSibling: function(other) {\n var _ref, _ref1;\n return ((_ref = this.element) != null ? _ref.parentNode : void 0) === ((_ref1 = other.element) != null ? _ref1.parentNode : void 0);\n },\n siblings: function(type) {\n var siblings, span;\n siblings = [];\n while ((span = (span || this)[type]) && this.isSibling(span)) {\n siblings.push(span);\n }\n return siblings;\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'data', {\n get: function() {\n return {\n id: this.id,\n type: 'span',\n text: this.text,\n \"class\": this[\"class\"]\n };\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'line', {\n get: function() {\n return this._line;\n },\n set: function(line) {\n if (this.line) {\n this.line.remove(this);\n }\n this._line = line;\n return this.line.add(this);\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'element', {\n get: function() {\n return document.getElementById(this.id);\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'head', {\n get: function() {\n return this.siblings('prev').reverse();\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'tail', {\n get: function() {\n return this.siblings('next');\n }\n });\n\n Log.Line = function(log) {\n this.log = log;\n this.spans = [];\n return this;\n };\n\n Log.extend(Log.Line, {\n create: function(log, spans) {\n var line, span, _i, _len;\n if ((span = spans[0]) && span.fold) {\n line = new Log.Fold(log, span.event, span.name);\n } else {\n line = new Log.Line(log);\n }\n for (_i = 0, _len = spans.length; _i < _len; _i++) {\n span = spans[_i];\n span.line = line;\n }\n return line;\n }\n });\n\n Log.extend(Log.Line.prototype, {\n add: function(span) {\n var ix;\n if (span.cr) {\n this.cr = true;\n }\n if (this.spans.indexOf(span) > -1) {\n\n } else if ((ix = this.spans.indexOf(span.prev)) > -1) {\n return this.spans.splice(ix + 1, 0, span);\n } else if ((ix = this.spans.indexOf(span.next)) > -1) {\n return this.spans.splice(ix, 0, span);\n } else {\n return this.spans.push(span);\n }\n },\n remove: function(span) {\n var ix;\n if ((ix = this.spans.indexOf(span)) > -1) {\n return this.spans.splice(ix, 1);\n }\n },\n render: function() {\n var fold;\n if ((fold = this.prev) && fold.event === 'start' && fold.active) {\n if (Log.DEBUG) {\n console.log(\"L.0 insert \" + this.id + \" into fold \" + fold.id);\n }\n fold = this.log.folds.folds[fold.name].fold;\n return this.element = this.log.insert(this.data, {\n into: fold\n });\n } else if (this.prev) {\n if (Log.DEBUG) {\n console.log(\"L.1 insert \" + this.spans[0].id + \" after prev \" + this.prev.id);\n }\n return this.element = this.log.insert(this.data, {\n after: this.prev.element\n });\n } else if (this.next) {\n if (Log.DEBUG) {\n console.log(\"L.2 insert \" + this.spans[0].id + \" before next \" + this.next.id);\n }\n return this.element = this.log.insert(this.data, {\n before: this.next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"L.3 insert \" + this.spans[0].id + \" into #log\");\n }\n return this.element = this.log.insert(this.data);\n }\n },\n clear: function() {\n var cr, _i, _len, _ref, _results;\n _ref = this.crs;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n cr = _ref[_i];\n _results.push(cr.clear());\n }\n return _results;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'id', {\n get: function() {\n var _ref;\n return (_ref = this.spans[0]) != null ? _ref.id : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'data', {\n get: function() {\n return {\n type: 'paragraph',\n nodes: this.nodes\n };\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'nodes', {\n get: function() {\n return this.spans.map(function(span) {\n return span.data;\n });\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'prev', {\n get: function() {\n var _ref;\n return (_ref = this.spans[0].prev) != null ? _ref.line : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'next', {\n get: function() {\n var _ref;\n return (_ref = this.spans[this.spans.length - 1].next) != null ? _ref.line : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'crs', {\n get: function() {\n return this.spans.filter(function(span) {\n return span.cr;\n });\n }\n });\n\n Log.Fold = function(log, event, name) {\n Log.Line.apply(this, arguments);\n this.fold = true;\n this.event = event;\n this.name = name;\n return this;\n };\n\n Log.Fold.prototype = Log.extend(new Log.Line, {\n render: function() {\n var element, _ref;\n if (this.prev) {\n if (Log.DEBUG) {\n console.log(\"F.1 insert \" + this.id + \" after prev \" + this.prev.id);\n }\n element = this.prev.element || this.prev.element.parentNode;\n this.element = this.log.insert(this.data, {\n after: element\n });\n } else if (this.next) {\n if (Log.DEBUG) {\n console.log(\"F.2 insert \" + this.id + \" before next \" + this.next.id);\n }\n element = this.next.element || this.next.element.parentNode;\n this.element = this.log.insert(this.data, {\n before: element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"F.3 insert \" + this.id);\n }\n this.element = this.log.insert(this.data);\n }\n if (this.span.next && ((_ref = this.span.prev) != null ? _ref.isSibling(this.span.next) : void 0)) {\n this.span.prev.split([this.span.next].concat(this.span.next.tail));\n }\n return this.active = this.log.folds.add(this.data);\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'id', {\n get: function() {\n return \"fold-\" + this.event + \"-\" + this.name;\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'span', {\n get: function() {\n return this.spans[0];\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'data', {\n get: function() {\n return {\n type: 'fold',\n id: this.id,\n event: this.event,\n name: this.name\n };\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/nodes");minispade.register('log/renderer', "(function() {(function() {\n\n Log.Renderer = function() {\n this.frag = document.createDocumentFragment();\n this.para = this.createParagraph();\n this.span = this.createSpan();\n this.text = document.createTextNode('');\n this.fold = this.createFold();\n return this;\n };\n\n Log.extend(Log.Renderer.prototype, {\n insert: function(data, pos) {\n var after, before, into, node;\n node = this.render(data);\n if (into = pos != null ? pos.into : void 0) {\n if (typeof into === 'String') {\n into = document.getElementById(pos != null ? pos.into : void 0);\n }\n this.appendTo(node, into);\n } else if (after = pos != null ? pos.after : void 0) {\n if (typeof after === 'String') {\n after = document.getElementById(pos);\n }\n this.insertAfter(node, after);\n } else if (before = pos != null ? pos.before : void 0) {\n if (typeof before === 'String') {\n before = document.getElementById(pos != null ? pos.before : void 0);\n }\n this.insertBefore(node, before);\n } else {\n this.insertBefore(node);\n }\n return node;\n },\n hide: function(node) {\n node.setAttribute('class', this.addClass(node.getAttribute('class'), 'hidden'));\n return node;\n },\n remove: function(node) {\n if (node) {\n node.parentNode.removeChild(node);\n }\n return node;\n },\n render: function(data) {\n var frag, node, type, _i, _len;\n if (data instanceof Array) {\n frag = this.frag.cloneNode(true);\n for (_i = 0, _len = data.length; _i < _len; _i++) {\n node = data[_i];\n node = this.render(node);\n if (node) {\n frag.appendChild(node);\n }\n }\n return frag;\n } else {\n data.type || (data.type = 'paragraph');\n type = data.type[0].toUpperCase() + data.type.slice(1);\n return this[\"render\" + type](data);\n }\n },\n renderParagraph: function(data) {\n var node, para, type, _i, _len, _ref;\n para = this.para.cloneNode(true);\n if (data.id) {\n para.setAttribute('id', data.id);\n }\n if (data.hidden) {\n para.setAttribute('style', 'display: none;');\n }\n _ref = data.nodes || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n type = node.type[0].toUpperCase() + node.type.slice(1);\n node = this[\"render\" + type](node);\n para.appendChild(node);\n }\n return para;\n },\n renderFold: function(data) {\n var fold;\n fold = this.fold.cloneNode(true);\n fold.setAttribute('id', data.id || (\"fold-\" + data.event + \"-\" + data.name));\n fold.setAttribute('class', \"fold-\" + data.event);\n if (data.event === 'start') {\n fold.lastChild.lastChild.nodeValue = data.name;\n } else {\n fold.removeChild(fold.lastChild);\n }\n return fold;\n },\n renderSpan: function(data) {\n var span;\n span = this.span.cloneNode(true);\n if (data.id) {\n span.setAttribute('id', data.id);\n }\n if (data[\"class\"]) {\n span.setAttribute('class', data[\"class\"]);\n }\n span.lastChild.nodeValue = data.text;\n return span;\n },\n renderText: function(data) {\n var text;\n text = this.text.cloneNode(true);\n text.nodeValue = data.text;\n return text;\n },\n createParagraph: function() {\n var para;\n para = document.createElement('p');\n para.appendChild(document.createElement('a'));\n return para;\n },\n createFold: function() {\n var fold;\n fold = document.createElement('div');\n fold.appendChild(this.createSpan());\n fold.lastChild.setAttribute('class', 'fold-name');\n return fold;\n },\n createSpan: function() {\n var span;\n span = document.createElement('span');\n span.appendChild(document.createTextNode(''));\n return span;\n },\n insertBefore: function(node, other) {\n var log;\n if (other) {\n return other.parentNode.insertBefore(node, other);\n } else {\n log = document.getElementById('log');\n return log.insertBefore(node, log.firstChild);\n }\n },\n insertAfter: function(node, other) {\n if (other.nextSibling) {\n return this.insertBefore(node, other.nextSibling);\n } else {\n return this.appendTo(node, other.parentNode);\n }\n },\n appendTo: function(node, other) {\n return other.appendChild(node);\n },\n addClass: function(classes, string) {\n if (classes != null ? classes.indexOf(string) : void 0) {\n return;\n }\n if (classes) {\n return \"\" + classes + \" \" + string;\n } else {\n return string;\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/renderer"); \ No newline at end of file +minispade.register('log', "(function() {(function() {\n\n this.Log = function() {\n this.listeners = [];\n this.renderer = new Log.Renderer;\n this.children = new Log.Nodes(this);\n this.parts = {};\n this.folds = new Log.Folds;\n return this;\n };\n\n Log.extend = function(one, other) {\n var name;\n for (name in other) {\n one[name] = other[name];\n }\n return one;\n };\n\n Log.extend(Log, {\n DEBUG: true,\n SLICE: 500,\n TIMEOUT: 25,\n FOLD: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(options) {\n var listener, log, _i, _len, _ref;\n options || (options = {});\n log = new Log();\n if (options.limit) {\n log.listeners.push(log.limit = new Log.Limit(options.limit));\n }\n _ref = options.listeners || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n listener = _ref[_i];\n log.listeners.push(listener);\n }\n return log;\n }\n });\nminispade.require('log/nodes');\n\n Log.prototype = Log.extend(new Log.Node, {\n set: function(num, string) {\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n this.parts[num] = true;\n return Log.Part.create(this, num, string);\n }\n },\n insert: function(data, pos) {\n this.trigger('insert', data, pos);\n return this.renderer.insert(data, pos);\n },\n remove: function(node) {\n this.trigger('remove', node);\n return this.renderer.remove(node);\n },\n hide: function(node) {\n this.trigger('hide', node);\n return this.renderer.hide(node);\n },\n trigger: function() {\n var args, ix, listener, _i, _len, _ref, _results;\n args = [this].concat(Array.prototype.slice.apply(arguments));\n _ref = this.listeners;\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n listener = _ref[ix];\n _results.push(listener.notify.apply(listener, args));\n }\n return _results;\n }\n });\n\n Log.Listener = function() {};\n\n Log.extend(Log.Listener.prototype, {\n notify: function(log, event) {\n if (this[event]) {\n return this[event].apply(this, [log].concat(Array.prototype.slice.call(arguments, 2)));\n }\n }\n });\nminispade.require('log/folds');\nminispade.require('log/deansi');\nminispade.require('log/limit');\nminispade.require('log/renderer');\n\n}).call(this);\n\n})();\n//@ sourceURL=log");minispade.register('log.old/buffer', "(function() {(function() {\n\n Log.Buffer = function(log, options) {\n this.start = 0;\n this.log = log;\n this.parts = [];\n this.options = $.extend({\n interval: 100,\n timeout: 500\n }, options || {});\n this.schedule();\n return this;\n };\n\n $.extend(Log.Buffer.prototype, {\n set: function(num, string) {\n return this.parts[num] = {\n string: string,\n time: (new Date).getTime()\n };\n },\n flush: function() {\n var num, part, _i, _len, _ref;\n _ref = this.parts;\n for (num = _i = 0, _len = _ref.length; _i < _len; num = ++_i) {\n part = _ref[num];\n if (!this.parts.hasOwnProperty(num)) {\n continue;\n }\n if (!part) {\n break;\n }\n delete this.parts[num];\n this.log.set(num, part.string);\n }\n return this.schedule();\n },\n schedule: function() {\n var _this = this;\n return setTimeout((function() {\n return _this.flush();\n }), this.options.interval);\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/buffer");minispade.register('log.old/engine/chunks', "(function() {(function() {\n\n Log.Chunks = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Chunks.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Chunks.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Chunks.Part = function(engine, num, string) {\n var chunk, ix, line, type;\n this.engine = engine;\n this.num = num;\n this.chunks = (function() {\n var _i, _len, _ref, _results;\n _ref = string.split(/^/m);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n chunk = _ref[ix];\n line = chunk[chunk.length - 1].match(/\\r|\\n/);\n type = line ? 'Line' : 'Chunk';\n _results.push(new Log.Chunks[type](this, ix, chunk));\n }\n return _results;\n }).call(this);\n return this;\n };\n\n $.extend(Log.Chunks.Part.prototype, {\n insert: function() {\n var chunk, _i, _len, _ref, _results;\n _ref = this.chunks;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n chunk = _ref[_i];\n _results.push(chunk.insert());\n }\n return _results;\n },\n prev: function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.engine.parts[num -= 1];\n }\n return prev;\n },\n next: function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.engine.parts.length)) {\n next = this.engine.parts[num += 1];\n }\n return next;\n },\n trigger: function() {\n return this.engine.trigger.apply(this.engine, arguments);\n }\n });\n\n Log.Chunks.Chunk = function(part, num, string) {\n this.part = part;\n this.num = num;\n this.string = string;\n this.id = \"\" + (part != null ? part.num : void 0) + \"-\" + num;\n this.isFold = (string != null ? string.indexOf('fold') : void 0) !== -1;\n if (string) {\n this.nodes = this.parse();\n }\n return this;\n };\n\n $.extend(Log.Chunks.Chunk.prototype, {\n parse: function() {\n return [\n {\n type: 'span',\n id: \"\" + this.id + \"-0\",\n text: this.string\n }\n ];\n },\n insert: function() {\n var next, prev;\n if ((next = this.next()) && next.isLine) {\n return this.trigger('insert', this.nodes, {\n before: next.nodes[0].nodes[0].id\n });\n } else if (next) {\n return this.trigger('insert', this.nodes, {\n before: next.nodes[0].id\n });\n } else if ((prev = this.prev()) && !prev.isLine) {\n return this.trigger('insert', this.nodes, {\n after: prev.nodes[prev.nodes.length - 1].id\n });\n } else {\n return this.trigger('insert', [\n {\n type: 'paragraph',\n id: this.id,\n nodes: this.nodes\n }\n ], {\n before: void 0\n });\n }\n },\n remove: function() {\n var node, _i, _len, _ref, _results;\n _ref = this.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n this.trigger('remove', this.id);\n if (node.nodes) {\n _results.push((function() {\n var _j, _len1, _ref1, _results1;\n _ref1 = node.nodes;\n _results1 = [];\n for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {\n node = _ref1[_j];\n _results1.push(this.trigger('remove', node.id));\n }\n return _results1;\n }).call(this));\n } else {\n _results.push(void 0);\n }\n }\n return _results;\n },\n reinsert: function() {\n this.remove();\n return this.insert();\n },\n prevLine: function() {\n var prev;\n prev = this.prev();\n while (prev && !prev.isLine) {\n prev = prev.prev();\n }\n return prev;\n },\n nextLine: function() {\n var next;\n next = this.next();\n while (next && !next.isLine) {\n next = next.next();\n }\n return next;\n },\n prev: function() {\n var chunk, _ref;\n chunk = this.part.chunks[this.num - 1];\n return chunk || ((_ref = this.part.prev()) != null ? _ref.chunks.slice(-1)[0] : void 0);\n },\n next: function() {\n var chunk, _ref;\n chunk = this.part.chunks[this.num + 1];\n return chunk || ((_ref = this.part.next()) != null ? _ref.chunks[0] : void 0);\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Chunks.Line = function(part, num, string) {\n Log.Chunks.Chunk.call(this, part, num, string.slice(0, string.length - 1));\n this.isLine = true;\n return this;\n };\n\n Log.Chunks.Line.prototype = $.extend(new Log.Chunks.Chunk, {\n parse: function() {\n return [\n {\n type: 'paragraph',\n id: this.id,\n nodes: [\n {\n type: 'span',\n id: \"\" + this.id + \"-0\",\n text: this.string\n }\n ]\n }\n ];\n },\n insert: function() {\n var next, prev;\n if ((prev = this.prev()) && !prev.isLine) {\n this.trigger('insert', this.nodes[0].nodes, {\n after: prev.nodes[0].id\n });\n document.getElementById(this.nodes[0].nodes[0].id).parentNode.setAttribute('id', this.id);\n if (this.isLine && (next = this.next())) {\n return next.reinsert();\n }\n } else if (prev) {\n return this.trigger('insert', this.nodes, {\n after: prev.id\n });\n } else if (next = this.nextLine()) {\n return this.trigger('insert', this.nodes, {\n before: next.id\n });\n } else {\n return this.trigger('insert', this.nodes, {\n before: void 0\n });\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/chunks");minispade.register('log.old/engine/dom', "(function() {(function() {\n\n Log.Dom = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Dom.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Dom.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Dom.Part = function(engine, num, string) {\n this.engine = engine;\n this.num = num;\n this.string = string.replace(/\\r\\n/gm, '\\n');\n this.nodes = new Log.Dom.Nodes(this);\n return this;\n };\n\n $.extend(Log.Dom.Part.prototype, {\n SLICE: 500,\n insert: function() {\n var ix, lines, next, slices,\n _this = this;\n lines = this.string.split(/^/gm) || [];\n slices = ((function() {\n var _results;\n _results = [];\n while (lines.length > 0) {\n _results.push(lines.splice(0, this.SLICE));\n }\n return _results;\n }).call(this));\n ix = -1;\n next = function() {\n _this.insertSlice(slices.shift(), ix += 1);\n if (slices.length !== 0) {\n return setTimeout(next, 50);\n }\n };\n return next();\n },\n insertSlice: function(lines, start) {\n var ix, line, node, _i, _len, _ref, _ref1, _results;\n _ref = lines || [];\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n if ((_ref1 = this.engine.log.limit) != null ? _ref1.limited : void 0) {\n break;\n }\n node = Log.Dom.Node.create(this, start * this.SLICE + ix, line);\n _results.push(this.nodes.insert(node));\n }\n return _results;\n },\n remove: function() {\n return delete this.engine.parts[this.num];\n },\n trigger: function() {\n return this.engine.trigger.apply(this.engine, arguments);\n }\n });\n\n Log.Dom.Part.prototype.__defineGetter__('prev', function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.engine.parts[num -= 1];\n }\n return prev;\n });\n\n Log.Dom.Part.prototype.__defineGetter__('next', function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.engine.parts.length)) {\n next = this.engine.parts[num += 1];\n }\n return next;\n });\n\n Log.Dom.Nodes = function(part) {\n this.part = part;\n this.nodes = [];\n return this;\n };\n\n $.extend(Log.Dom.Nodes.prototype, {\n at: function(ix) {\n return this.nodes[ix];\n },\n insert: function(node) {\n this.nodes[node.num] = node;\n return node.insert();\n },\n remove: function(node) {\n this.nodes.splice(node.num, 1);\n if (this.nodes.length === 0) {\n return this.part.remove();\n }\n }\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('length', function() {\n return this.nodes.length;\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('first', function() {\n return this.nodes[0];\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('last', function() {\n return this.nodes[this.nodes.length - 1];\n });\n\n Log.Dom.Node = function() {};\n\n $.extend(Log.Dom.Node, {\n FOLDS_PATTERN: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(part, num, string) {\n var fold;\n if (fold = string.match(this.FOLDS_PATTERN)) {\n return new Log.Dom.Fold(part, num, fold[1], fold[2]);\n } else {\n return new Log.Dom.Paragraph(part, num, string);\n }\n },\n reinsert: function(nodes) {\n var node, _i, _j, _len, _len1;\n console.log(\"reinsert: \" + (nodes.map(function(node) {\n return node.id;\n }).join(', ')));\n for (_i = 0, _len = nodes.length; _i < _len; _i++) {\n node = nodes[_i];\n node.remove();\n }\n for (_j = 0, _len1 = nodes.length; _j < _len1; _j++) {\n node = nodes[_j];\n node.part.nodes.insert(node);\n }\n return console.log(document.firstChild.innerHTML.replace(/<\\/p>/gm, '

\\n') + '\\n');\n }\n });\n\n Log.Dom.Node.prototype.__defineGetter__('prev', function() {\n var num, prev, _ref;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.part.nodes.at(num -= 1);\n }\n return prev || ((_ref = this.part.prev) != null ? _ref.nodes.last : void 0);\n });\n\n Log.Dom.Node.prototype.__defineGetter__('next', function() {\n var next, num, _ref;\n num = this.num;\n while (!(next || num >= this.part.nodes.length)) {\n next = this.part.nodes.at(num += 1);\n }\n return next || ((_ref = this.part.next) != null ? _ref.nodes.first : void 0);\n });\n\n Log.Dom.Fold = function(part, num, event, name) {\n this.part = part;\n this.ends = true;\n this.fold = true;\n this.num = num;\n this.id = \"fold-\" + event + \"-\" + name;\n this.data = {\n type: 'fold',\n id: this.id,\n event: event,\n name: name\n };\n return this;\n };\n\n Log.Dom.Fold.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n return fail;\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Dom.Paragraph = function(part, num, string) {\n var span, _ref;\n this.part = part;\n this.num = num;\n this.ends = !!((_ref = string[string.length - 1]) != null ? _ref.match(/\\n/) : void 0);\n this.spans = new Log.Dom.Spans(this, string.replace(/\\n$/, ''));\n this.data = {\n type: 'paragraph',\n num: this.part.num,\n hidden: this.hidden,\n nodes: (function() {\n var _i, _len, _ref1, _results;\n _ref1 = this.spans.content;\n _results = [];\n for (_i = 0, _len = _ref1.length; _i < _len; _i++) {\n span = _ref1[_i];\n _results.push(span.data);\n }\n return _results;\n }).call(this)\n };\n return this;\n };\n\n Log.Dom.Paragraph.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n var next, prev, span, _i, _j, _len, _len1, _ref, _ref1, _ref2, _results;\n if ((prev = this.prev) && !prev.ends && !prev.fold) {\n if (Log.DEBUG) {\n console.log(\"P.1 - move \" + this.id + \"'s spans into prev\");\n }\n _ref = this.spans.content;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n prev.spans.append(span);\n }\n return this.part.nodes.remove(this);\n } else if ((next = this.next) && !this.ends && !next.fold) {\n if (Log.DEBUG) {\n console.log(\"P.2 - move \" + this.id + \"'s spans into next\");\n }\n _ref1 = this.spans.reverse();\n _results = [];\n for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {\n span = _ref1[_j];\n _results.push(next.prepend(span));\n }\n return _results;\n } else if ((prev != null ? prev.fold : void 0) && (prev != null ? (_ref2 = prev.element.getAttribute('class')) != null ? _ref2.match(' fold') : void 0 : void 0)) {\n if (Log.DEBUG) {\n console.log(\"P.3 - append \" + this.id + \" to fold \" + prev.id);\n }\n return this.element = this.trigger('insert', this.data, {\n after: prev.element.firstChild\n });\n } else if (prev) {\n if (Log.DEBUG) {\n console.log(\"P.4 - insert \" + this.id + \" after the parentNode of the last node of prev, id \" + prev.id);\n }\n return this.element = this.trigger('insert', this.data, {\n after: prev.element\n });\n } else if (next) {\n if (Log.DEBUG) {\n console.log(\"P.5 - insert \" + this.id + \" before the parentNode of the first node of next, id \" + next.id);\n }\n return this.element = this.trigger('insert', this.data, {\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"P.6 - insert \" + this.id + \" at the beginning of #log\");\n }\n return this.element = this.trigger('insert', this.data);\n }\n },\n remove: function() {\n var element, span, _i, _len, _ref;\n element = this.element;\n _ref = this.spans;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n this.trigger('remove', span.element);\n }\n if (!(element.childNodes.length > 1)) {\n this.trigger('remove', element);\n }\n return this.part.nodes.remove(this);\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('id', function() {\n return \"\" + this.part.num + \"-\" + this.num;\n });\n\n Log.Dom.Paragraph.prototype.__defineSetter__('element', function(element) {\n var child, span, _i, _len, _ref, _results;\n child = element.firstChild;\n _ref = this.spans.content;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n _results.push(span.element = child = child.nextSibling);\n }\n return _results;\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('element', function() {\n return this.spans.first.element.parentNode;\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('tail', function() {\n var next, parent, tail, _ref;\n parent = this.element.parentNode;\n next = this;\n tail = [];\n while ((next = next.next) && !next.fold && ((_ref = next.element) != null ? _ref.parentNode : void 0) === parent) {\n tail.push(next);\n }\n return tail;\n });\n\n Log.Dom.Spans = function(parent, string) {\n this.parent = parent;\n this.content = this.parse(parent, string);\n return this;\n };\n\n $.extend(Log.Dom.Spans.prototype, {\n parse: function(parent, string) {\n var ix, span, _i, _len, _ref, _results;\n _ref = Log.Deansi.apply(string);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n span = _ref[ix];\n _results.push(new Log.Dom.Span(parent, ix, span));\n }\n return _results;\n },\n at: function(ix) {\n return this.content[ix];\n },\n indexOf: function(span) {\n return this.content.indexOf(span);\n },\n append: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.push(span);\n return span.insert();\n },\n prepend: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.unshift(span);\n return span.insert();\n },\n reverse: function() {\n return this.content.reverse();\n },\n remove: function(span) {\n return this.content.splice(this.content.indexOf(span), 1);\n }\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('last', function() {\n return this.content[this.length - 1];\n });\n\n Log.Dom.Span = function(parent, num, data) {\n this.parent = parent;\n this.num = num;\n this.id = \"\" + parent.id + \"-\" + num;\n this.data = $.extend(data, {\n id: this.id\n });\n if (data.text.match(/\\r/)) {\n this.hidden = true;\n }\n this.data.text = data.text.replace(/^.*\\r/gm, '');\n if (this.hidden) {\n this.data[\"class\"] = ['hidden'];\n }\n return this;\n };\n\n $.extend(Log.Dom.Span.prototype, {\n insert: function() {\n var next, prev;\n if (prev = this.prev) {\n if (Log.DEBUG) {\n console.log(\"S.1 - insert \" + this.id + \" after prev \" + prev.id);\n }\n return this.insertAt({\n after: prev.element\n });\n } else if (next = this.next) {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" before next \" + next.id);\n }\n return this.insertAt({\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" into parent \" + this.parent.id);\n }\n return this.insertAt({\n into: this.parent.element\n });\n }\n },\n insertAt: function(pos) {\n var span, _i, _len, _ref, _results;\n this.element = this.trigger('insert', this.data, pos);\n if (this.hidden) {\n _ref = this.head;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n _results.push(span.hide());\n }\n return _results;\n } else {\n if (this.tail.some(function(span) {\n return span.hidden;\n })) {\n return this.hide();\n }\n }\n },\n hide: function() {\n if (!this.hidden) {\n this.trigger('hide', this.id);\n }\n return this.hidden = true;\n },\n siblings: function(direction) {\n var siblings, span, _ref;\n siblings = [];\n span = this;\n while ((span = span[direction]) && ((_ref = span.element) != null ? _ref.parentNode : void 0) === this.element.parentNode) {\n siblings.unshift(span);\n }\n return siblings;\n },\n trigger: function() {\n return this.parent.trigger.apply(this.parent, arguments);\n }\n });\n\n Log.Dom.Span.prototype.__defineGetter__('head', function() {\n return this.siblings('prev');\n });\n\n Log.Dom.Span.prototype.__defineGetter__('tail', function() {\n return this.siblings('next');\n });\n\n Log.Dom.Span.prototype.__defineGetter__('prev', function() {\n var span, _ref, _ref1;\n span = this.parent.spans.at(this.parent.spans.indexOf(this) - 1);\n return span || ((_ref = this.parent.prev) != null ? (_ref1 = _ref.spans) != null ? _ref1.last : void 0 : void 0);\n });\n\n Log.Dom.Span.prototype.__defineGetter__('next', function() {\n var span, _ref, _ref1;\n span = this.parent.spans.at(this.parent.spans.indexOf(this) + 1);\n return span || ((_ref = this.parent.next) != null ? (_ref1 = _ref.spans) != null ? _ref1.first : void 0 : void 0);\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/dom");minispade.register('log.old/engine/dom2', "(function() {(function() {\n\n Log.Dom = function(log) {\n this.log = log;\n this.parts = [];\n this.nodes = new Log.Dom.Nodes(this);\n return this;\n };\n\n $.extend(Log.Dom.prototype, {\n set: function(num, string) {\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n this.parts[num] = true;\n return this.insert(num, string);\n }\n },\n SLICE: 500,\n insert: function(num, string) {\n var ix, lines, next, slices,\n _this = this;\n lines = string.split(/^/gm) || [];\n slices = ((function() {\n var _results;\n _results = [];\n while (lines.length > 0) {\n _results.push(lines.splice(0, this.SLICE));\n }\n return _results;\n }).call(this));\n ix = -1;\n next = function() {\n _this.insertSlice(num, slices.shift(), ix += 1);\n if (slices.length !== 0) {\n return setTimeout(next, 50);\n }\n };\n return next();\n },\n insertSlice: function(num, lines, start) {\n var ix, line, _i, _len, _ref, _ref1, _results;\n _ref = lines || [];\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n if ((_ref1 = this.log.limit) != null ? _ref1.limited : void 0) {\n break;\n }\n _results.push(this.nodes.insert(Log.Dom.Node.create(this, [num, start * this.SLICE + ix], line)));\n }\n return _results;\n }\n });\n\n Log.Dom.Nodes = function(parent) {\n this.parent = parent;\n this.content = [];\n return this;\n };\n\n $.extend(Log.Dom.Nodes.prototype, {\n at: function(ix) {\n return this.content[ix];\n },\n insert: function(node) {\n return this.content[node.num] = node;\n },\n remove: function(node) {\n return this.content.splice(this.content.indexOf(node), 1);\n }\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('length', function() {\n return this.content.length;\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('last', function() {\n return this.content[this.content.length - 1];\n });\n\n Log.Dom.Node = function() {};\n\n $.extend(Log.Dom.Node, {\n FOLDS_PATTERN: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(parent, ids, string) {\n var fold;\n if (fold = string.match(this.FOLDS_PATTERN)) {\n return new Log.Dom.Fold(parent, ids, fold[1], fold[2]);\n } else {\n return new Log.Dom.Paragraph(parent, ids, string);\n }\n }\n });\n\n Log.Dom.Node.prototype.__defineGetter__('prev', function() {\n var num, prev, _ref;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.parent.nodes.at(num -= 1);\n }\n return prev || ((_ref = this.parent.prev) != null ? _ref.nodes.last : void 0);\n });\n\n Log.Dom.Node.prototype.__defineGetter__('next', function() {\n var next, num, _ref;\n num = this.num;\n while (!(next || num >= this.parent.nodes.length)) {\n next = this.parent.nodes.at(num += 1);\n }\n return next || ((_ref = this.parent.next) != null ? _ref.nodes.first : void 0);\n });\n\n Log.Dom.Paragraph = function(parent, ids, string) {\n var _ref;\n this.parent = parent;\n this.ids = ids;\n this.id = ids.join('-');\n this.ends = !!((_ref = string[string.length - 1]) != null ? _ref.match(/\\n/) : void 0);\n this.spans = new Log.Dom.Spans(this, ids, string.replace(/\\n$/, ''));\n return this;\n };\n\n Log.Dom.Paragraph.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n var next, prev,\n _this = this;\n if ((prev = this.prev) && !prev.ends && !prev.fold) {\n if (Log.DEBUG) {\n console.log(\"P.1 - move \" + this.id + \"'s spans into prev (\" + prev.id + \")\");\n }\n this.spans.content.slice().forEach(function(span) {\n return prev.spans.insert(span);\n });\n return this.remove();\n } else if ((next = this.next) && !this.ends && !next.fold) {\n if (Log.DEBUG) {\n console.log(\"P.2 - move next's (\" + next.id + \") spans into \" + this.id);\n }\n this.next.spans.content.slice().forEach(function(span) {\n return _this.spans.insert(span);\n });\n return next.remove();\n }\n },\n remove: function() {\n return this.parent.nodes.remove(this);\n }\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('id', function() {\n return \"\" + this.parent.num + \"-\" + this.num;\n });\n\n Log.Dom.Spans = function(parent, ids, string) {\n this.parent = parent;\n this.content = this.parse(parent, ids, string);\n return this;\n };\n\n $.extend(Log.Dom.Spans.prototype, {\n parse: function(parent, ids, string) {\n var ix, span, _i, _len, _ref, _results;\n _ref = Log.Deansi.apply(string);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n span = _ref[ix];\n _results.push(new Log.Dom.Span(parent, ids.concat([ix]), span));\n }\n return _results;\n },\n at: function(ix) {\n return this.content[ix];\n },\n indexOf: function(span) {\n return this.content.indexOf(span);\n },\n insert: function(span) {\n var head, ix, prev;\n console.log('insert', span.id);\n span.parent.spans.remove(span);\n span.parent = this.parent;\n head = this.content.filter(function(s) {\n return s.id < span.id;\n });\n if (prev = head[head.length - 1]) {\n ix = this.content.indexOf(prev) + 1;\n }\n return this.content.splice(ix || 0, 0, span);\n },\n prepend: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.unshift(span);\n return span.insert();\n },\n remove: function(span) {\n return this.content.splice(this.content.indexof(span), 1);\n }\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('last', function() {\n return this.content[this.content.length - 1];\n });\n\n Log.Dom.Span = function(parent, ids, data) {\n this.parent = parent;\n this.ids = ids;\n this.id = ids.join('-');\n this.data = $.extend(data, {\n id: this.id\n });\n if (data.text.match(/\\r/)) {\n this.hidden = true;\n }\n this.data.text = data.text.replace(/^.*\\r/gm, '');\n if (this.hidden) {\n this.data[\"class\"] = ['hidden'];\n }\n return this;\n };\n\n $.extend(Log.Dom.Span.prototype, {\n insert: function() {\n var next, prev;\n if (prev = this.prev) {\n if (Log.DEBUG) {\n console.log(\"S.1 - insert \" + this.id + \" after prev \" + prev.id);\n }\n return this.insertAt({\n after: prev.element\n });\n } else if (next = this.next) {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" before next \" + next.id);\n }\n return this.insertAt({\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" into parent \" + this.parent.id);\n }\n return this.insertAt({\n into: this.parent.element\n });\n }\n },\n insertAt: function(pos) {\n return this.element = this.trigger('insert', this.data, pos);\n }\n });\n\n Log.Dom.Span.prototype.__defineGetter__('prev', function() {\n var span, _ref, _ref1;\n console.log(this.num);\n span = this.parent.spans.at(this.num - 1);\n return span || ((_ref = this.parent.prev) != null ? (_ref1 = _ref.spans) != null ? _ref1.last : void 0 : void 0);\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/dom2");minispade.register('log.old/engine/live', "(function() {(function() {\n\n Log.Live = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Live.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Live.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Live.Part = function(log, num, string) {\n var ix, line;\n this.log = log;\n this.num = num;\n this.lines = (function() {\n var _i, _len, _ref, _results;\n _ref = string.split(/^/m);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n _results.push(new Log.Live.Line(this, ix, line));\n }\n return _results;\n }).call(this);\n return this;\n };\n\n $.extend(Log.Live.Part.prototype, {\n insert: function() {\n return new Log.Live.Context(this.log, this).insert();\n },\n head: function() {\n var head, line;\n head = [];\n line = this.lines[0];\n while ((line = line != null ? line.prev() : void 0) && !line.isNewline()) {\n head.unshift(line);\n }\n return head;\n },\n tail: function() {\n var line, tail;\n tail = [];\n line = this.lines[this.lines.length - 1];\n while (line = line != null ? line.next() : void 0) {\n tail.push(line);\n if (line != null ? line.isNewline() : void 0) {\n break;\n }\n }\n return tail;\n },\n prev: function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.log.parts[num -= 1];\n }\n return prev;\n },\n next: function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.log.parts.length)) {\n next = this.log.parts[num += 1];\n }\n return next;\n }\n });\n\n Log.Live.Line = function(part, num, string) {\n this.part = part;\n this.num = num;\n this.id = \"\" + part.num + \"-\" + num;\n this.string = string;\n return this;\n };\n\n $.extend(Log.Live.Line.prototype, {\n prev: function() {\n var line, _ref;\n line = this.part.lines[this.num - 1];\n return line || ((_ref = this.part.prev()) != null ? _ref.lines.slice(-1)[0] : void 0);\n },\n next: function() {\n var line, _ref;\n line = this.part.lines[this.num + 1];\n return line || ((_ref = this.part.next()) != null ? _ref.lines[0] : void 0);\n },\n isNewline: function() {\n return this.string[this.string.length - 1] === \"\\n\";\n },\n isFold: function() {\n return this.string.indexOf('fold') !== -1;\n },\n clone: function() {\n return new Log.Live.Line(this.part, this.num, this.string);\n }\n });\n\n Log.Live.Context = function(log, part) {\n this.log = log;\n this.part = part;\n this.head = part.head();\n this.tail = part.tail();\n this.lines = this.join(this.head.concat(part.lines).concat(this.tail));\n return this;\n };\n\n $.extend(Log.Live.Context.prototype, {\n insert: function() {\n var ids;\n ids = this.head.concat(this.tail).map(function(line) {\n return line.id;\n });\n if (ids.length !== 0) {\n this.log.trigger('remove', ids);\n }\n return this.log.trigger('insert', this.after(), this.nodes());\n },\n nodes: function() {\n var _this = this;\n return this.lines.map(function(line) {\n var fold, string;\n string = line.string;\n if (fold = _this.defold(string)) {\n return $.extend(fold, {\n id: line.id\n });\n } else {\n return {\n id: line.id,\n nodes: _this.deansi(string)\n };\n }\n });\n },\n join: function(all) {\n var line, lines;\n lines = [];\n while (line = all.pop()) {\n if (lines.length === 0 || line.isNewline()) {\n lines.unshift(line.clone());\n } else {\n lines[0].string = line.string + lines[0].string;\n }\n }\n return lines;\n },\n after: function() {\n var line, _ref;\n line = (_ref = this.part.lines[0]) != null ? _ref.prev() : void 0;\n while (line && !line.isNewline() && !line.isFold()) {\n line = line.prev();\n }\n return line != null ? line.id : void 0;\n },\n defold: function(string) {\n var matches;\n if (matches = string.match(/fold:(start|end):([\\w_\\-\\.]+)/)) {\n return {\n type: 'fold',\n event: matches[1],\n name: matches[2]\n };\n }\n },\n deansi: function(string) {\n return Log.Deansi.apply(string);\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/live");minispade.register('log.old/folds', "(function() {(function() {\n\n Log.Folds = function() {\n this.folds = {};\n return this;\n };\n\n Log.Folds.prototype = $.extend(new Log.Listener, {\n insert: function(log, data, pos) {\n var fold, _base, _name;\n if (data.type === 'fold') {\n fold = (_base = this.folds)[_name = data.name] || (_base[_name] = new Log.Folds.Fold);\n fold.receive(data);\n }\n return true;\n }\n });\n\n Log.Folds.Fold = function() {\n return this;\n };\n\n $.extend(Log.Folds.Fold.prototype, {\n receive: function(data) {\n this[data.event] = data.id;\n if (this.start && this.end && !this.active) {\n return this.activate();\n }\n },\n activate: function() {\n var node, _i, _len, _ref;\n console.log(\"F - activate \" + this.start);\n console.log(document.firstChild.innerHTML.replace(/

rcv \" + num + \" \" + (JSON.stringify(string)) + \"\");\n },\n insert: function(log, after, datas) {\n return this.log(\"ins \" + (datas.map(function(data) {\n return data.id;\n }).join(', ')) + \", after: \" + (after || '?') + \", \" + (JSON.stringify(datas)));\n },\n remove: function(log, id) {\n return this.log(\"rem \" + id);\n },\n log: function(line) {\n return $('#events').append(\"\" + line + \"\\n\");\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/instrument");minispade.register('log.old/limit', "(function() {(function() {\n\n Log.Limit = function(max_lines) {\n this.max_lines = max_lines || 1000;\n return this;\n };\n\n Log.Limit.prototype = $.extend(new Log.Listener, {\n count: 0,\n insert: function(log, line, pos) {\n if (line.type === 'paragraph' && !line.hidden) {\n return this.count += 1;\n }\n }\n });\n\n Log.Limit.prototype.__defineGetter__('limited', function() {\n return this.count >= this.max_lines;\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/limit");minispade.register('log.old/renderer/inner_html', "(function() {(function() {\n\n Log.InnerHtmlRenderer = function() {\n this.frag = document.createDocumentFragment();\n this.div = document.createElement('div');\n return this;\n };\n\n Log.InnerHtmlRenderer.prototype = $.extend(new Log.Listener, {\n remove: function(log, ids) {\n var id, node, _i, _len, _ref, _results;\n _results = [];\n for (_i = 0, _len = ids.length; _i < _len; _i++) {\n id = ids[_i];\n node = document.getElementById(id);\n if (node && !((_ref = node.getAttribute('class')) != null ? _ref.match(/fold/) : void 0)) {\n _results.push(node.parentNode.removeChild(node));\n } else {\n _results.push(void 0);\n }\n }\n return _results;\n },\n insert: function(log, after, nodes) {\n var html;\n log = document.getElementById('log');\n html = this.render(nodes);\n if (log.childNodes.length === 0) {\n return log.innerHTML = html;\n } else if (after) {\n after = document.getElementById(after);\n return this.insertAfter(this.fragmentFrom(html), after);\n } else {\n log = document.getElementById('log');\n return log.insertBefore(this.fragmentFrom(html), log.firstChild);\n }\n },\n render: function(nodes) {\n var node;\n return ((function() {\n var _i, _len, _results;\n _results = [];\n for (_i = 0, _len = nodes.length; _i < _len; _i++) {\n node = nodes[_i];\n _results.push(this.renderNode(node));\n }\n return _results;\n }).call(this)).join('');\n },\n renderNode: function(node) {\n var type;\n node.type || (node.type = 'paragraph');\n type = node.type[0].toUpperCase() + node.type.slice(1);\n return this[\"render\" + type](node) || '';\n },\n renderParagraph: function(node) {\n var html, style;\n if (node.hidden) {\n style = ' style=\"display:none\"';\n }\n html = \"

\";\n html += ((function() {\n var _i, _len, _ref, _results;\n _ref = node.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n _results.push(this.renderNode(node));\n }\n return _results;\n }).call(this)).join('');\n return html + '

';\n },\n renderFold: function(node) {\n if (!document.getElementById(node.id)) {\n return \"
\";\n }\n },\n renderSpan: function(node) {\n return \"\" + (this.clean(node.text)) + \"\";\n },\n renderText: function(node) {\n return this.clean(node.text);\n },\n clean: function(text) {\n return text.replace(/\\n/gm, '');\n },\n fragmentFrom: function(html) {\n var div, frag, node;\n frag = this.frag.cloneNode();\n div = this.div.cloneNode();\n div.innerHTML = html;\n while (node = div.firstChild) {\n frag.appendChild(node);\n }\n return frag;\n },\n insertAfter: function(node, after) {\n if (after.nextSibling) {\n return after.parentNode.insertBefore(node, after.nextSibling);\n } else {\n return after.parentNode.appendChild(node);\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/renderer/inner_html");minispade.register('log.old/renderer/jquery', "(function() {(function() {\n\n Log.JqueryRenderer = function() {};\n\n Log.JqueryRenderer.prototype = $.extend(new Log.Listener, {\n remove: function(log, ids) {\n var id, _i, _len, _results;\n _results = [];\n for (_i = 0, _len = ids.length; _i < _len; _i++) {\n id = ids[_i];\n _results.push($(\"#log #\" + id).remove());\n }\n return _results;\n },\n insert: function(log, after, datas) {\n var html,\n _this = this;\n html = datas.map(function(data) {\n return _this.render(data);\n });\n return after && $(\"#log #\" + after).after(html) || $('#log').prepend(html);\n },\n render: function(data) {\n var node, nodes, text;\n nodes = (function() {\n var _i, _len, _ref, _results;\n _ref = data.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n text = node.text.replace(/\\n/gm, '');\n if (node.type === 'span') {\n text = \"\" + text + \"\";\n }\n _results.push(\"

\" + text + \"

\");\n }\n return _results;\n }).call(this);\n return nodes.join(\"\\n\");\n },\n style: function(data) {\n return data.hidden && 'display: none;' || '';\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/renderer/jquery");minispade.register('log/deansi', "(function() {(function() {\n\n Log.Deansi = {\n CLEAR_ANSI: /(\\e|\\033)\\[K|(\\e|\\033)\\[K|(\\e|\\033)\\[\\d+G|(\\e|\\033)\\(B|(\\e|\\033)M|(\\e|\\033)\\[\\?25(l|h)/gm,\n apply: function(string) {\n var nodes,\n _this = this;\n if (!string) {\n return [];\n }\n string = string.replace(this.CLEAR_ANSI, '');\n nodes = ansiparse(string).map(function(part) {\n return _this.node(part);\n });\n if (nodes.length === 0) {\n nodes.push(this.node({\n text: ''\n }));\n }\n return nodes;\n },\n node: function(part) {\n var classes, node;\n node = {\n type: 'span',\n text: part.text\n };\n if (classes = this.classes(part)) {\n node[\"class\"] = classes.join(' ');\n }\n return node;\n },\n classes: function(part) {\n var result;\n result = [];\n result = result.concat(this.colors(part));\n if (result.length > 0) {\n return result;\n }\n },\n colors: function(part) {\n var colors;\n colors = [];\n if (part.foreground) {\n colors.push(part.foreground);\n }\n if (part.background) {\n colors.push(\"bg-\" + part.background);\n }\n if (part.bold) {\n colors.push('bold');\n }\n if (part.italic) {\n colors.push('italic');\n }\n return colors;\n },\n hidden: function(part) {\n if (part.text.match(/\\r/)) {\n part.text = part.text.replace(/^.*\\r/gm, '');\n return true;\n }\n }\n };\n\n}).call(this);\n\n})();\n//@ sourceURL=log/deansi");minispade.register('log/folds', "(function() {(function() {\n\n Log.Folds = function() {\n this.folds = {};\n return this;\n };\n\n Log.extend(Log.Folds.prototype, {\n add: function(data) {\n var fold, _base, _name;\n fold = (_base = this.folds)[_name = data.name] || (_base[_name] = new Log.Folds.Fold);\n fold.receive(data);\n return fold.active;\n }\n });\n\n Log.Folds.Fold = function() {\n return this;\n };\n\n Log.extend(Log.Folds.Fold.prototype, {\n receive: function(data) {\n this[data.event] = data.id;\n if (this.start && this.end && !this.active) {\n return this.activate();\n }\n },\n activate: function() {\n var node, _i, _len, _ref;\n if (Log.DEBUG) {\n console.log(\"F - activate \" + this.start);\n }\n _ref = this.nodes;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n this.fold.appendChild(node);\n }\n this.fold.setAttribute('class', this.classes());\n return this.active = true;\n },\n classes: function() {\n var classes;\n classes = this.fold.getAttribute('class').split(' ');\n classes.push('fold');\n if (this.fold.childNodes.length > 2) {\n classes.push('active');\n }\n return classes.join(' ');\n }\n });\n\n Object.defineProperty(Log.Folds.Fold.prototype, 'fold', {\n get: function() {\n return this._fold || (this._fold = document.getElementById(this.start));\n }\n });\n\n Object.defineProperty(Log.Folds.Fold.prototype, 'nodes', {\n get: function() {\n var node, nodes;\n node = this.fold;\n nodes = [];\n while ((node = node.nextSibling) && node.id !== this.end) {\n nodes.push(node);\n }\n return nodes;\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/folds");minispade.register('log/limit', "(function() {(function() {\n\n Log.Limit = function(max_lines) {\n this.max_lines = max_lines || 1000;\n return this;\n };\n\n Log.Limit.prototype = Log.extend(new Log.Listener, {\n count: 0,\n insert: function(log, node, pos) {\n if (node.type === 'paragraph' && !node.hidden) {\n return this.count += 1;\n }\n }\n });\n\n Object.defineProperty(Log.Limit.prototype, 'limited', {\n get: function() {\n return this.count >= this.max_lines;\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/limit");minispade.register('log/nodes', "(function() {(function() {\n var asteriskRRegexp, newLineAtTheEndRegexp, newLineRegexp, rRegexp;\n\n Log.Node = function(id, num) {\n this.id = id;\n this.num = num;\n this.key = Log.Node.key(this.id);\n this.children = new Log.Nodes(this);\n return this;\n };\n\n Log.extend(Log.Node, {\n key: function(id) {\n if (id) {\n return id.split('-').map(function(i) {\n return '000000'.concat(i).slice(-6);\n }).join('');\n }\n }\n });\n\n Log.extend(Log.Node.prototype, {\n addChild: function(node) {\n return this.children.add(node);\n },\n remove: function() {\n this.log.remove(this.element);\n return this.parent.children.remove(this);\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'log', {\n get: function() {\n var _ref;\n return this._log || (this._log = ((_ref = this.parent) != null ? _ref.log : void 0) || this.parent);\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'firstChild', {\n get: function() {\n return this.children.first;\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'lastChild', {\n get: function() {\n return this.children.last;\n }\n });\n\n Log.Nodes = function(parent) {\n if (parent) {\n this.parent = parent;\n }\n this.items = [];\n this.index = {};\n return this;\n };\n\n Log.extend(Log.Nodes.prototype, {\n add: function(item) {\n var ix, next, prev, _ref, _ref1;\n ix = this.position(item) || 0;\n this.items.splice(ix, 0, item);\n if (this.parent) {\n item.parent = this.parent;\n }\n prev = function(item) {\n while (item && !item.children.last) {\n item = item.prev;\n }\n return item != null ? item.children.last : void 0;\n };\n next = function(item) {\n while (item && !item.children.first) {\n item = item.next;\n }\n return item != null ? item.children.first : void 0;\n };\n if (item.prev = this.items[ix - 1] || prev((_ref = this.parent) != null ? _ref.prev : void 0)) {\n item.prev.next = item;\n }\n if (item.next = this.items[ix + 1] || next((_ref1 = this.parent) != null ? _ref1.next : void 0)) {\n item.next.prev = item;\n }\n return item;\n },\n remove: function(item) {\n this.items.splice(this.items.indexOf(item), 1);\n if (item.next) {\n item.next.prev = item.prev;\n }\n if (item.prev) {\n item.prev.next = item.next;\n }\n if (this.items.length === 0) {\n return this.parent.remove();\n }\n },\n position: function(item) {\n var ix, _i, _ref;\n for (ix = _i = _ref = this.items.length - 1; _i >= 0; ix = _i += -1) {\n if (this.items[ix].key < item.key) {\n return ix + 1;\n }\n }\n },\n indexOf: function() {\n return this.items.indexOf.apply(this.items, arguments);\n },\n slice: function() {\n return this.items.slice.apply(this.items, arguments);\n },\n each: function(func) {\n return this.items.slice().forEach(func);\n },\n map: function(func) {\n return this.items.map(func);\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'first', {\n get: function() {\n return this.items[0];\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'last', {\n get: function() {\n return this.items[this.length - 1];\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'length', {\n get: function() {\n return this.items.length;\n }\n });\n\n Log.Part = function(id, num, string) {\n Log.Node.apply(this, arguments);\n this.string = string || '';\n this.string = this.string.replace(/\\033\\[1000D/gm, '\\r');\n this.string = this.string.replace(/\\r+\\n/gm, '\\n');\n this.strings = this.string.split(/^/gm) || [];\n this.slices = ((function() {\n var _results;\n _results = [];\n while (this.strings.length > 0) {\n _results.push(this.strings.splice(0, Log.SLICE));\n }\n return _results;\n }).call(this));\n return this;\n };\n\n Log.extend(Log.Part, {\n create: function(log, num, string) {\n var part;\n part = new Log.Part(num.toString(), num, string);\n log.addChild(part);\n return part.process(0, -1);\n }\n });\n\n Log.Part.prototype = Log.extend(new Log.Node, {\n remove: function() {},\n process: function(slice, num) {\n var node, span, spans, string, _i, _j, _len, _len1, _ref, _ref1, _ref2,\n _this = this;\n _ref = this.slices[slice] || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n string = _ref[_i];\n if ((_ref1 = this.log.limit) != null ? _ref1.limited : void 0) {\n return;\n }\n spans = [];\n _ref2 = Log.Deansi.apply(string);\n for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {\n node = _ref2[_j];\n span = Log.Span.create(this, \"\" + this.id + \"-\" + (num += 1), num, node.text, node[\"class\"]);\n span.render();\n spans.push(span);\n }\n if (spans[0] && spans[0].line.cr) {\n spans[0].line.clear();\n }\n }\n if (!(slice >= this.slices.length - 1)) {\n return setTimeout((function() {\n return _this.process(slice + 1, num);\n }), Log.TIMEOUT);\n }\n }\n });\n\n asteriskRRegexp = new RegExp(\".*\\r\", 'gm');\n\n newLineAtTheEndRegexp = new RegExp(\"\\n$\");\n\n newLineRegexp = new RegExp(\"\\n\");\n\n rRegexp = new RegExp(\"\\r\");\n\n Log.Span = function(id, num, text, classes) {\n var fold, _ref;\n Log.Node.apply(this, arguments);\n if (fold = text.match(Log.FOLD)) {\n this.fold = true;\n this.event = fold[1];\n this.text = this.name = fold[2];\n } else {\n this.text = text;\n if (this.text.indexOf(\"\\r\") !== -1) {\n this.text = this.text.replace(asteriskRRegexp, '');\n }\n this.text = this.text.replace(newLineAtTheEndRegexp, '');\n this.nl = !!((_ref = text[text.length - 1]) != null ? _ref.match(newLineRegexp) : void 0);\n this.cr = !!text.match(rRegexp);\n this[\"class\"] = this.cr && ['clears'] || classes;\n }\n return this;\n };\n\n Log.extend(Log.Span, {\n create: function(parent, id, num, text, classes) {\n var span;\n span = new Log.Span(id, num, text, classes);\n parent.addChild(span);\n return span;\n },\n render: function(parent, id, num, text, classes) {\n var span;\n span = this.create(parent, id, num, text, classes);\n return span.render();\n }\n });\n\n Log.Span.prototype = Log.extend(new Log.Node, {\n render: function() {\n var tail;\n if (!this.fold && this.prev && !this.prev.fold && !this.prev.nl) {\n if (Log.DEBUG) {\n console.log(\"S.1 insert \" + this.id + \" after prev \" + this.prev.id);\n }\n this.log.insert(this.data, {\n after: this.prev.element\n });\n this.line = this.prev.line;\n } else if (!this.fold && this.next && !this.next.fold) {\n if (Log.DEBUG) {\n console.log(\"S.2 insert \" + this.id + \" before next \" + this.next.id);\n }\n this.log.insert(this.data, {\n before: this.next.element\n });\n this.line = this.next.line;\n } else {\n this.line = Log.Line.create(this.log, [this]);\n this.line.render();\n }\n if (this.nl && (tail = this.tail).length > 0) {\n return this.split(tail);\n }\n },\n remove: function() {\n Log.Node.prototype.remove.apply(this);\n if (this.line) {\n return this.line.remove(this);\n }\n },\n split: function(spans) {\n var line, span, _i, _len;\n if (Log.DEBUG) {\n console.log(\"S.3 split [\" + (spans.map(function(span) {\n return span.id;\n }).join(', ')) + \"]\");\n }\n for (_i = 0, _len = spans.length; _i < _len; _i++) {\n span = spans[_i];\n this.log.remove(span.element);\n }\n line = Log.Line.create(this.log, spans);\n line.render();\n if (line.cr) {\n return line.clear();\n }\n },\n clear: function() {\n if (this.prev && this.isSibling(this.prev) && this.isSequence(this.prev)) {\n this.prev.clear();\n return this.prev.remove();\n }\n },\n isSequence: function(other) {\n return this.parent.num - other.parent.num === this.log.children.indexOf(this.parent) - this.log.children.indexOf(other.parent);\n },\n isSibling: function(other) {\n var _ref, _ref1;\n return ((_ref = this.element) != null ? _ref.parentNode : void 0) === ((_ref1 = other.element) != null ? _ref1.parentNode : void 0);\n },\n siblings: function(type) {\n var siblings, span;\n siblings = [];\n while ((span = (span || this)[type]) && this.isSibling(span)) {\n siblings.push(span);\n }\n return siblings;\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'data', {\n get: function() {\n return {\n id: this.id,\n type: 'span',\n text: this.text,\n \"class\": this[\"class\"]\n };\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'line', {\n get: function() {\n return this._line;\n },\n set: function(line) {\n if (this.line) {\n this.line.remove(this);\n }\n this._line = line;\n return this.line.add(this);\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'element', {\n get: function() {\n return document.getElementById(this.id);\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'head', {\n get: function() {\n return this.siblings('prev').reverse();\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'tail', {\n get: function() {\n return this.siblings('next');\n }\n });\n\n Log.Line = function(log) {\n this.log = log;\n this.spans = [];\n return this;\n };\n\n Log.extend(Log.Line, {\n create: function(log, spans) {\n var line, span, _i, _len;\n if ((span = spans[0]) && span.fold) {\n line = new Log.Fold(log, span.event, span.name);\n } else {\n line = new Log.Line(log);\n }\n for (_i = 0, _len = spans.length; _i < _len; _i++) {\n span = spans[_i];\n span.line = line;\n }\n return line;\n }\n });\n\n Log.extend(Log.Line.prototype, {\n add: function(span) {\n var ix;\n if (span.cr) {\n this.cr = true;\n }\n if (this.spans.indexOf(span) > -1) {\n\n } else if ((ix = this.spans.indexOf(span.prev)) > -1) {\n return this.spans.splice(ix + 1, 0, span);\n } else if ((ix = this.spans.indexOf(span.next)) > -1) {\n return this.spans.splice(ix, 0, span);\n } else {\n return this.spans.push(span);\n }\n },\n remove: function(span) {\n var ix;\n if ((ix = this.spans.indexOf(span)) > -1) {\n return this.spans.splice(ix, 1);\n }\n },\n render: function() {\n var fold;\n if ((fold = this.prev) && fold.event === 'start' && fold.active) {\n if (Log.DEBUG) {\n console.log(\"L.0 insert \" + this.id + \" into fold \" + fold.id);\n }\n fold = this.log.folds.folds[fold.name].fold;\n return this.element = this.log.insert(this.data, {\n into: fold\n });\n } else if (this.prev) {\n if (Log.DEBUG) {\n console.log(\"L.1 insert \" + this.spans[0].id + \" after prev \" + this.prev.id);\n }\n return this.element = this.log.insert(this.data, {\n after: this.prev.element\n });\n } else if (this.next) {\n if (Log.DEBUG) {\n console.log(\"L.2 insert \" + this.spans[0].id + \" before next \" + this.next.id);\n }\n return this.element = this.log.insert(this.data, {\n before: this.next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"L.3 insert \" + this.spans[0].id + \" into #log\");\n }\n return this.element = this.log.insert(this.data);\n }\n },\n clear: function() {\n var cr, _i, _len, _ref, _results;\n _ref = this.crs;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n cr = _ref[_i];\n _results.push(cr.clear());\n }\n return _results;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'id', {\n get: function() {\n var _ref;\n return (_ref = this.spans[0]) != null ? _ref.id : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'data', {\n get: function() {\n return {\n type: 'paragraph',\n nodes: this.nodes\n };\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'nodes', {\n get: function() {\n return this.spans.map(function(span) {\n return span.data;\n });\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'prev', {\n get: function() {\n var _ref;\n return (_ref = this.spans[0].prev) != null ? _ref.line : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'next', {\n get: function() {\n var _ref;\n return (_ref = this.spans[this.spans.length - 1].next) != null ? _ref.line : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'crs', {\n get: function() {\n return this.spans.filter(function(span) {\n return span.cr;\n });\n }\n });\n\n Log.Fold = function(log, event, name) {\n Log.Line.apply(this, arguments);\n this.fold = true;\n this.event = event;\n this.name = name;\n return this;\n };\n\n Log.Fold.prototype = Log.extend(new Log.Line, {\n render: function() {\n var element, _ref;\n if (this.prev) {\n if (Log.DEBUG) {\n console.log(\"F.1 insert \" + this.id + \" after prev \" + this.prev.id);\n }\n element = this.prev.element || this.prev.element.parentNode;\n this.element = this.log.insert(this.data, {\n after: element\n });\n } else if (this.next) {\n if (Log.DEBUG) {\n console.log(\"F.2 insert \" + this.id + \" before next \" + this.next.id);\n }\n element = this.next.element || this.next.element.parentNode;\n this.element = this.log.insert(this.data, {\n before: element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"F.3 insert \" + this.id);\n }\n this.element = this.log.insert(this.data);\n }\n if (this.span.next && ((_ref = this.span.prev) != null ? _ref.isSibling(this.span.next) : void 0)) {\n this.span.prev.split([this.span.next].concat(this.span.next.tail));\n }\n return this.active = this.log.folds.add(this.data);\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'id', {\n get: function() {\n return \"fold-\" + this.event + \"-\" + this.name;\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'span', {\n get: function() {\n return this.spans[0];\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'data', {\n get: function() {\n return {\n type: 'fold',\n id: this.id,\n event: this.event,\n name: this.name\n };\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/nodes");minispade.register('log/renderer', "(function() {(function() {\n\n Log.Renderer = function() {\n this.frag = document.createDocumentFragment();\n this.para = this.createParagraph();\n this.span = this.createSpan();\n this.text = document.createTextNode('');\n this.fold = this.createFold();\n return this;\n };\n\n Log.extend(Log.Renderer.prototype, {\n insert: function(data, pos) {\n var after, before, into, node;\n node = this.render(data);\n if (into = pos != null ? pos.into : void 0) {\n if (typeof into === 'String') {\n into = document.getElementById(pos != null ? pos.into : void 0);\n }\n this.appendTo(node, into);\n } else if (after = pos != null ? pos.after : void 0) {\n if (typeof after === 'String') {\n after = document.getElementById(pos);\n }\n this.insertAfter(node, after);\n } else if (before = pos != null ? pos.before : void 0) {\n if (typeof before === 'String') {\n before = document.getElementById(pos != null ? pos.before : void 0);\n }\n this.insertBefore(node, before);\n } else {\n this.insertBefore(node);\n }\n return node;\n },\n hide: function(node) {\n node.setAttribute('class', this.addClass(node.getAttribute('class'), 'hidden'));\n return node;\n },\n remove: function(node) {\n if (node) {\n node.parentNode.removeChild(node);\n }\n return node;\n },\n render: function(data) {\n var frag, node, type, _i, _len;\n if (data instanceof Array) {\n frag = this.frag.cloneNode(true);\n for (_i = 0, _len = data.length; _i < _len; _i++) {\n node = data[_i];\n node = this.render(node);\n if (node) {\n frag.appendChild(node);\n }\n }\n return frag;\n } else {\n data.type || (data.type = 'paragraph');\n type = data.type[0].toUpperCase() + data.type.slice(1);\n return this[\"render\" + type](data);\n }\n },\n renderParagraph: function(data) {\n var node, para, type, _i, _len, _ref;\n para = this.para.cloneNode(true);\n if (data.id) {\n para.setAttribute('id', data.id);\n }\n if (data.hidden) {\n para.setAttribute('style', 'display: none;');\n }\n _ref = data.nodes || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n type = node.type[0].toUpperCase() + node.type.slice(1);\n node = this[\"render\" + type](node);\n para.appendChild(node);\n }\n return para;\n },\n renderFold: function(data) {\n var fold;\n fold = this.fold.cloneNode(true);\n fold.setAttribute('id', data.id || (\"fold-\" + data.event + \"-\" + data.name));\n fold.setAttribute('class', \"fold-\" + data.event);\n if (data.event === 'start') {\n fold.lastChild.lastChild.nodeValue = data.name;\n } else {\n fold.removeChild(fold.lastChild);\n }\n return fold;\n },\n renderSpan: function(data) {\n var span;\n span = this.span.cloneNode(true);\n if (data.id) {\n span.setAttribute('id', data.id);\n }\n if (data[\"class\"]) {\n span.setAttribute('class', data[\"class\"]);\n }\n span.lastChild.nodeValue = data.text;\n return span;\n },\n renderText: function(data) {\n var text;\n text = this.text.cloneNode(true);\n text.nodeValue = data.text;\n return text;\n },\n createParagraph: function() {\n var para;\n para = document.createElement('p');\n para.appendChild(document.createElement('a'));\n return para;\n },\n createFold: function() {\n var fold;\n fold = document.createElement('div');\n fold.appendChild(this.createSpan());\n fold.lastChild.setAttribute('class', 'fold-name');\n return fold;\n },\n createSpan: function() {\n var span;\n span = document.createElement('span');\n span.appendChild(document.createTextNode(''));\n return span;\n },\n insertBefore: function(node, other) {\n var log;\n if (other) {\n return other.parentNode.insertBefore(node, other);\n } else {\n log = document.getElementById('log');\n return log.insertBefore(node, log.firstChild);\n }\n },\n insertAfter: function(node, other) {\n if (other.nextSibling) {\n return this.insertBefore(node, other.nextSibling);\n } else {\n return this.appendTo(node, other.parentNode);\n }\n },\n appendTo: function(node, other) {\n return other.appendChild(node);\n },\n addClass: function(classes, string) {\n if (classes != null ? classes.indexOf(string) : void 0) {\n return;\n }\n if (classes) {\n return \"\" + classes + \" \" + string;\n } else {\n return string;\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/renderer"); \ No newline at end of file From 2c5071bbba804d911ca49c3e2ef40a23c1af589e Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 29 Aug 2013 04:05:10 +0200 Subject: [PATCH 12/33] Update travis-web-log --- assets/scripts/vendor/log.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts/vendor/log.js b/assets/scripts/vendor/log.js index f50756a7..20bb2d7f 100644 --- a/assets/scripts/vendor/log.js +++ b/assets/scripts/vendor/log.js @@ -1 +1 @@ -minispade.register('log', "(function() {(function() {\n\n this.Log = function() {\n this.listeners = [];\n this.renderer = new Log.Renderer;\n this.children = new Log.Nodes(this);\n this.parts = {};\n this.folds = new Log.Folds;\n return this;\n };\n\n Log.extend = function(one, other) {\n var name;\n for (name in other) {\n one[name] = other[name];\n }\n return one;\n };\n\n Log.extend(Log, {\n DEBUG: true,\n SLICE: 500,\n TIMEOUT: 25,\n FOLD: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(options) {\n var listener, log, _i, _len, _ref;\n options || (options = {});\n log = new Log();\n if (options.limit) {\n log.listeners.push(log.limit = new Log.Limit(options.limit));\n }\n _ref = options.listeners || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n listener = _ref[_i];\n log.listeners.push(listener);\n }\n return log;\n }\n });\nminispade.require('log/nodes');\n\n Log.prototype = Log.extend(new Log.Node, {\n set: function(num, string) {\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n this.parts[num] = true;\n return Log.Part.create(this, num, string);\n }\n },\n insert: function(data, pos) {\n this.trigger('insert', data, pos);\n return this.renderer.insert(data, pos);\n },\n remove: function(node) {\n this.trigger('remove', node);\n return this.renderer.remove(node);\n },\n hide: function(node) {\n this.trigger('hide', node);\n return this.renderer.hide(node);\n },\n trigger: function() {\n var args, ix, listener, _i, _len, _ref, _results;\n args = [this].concat(Array.prototype.slice.apply(arguments));\n _ref = this.listeners;\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n listener = _ref[ix];\n _results.push(listener.notify.apply(listener, args));\n }\n return _results;\n }\n });\n\n Log.Listener = function() {};\n\n Log.extend(Log.Listener.prototype, {\n notify: function(log, event) {\n if (this[event]) {\n return this[event].apply(this, [log].concat(Array.prototype.slice.call(arguments, 2)));\n }\n }\n });\nminispade.require('log/folds');\nminispade.require('log/deansi');\nminispade.require('log/limit');\nminispade.require('log/renderer');\n\n}).call(this);\n\n})();\n//@ sourceURL=log");minispade.register('log.old/buffer', "(function() {(function() {\n\n Log.Buffer = function(log, options) {\n this.start = 0;\n this.log = log;\n this.parts = [];\n this.options = $.extend({\n interval: 100,\n timeout: 500\n }, options || {});\n this.schedule();\n return this;\n };\n\n $.extend(Log.Buffer.prototype, {\n set: function(num, string) {\n return this.parts[num] = {\n string: string,\n time: (new Date).getTime()\n };\n },\n flush: function() {\n var num, part, _i, _len, _ref;\n _ref = this.parts;\n for (num = _i = 0, _len = _ref.length; _i < _len; num = ++_i) {\n part = _ref[num];\n if (!this.parts.hasOwnProperty(num)) {\n continue;\n }\n if (!part) {\n break;\n }\n delete this.parts[num];\n this.log.set(num, part.string);\n }\n return this.schedule();\n },\n schedule: function() {\n var _this = this;\n return setTimeout((function() {\n return _this.flush();\n }), this.options.interval);\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/buffer");minispade.register('log.old/engine/chunks', "(function() {(function() {\n\n Log.Chunks = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Chunks.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Chunks.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Chunks.Part = function(engine, num, string) {\n var chunk, ix, line, type;\n this.engine = engine;\n this.num = num;\n this.chunks = (function() {\n var _i, _len, _ref, _results;\n _ref = string.split(/^/m);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n chunk = _ref[ix];\n line = chunk[chunk.length - 1].match(/\\r|\\n/);\n type = line ? 'Line' : 'Chunk';\n _results.push(new Log.Chunks[type](this, ix, chunk));\n }\n return _results;\n }).call(this);\n return this;\n };\n\n $.extend(Log.Chunks.Part.prototype, {\n insert: function() {\n var chunk, _i, _len, _ref, _results;\n _ref = this.chunks;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n chunk = _ref[_i];\n _results.push(chunk.insert());\n }\n return _results;\n },\n prev: function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.engine.parts[num -= 1];\n }\n return prev;\n },\n next: function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.engine.parts.length)) {\n next = this.engine.parts[num += 1];\n }\n return next;\n },\n trigger: function() {\n return this.engine.trigger.apply(this.engine, arguments);\n }\n });\n\n Log.Chunks.Chunk = function(part, num, string) {\n this.part = part;\n this.num = num;\n this.string = string;\n this.id = \"\" + (part != null ? part.num : void 0) + \"-\" + num;\n this.isFold = (string != null ? string.indexOf('fold') : void 0) !== -1;\n if (string) {\n this.nodes = this.parse();\n }\n return this;\n };\n\n $.extend(Log.Chunks.Chunk.prototype, {\n parse: function() {\n return [\n {\n type: 'span',\n id: \"\" + this.id + \"-0\",\n text: this.string\n }\n ];\n },\n insert: function() {\n var next, prev;\n if ((next = this.next()) && next.isLine) {\n return this.trigger('insert', this.nodes, {\n before: next.nodes[0].nodes[0].id\n });\n } else if (next) {\n return this.trigger('insert', this.nodes, {\n before: next.nodes[0].id\n });\n } else if ((prev = this.prev()) && !prev.isLine) {\n return this.trigger('insert', this.nodes, {\n after: prev.nodes[prev.nodes.length - 1].id\n });\n } else {\n return this.trigger('insert', [\n {\n type: 'paragraph',\n id: this.id,\n nodes: this.nodes\n }\n ], {\n before: void 0\n });\n }\n },\n remove: function() {\n var node, _i, _len, _ref, _results;\n _ref = this.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n this.trigger('remove', this.id);\n if (node.nodes) {\n _results.push((function() {\n var _j, _len1, _ref1, _results1;\n _ref1 = node.nodes;\n _results1 = [];\n for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {\n node = _ref1[_j];\n _results1.push(this.trigger('remove', node.id));\n }\n return _results1;\n }).call(this));\n } else {\n _results.push(void 0);\n }\n }\n return _results;\n },\n reinsert: function() {\n this.remove();\n return this.insert();\n },\n prevLine: function() {\n var prev;\n prev = this.prev();\n while (prev && !prev.isLine) {\n prev = prev.prev();\n }\n return prev;\n },\n nextLine: function() {\n var next;\n next = this.next();\n while (next && !next.isLine) {\n next = next.next();\n }\n return next;\n },\n prev: function() {\n var chunk, _ref;\n chunk = this.part.chunks[this.num - 1];\n return chunk || ((_ref = this.part.prev()) != null ? _ref.chunks.slice(-1)[0] : void 0);\n },\n next: function() {\n var chunk, _ref;\n chunk = this.part.chunks[this.num + 1];\n return chunk || ((_ref = this.part.next()) != null ? _ref.chunks[0] : void 0);\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Chunks.Line = function(part, num, string) {\n Log.Chunks.Chunk.call(this, part, num, string.slice(0, string.length - 1));\n this.isLine = true;\n return this;\n };\n\n Log.Chunks.Line.prototype = $.extend(new Log.Chunks.Chunk, {\n parse: function() {\n return [\n {\n type: 'paragraph',\n id: this.id,\n nodes: [\n {\n type: 'span',\n id: \"\" + this.id + \"-0\",\n text: this.string\n }\n ]\n }\n ];\n },\n insert: function() {\n var next, prev;\n if ((prev = this.prev()) && !prev.isLine) {\n this.trigger('insert', this.nodes[0].nodes, {\n after: prev.nodes[0].id\n });\n document.getElementById(this.nodes[0].nodes[0].id).parentNode.setAttribute('id', this.id);\n if (this.isLine && (next = this.next())) {\n return next.reinsert();\n }\n } else if (prev) {\n return this.trigger('insert', this.nodes, {\n after: prev.id\n });\n } else if (next = this.nextLine()) {\n return this.trigger('insert', this.nodes, {\n before: next.id\n });\n } else {\n return this.trigger('insert', this.nodes, {\n before: void 0\n });\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/chunks");minispade.register('log.old/engine/dom', "(function() {(function() {\n\n Log.Dom = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Dom.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Dom.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Dom.Part = function(engine, num, string) {\n this.engine = engine;\n this.num = num;\n this.string = string.replace(/\\r\\n/gm, '\\n');\n this.nodes = new Log.Dom.Nodes(this);\n return this;\n };\n\n $.extend(Log.Dom.Part.prototype, {\n SLICE: 500,\n insert: function() {\n var ix, lines, next, slices,\n _this = this;\n lines = this.string.split(/^/gm) || [];\n slices = ((function() {\n var _results;\n _results = [];\n while (lines.length > 0) {\n _results.push(lines.splice(0, this.SLICE));\n }\n return _results;\n }).call(this));\n ix = -1;\n next = function() {\n _this.insertSlice(slices.shift(), ix += 1);\n if (slices.length !== 0) {\n return setTimeout(next, 50);\n }\n };\n return next();\n },\n insertSlice: function(lines, start) {\n var ix, line, node, _i, _len, _ref, _ref1, _results;\n _ref = lines || [];\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n if ((_ref1 = this.engine.log.limit) != null ? _ref1.limited : void 0) {\n break;\n }\n node = Log.Dom.Node.create(this, start * this.SLICE + ix, line);\n _results.push(this.nodes.insert(node));\n }\n return _results;\n },\n remove: function() {\n return delete this.engine.parts[this.num];\n },\n trigger: function() {\n return this.engine.trigger.apply(this.engine, arguments);\n }\n });\n\n Log.Dom.Part.prototype.__defineGetter__('prev', function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.engine.parts[num -= 1];\n }\n return prev;\n });\n\n Log.Dom.Part.prototype.__defineGetter__('next', function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.engine.parts.length)) {\n next = this.engine.parts[num += 1];\n }\n return next;\n });\n\n Log.Dom.Nodes = function(part) {\n this.part = part;\n this.nodes = [];\n return this;\n };\n\n $.extend(Log.Dom.Nodes.prototype, {\n at: function(ix) {\n return this.nodes[ix];\n },\n insert: function(node) {\n this.nodes[node.num] = node;\n return node.insert();\n },\n remove: function(node) {\n this.nodes.splice(node.num, 1);\n if (this.nodes.length === 0) {\n return this.part.remove();\n }\n }\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('length', function() {\n return this.nodes.length;\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('first', function() {\n return this.nodes[0];\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('last', function() {\n return this.nodes[this.nodes.length - 1];\n });\n\n Log.Dom.Node = function() {};\n\n $.extend(Log.Dom.Node, {\n FOLDS_PATTERN: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(part, num, string) {\n var fold;\n if (fold = string.match(this.FOLDS_PATTERN)) {\n return new Log.Dom.Fold(part, num, fold[1], fold[2]);\n } else {\n return new Log.Dom.Paragraph(part, num, string);\n }\n },\n reinsert: function(nodes) {\n var node, _i, _j, _len, _len1;\n console.log(\"reinsert: \" + (nodes.map(function(node) {\n return node.id;\n }).join(', ')));\n for (_i = 0, _len = nodes.length; _i < _len; _i++) {\n node = nodes[_i];\n node.remove();\n }\n for (_j = 0, _len1 = nodes.length; _j < _len1; _j++) {\n node = nodes[_j];\n node.part.nodes.insert(node);\n }\n return console.log(document.firstChild.innerHTML.replace(/<\\/p>/gm, '

\\n') + '\\n');\n }\n });\n\n Log.Dom.Node.prototype.__defineGetter__('prev', function() {\n var num, prev, _ref;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.part.nodes.at(num -= 1);\n }\n return prev || ((_ref = this.part.prev) != null ? _ref.nodes.last : void 0);\n });\n\n Log.Dom.Node.prototype.__defineGetter__('next', function() {\n var next, num, _ref;\n num = this.num;\n while (!(next || num >= this.part.nodes.length)) {\n next = this.part.nodes.at(num += 1);\n }\n return next || ((_ref = this.part.next) != null ? _ref.nodes.first : void 0);\n });\n\n Log.Dom.Fold = function(part, num, event, name) {\n this.part = part;\n this.ends = true;\n this.fold = true;\n this.num = num;\n this.id = \"fold-\" + event + \"-\" + name;\n this.data = {\n type: 'fold',\n id: this.id,\n event: event,\n name: name\n };\n return this;\n };\n\n Log.Dom.Fold.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n return fail;\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Dom.Paragraph = function(part, num, string) {\n var span, _ref;\n this.part = part;\n this.num = num;\n this.ends = !!((_ref = string[string.length - 1]) != null ? _ref.match(/\\n/) : void 0);\n this.spans = new Log.Dom.Spans(this, string.replace(/\\n$/, ''));\n this.data = {\n type: 'paragraph',\n num: this.part.num,\n hidden: this.hidden,\n nodes: (function() {\n var _i, _len, _ref1, _results;\n _ref1 = this.spans.content;\n _results = [];\n for (_i = 0, _len = _ref1.length; _i < _len; _i++) {\n span = _ref1[_i];\n _results.push(span.data);\n }\n return _results;\n }).call(this)\n };\n return this;\n };\n\n Log.Dom.Paragraph.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n var next, prev, span, _i, _j, _len, _len1, _ref, _ref1, _ref2, _results;\n if ((prev = this.prev) && !prev.ends && !prev.fold) {\n if (Log.DEBUG) {\n console.log(\"P.1 - move \" + this.id + \"'s spans into prev\");\n }\n _ref = this.spans.content;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n prev.spans.append(span);\n }\n return this.part.nodes.remove(this);\n } else if ((next = this.next) && !this.ends && !next.fold) {\n if (Log.DEBUG) {\n console.log(\"P.2 - move \" + this.id + \"'s spans into next\");\n }\n _ref1 = this.spans.reverse();\n _results = [];\n for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {\n span = _ref1[_j];\n _results.push(next.prepend(span));\n }\n return _results;\n } else if ((prev != null ? prev.fold : void 0) && (prev != null ? (_ref2 = prev.element.getAttribute('class')) != null ? _ref2.match(' fold') : void 0 : void 0)) {\n if (Log.DEBUG) {\n console.log(\"P.3 - append \" + this.id + \" to fold \" + prev.id);\n }\n return this.element = this.trigger('insert', this.data, {\n after: prev.element.firstChild\n });\n } else if (prev) {\n if (Log.DEBUG) {\n console.log(\"P.4 - insert \" + this.id + \" after the parentNode of the last node of prev, id \" + prev.id);\n }\n return this.element = this.trigger('insert', this.data, {\n after: prev.element\n });\n } else if (next) {\n if (Log.DEBUG) {\n console.log(\"P.5 - insert \" + this.id + \" before the parentNode of the first node of next, id \" + next.id);\n }\n return this.element = this.trigger('insert', this.data, {\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"P.6 - insert \" + this.id + \" at the beginning of #log\");\n }\n return this.element = this.trigger('insert', this.data);\n }\n },\n remove: function() {\n var element, span, _i, _len, _ref;\n element = this.element;\n _ref = this.spans;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n this.trigger('remove', span.element);\n }\n if (!(element.childNodes.length > 1)) {\n this.trigger('remove', element);\n }\n return this.part.nodes.remove(this);\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('id', function() {\n return \"\" + this.part.num + \"-\" + this.num;\n });\n\n Log.Dom.Paragraph.prototype.__defineSetter__('element', function(element) {\n var child, span, _i, _len, _ref, _results;\n child = element.firstChild;\n _ref = this.spans.content;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n _results.push(span.element = child = child.nextSibling);\n }\n return _results;\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('element', function() {\n return this.spans.first.element.parentNode;\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('tail', function() {\n var next, parent, tail, _ref;\n parent = this.element.parentNode;\n next = this;\n tail = [];\n while ((next = next.next) && !next.fold && ((_ref = next.element) != null ? _ref.parentNode : void 0) === parent) {\n tail.push(next);\n }\n return tail;\n });\n\n Log.Dom.Spans = function(parent, string) {\n this.parent = parent;\n this.content = this.parse(parent, string);\n return this;\n };\n\n $.extend(Log.Dom.Spans.prototype, {\n parse: function(parent, string) {\n var ix, span, _i, _len, _ref, _results;\n _ref = Log.Deansi.apply(string);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n span = _ref[ix];\n _results.push(new Log.Dom.Span(parent, ix, span));\n }\n return _results;\n },\n at: function(ix) {\n return this.content[ix];\n },\n indexOf: function(span) {\n return this.content.indexOf(span);\n },\n append: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.push(span);\n return span.insert();\n },\n prepend: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.unshift(span);\n return span.insert();\n },\n reverse: function() {\n return this.content.reverse();\n },\n remove: function(span) {\n return this.content.splice(this.content.indexOf(span), 1);\n }\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('last', function() {\n return this.content[this.length - 1];\n });\n\n Log.Dom.Span = function(parent, num, data) {\n this.parent = parent;\n this.num = num;\n this.id = \"\" + parent.id + \"-\" + num;\n this.data = $.extend(data, {\n id: this.id\n });\n if (data.text.match(/\\r/)) {\n this.hidden = true;\n }\n this.data.text = data.text.replace(/^.*\\r/gm, '');\n if (this.hidden) {\n this.data[\"class\"] = ['hidden'];\n }\n return this;\n };\n\n $.extend(Log.Dom.Span.prototype, {\n insert: function() {\n var next, prev;\n if (prev = this.prev) {\n if (Log.DEBUG) {\n console.log(\"S.1 - insert \" + this.id + \" after prev \" + prev.id);\n }\n return this.insertAt({\n after: prev.element\n });\n } else if (next = this.next) {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" before next \" + next.id);\n }\n return this.insertAt({\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" into parent \" + this.parent.id);\n }\n return this.insertAt({\n into: this.parent.element\n });\n }\n },\n insertAt: function(pos) {\n var span, _i, _len, _ref, _results;\n this.element = this.trigger('insert', this.data, pos);\n if (this.hidden) {\n _ref = this.head;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n _results.push(span.hide());\n }\n return _results;\n } else {\n if (this.tail.some(function(span) {\n return span.hidden;\n })) {\n return this.hide();\n }\n }\n },\n hide: function() {\n if (!this.hidden) {\n this.trigger('hide', this.id);\n }\n return this.hidden = true;\n },\n siblings: function(direction) {\n var siblings, span, _ref;\n siblings = [];\n span = this;\n while ((span = span[direction]) && ((_ref = span.element) != null ? _ref.parentNode : void 0) === this.element.parentNode) {\n siblings.unshift(span);\n }\n return siblings;\n },\n trigger: function() {\n return this.parent.trigger.apply(this.parent, arguments);\n }\n });\n\n Log.Dom.Span.prototype.__defineGetter__('head', function() {\n return this.siblings('prev');\n });\n\n Log.Dom.Span.prototype.__defineGetter__('tail', function() {\n return this.siblings('next');\n });\n\n Log.Dom.Span.prototype.__defineGetter__('prev', function() {\n var span, _ref, _ref1;\n span = this.parent.spans.at(this.parent.spans.indexOf(this) - 1);\n return span || ((_ref = this.parent.prev) != null ? (_ref1 = _ref.spans) != null ? _ref1.last : void 0 : void 0);\n });\n\n Log.Dom.Span.prototype.__defineGetter__('next', function() {\n var span, _ref, _ref1;\n span = this.parent.spans.at(this.parent.spans.indexOf(this) + 1);\n return span || ((_ref = this.parent.next) != null ? (_ref1 = _ref.spans) != null ? _ref1.first : void 0 : void 0);\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/dom");minispade.register('log.old/engine/dom2', "(function() {(function() {\n\n Log.Dom = function(log) {\n this.log = log;\n this.parts = [];\n this.nodes = new Log.Dom.Nodes(this);\n return this;\n };\n\n $.extend(Log.Dom.prototype, {\n set: function(num, string) {\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n this.parts[num] = true;\n return this.insert(num, string);\n }\n },\n SLICE: 500,\n insert: function(num, string) {\n var ix, lines, next, slices,\n _this = this;\n lines = string.split(/^/gm) || [];\n slices = ((function() {\n var _results;\n _results = [];\n while (lines.length > 0) {\n _results.push(lines.splice(0, this.SLICE));\n }\n return _results;\n }).call(this));\n ix = -1;\n next = function() {\n _this.insertSlice(num, slices.shift(), ix += 1);\n if (slices.length !== 0) {\n return setTimeout(next, 50);\n }\n };\n return next();\n },\n insertSlice: function(num, lines, start) {\n var ix, line, _i, _len, _ref, _ref1, _results;\n _ref = lines || [];\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n if ((_ref1 = this.log.limit) != null ? _ref1.limited : void 0) {\n break;\n }\n _results.push(this.nodes.insert(Log.Dom.Node.create(this, [num, start * this.SLICE + ix], line)));\n }\n return _results;\n }\n });\n\n Log.Dom.Nodes = function(parent) {\n this.parent = parent;\n this.content = [];\n return this;\n };\n\n $.extend(Log.Dom.Nodes.prototype, {\n at: function(ix) {\n return this.content[ix];\n },\n insert: function(node) {\n return this.content[node.num] = node;\n },\n remove: function(node) {\n return this.content.splice(this.content.indexOf(node), 1);\n }\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('length', function() {\n return this.content.length;\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('last', function() {\n return this.content[this.content.length - 1];\n });\n\n Log.Dom.Node = function() {};\n\n $.extend(Log.Dom.Node, {\n FOLDS_PATTERN: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(parent, ids, string) {\n var fold;\n if (fold = string.match(this.FOLDS_PATTERN)) {\n return new Log.Dom.Fold(parent, ids, fold[1], fold[2]);\n } else {\n return new Log.Dom.Paragraph(parent, ids, string);\n }\n }\n });\n\n Log.Dom.Node.prototype.__defineGetter__('prev', function() {\n var num, prev, _ref;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.parent.nodes.at(num -= 1);\n }\n return prev || ((_ref = this.parent.prev) != null ? _ref.nodes.last : void 0);\n });\n\n Log.Dom.Node.prototype.__defineGetter__('next', function() {\n var next, num, _ref;\n num = this.num;\n while (!(next || num >= this.parent.nodes.length)) {\n next = this.parent.nodes.at(num += 1);\n }\n return next || ((_ref = this.parent.next) != null ? _ref.nodes.first : void 0);\n });\n\n Log.Dom.Paragraph = function(parent, ids, string) {\n var _ref;\n this.parent = parent;\n this.ids = ids;\n this.id = ids.join('-');\n this.ends = !!((_ref = string[string.length - 1]) != null ? _ref.match(/\\n/) : void 0);\n this.spans = new Log.Dom.Spans(this, ids, string.replace(/\\n$/, ''));\n return this;\n };\n\n Log.Dom.Paragraph.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n var next, prev,\n _this = this;\n if ((prev = this.prev) && !prev.ends && !prev.fold) {\n if (Log.DEBUG) {\n console.log(\"P.1 - move \" + this.id + \"'s spans into prev (\" + prev.id + \")\");\n }\n this.spans.content.slice().forEach(function(span) {\n return prev.spans.insert(span);\n });\n return this.remove();\n } else if ((next = this.next) && !this.ends && !next.fold) {\n if (Log.DEBUG) {\n console.log(\"P.2 - move next's (\" + next.id + \") spans into \" + this.id);\n }\n this.next.spans.content.slice().forEach(function(span) {\n return _this.spans.insert(span);\n });\n return next.remove();\n }\n },\n remove: function() {\n return this.parent.nodes.remove(this);\n }\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('id', function() {\n return \"\" + this.parent.num + \"-\" + this.num;\n });\n\n Log.Dom.Spans = function(parent, ids, string) {\n this.parent = parent;\n this.content = this.parse(parent, ids, string);\n return this;\n };\n\n $.extend(Log.Dom.Spans.prototype, {\n parse: function(parent, ids, string) {\n var ix, span, _i, _len, _ref, _results;\n _ref = Log.Deansi.apply(string);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n span = _ref[ix];\n _results.push(new Log.Dom.Span(parent, ids.concat([ix]), span));\n }\n return _results;\n },\n at: function(ix) {\n return this.content[ix];\n },\n indexOf: function(span) {\n return this.content.indexOf(span);\n },\n insert: function(span) {\n var head, ix, prev;\n console.log('insert', span.id);\n span.parent.spans.remove(span);\n span.parent = this.parent;\n head = this.content.filter(function(s) {\n return s.id < span.id;\n });\n if (prev = head[head.length - 1]) {\n ix = this.content.indexOf(prev) + 1;\n }\n return this.content.splice(ix || 0, 0, span);\n },\n prepend: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.unshift(span);\n return span.insert();\n },\n remove: function(span) {\n return this.content.splice(this.content.indexof(span), 1);\n }\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('last', function() {\n return this.content[this.content.length - 1];\n });\n\n Log.Dom.Span = function(parent, ids, data) {\n this.parent = parent;\n this.ids = ids;\n this.id = ids.join('-');\n this.data = $.extend(data, {\n id: this.id\n });\n if (data.text.match(/\\r/)) {\n this.hidden = true;\n }\n this.data.text = data.text.replace(/^.*\\r/gm, '');\n if (this.hidden) {\n this.data[\"class\"] = ['hidden'];\n }\n return this;\n };\n\n $.extend(Log.Dom.Span.prototype, {\n insert: function() {\n var next, prev;\n if (prev = this.prev) {\n if (Log.DEBUG) {\n console.log(\"S.1 - insert \" + this.id + \" after prev \" + prev.id);\n }\n return this.insertAt({\n after: prev.element\n });\n } else if (next = this.next) {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" before next \" + next.id);\n }\n return this.insertAt({\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" into parent \" + this.parent.id);\n }\n return this.insertAt({\n into: this.parent.element\n });\n }\n },\n insertAt: function(pos) {\n return this.element = this.trigger('insert', this.data, pos);\n }\n });\n\n Log.Dom.Span.prototype.__defineGetter__('prev', function() {\n var span, _ref, _ref1;\n console.log(this.num);\n span = this.parent.spans.at(this.num - 1);\n return span || ((_ref = this.parent.prev) != null ? (_ref1 = _ref.spans) != null ? _ref1.last : void 0 : void 0);\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/dom2");minispade.register('log.old/engine/live', "(function() {(function() {\n\n Log.Live = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Live.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Live.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Live.Part = function(log, num, string) {\n var ix, line;\n this.log = log;\n this.num = num;\n this.lines = (function() {\n var _i, _len, _ref, _results;\n _ref = string.split(/^/m);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n _results.push(new Log.Live.Line(this, ix, line));\n }\n return _results;\n }).call(this);\n return this;\n };\n\n $.extend(Log.Live.Part.prototype, {\n insert: function() {\n return new Log.Live.Context(this.log, this).insert();\n },\n head: function() {\n var head, line;\n head = [];\n line = this.lines[0];\n while ((line = line != null ? line.prev() : void 0) && !line.isNewline()) {\n head.unshift(line);\n }\n return head;\n },\n tail: function() {\n var line, tail;\n tail = [];\n line = this.lines[this.lines.length - 1];\n while (line = line != null ? line.next() : void 0) {\n tail.push(line);\n if (line != null ? line.isNewline() : void 0) {\n break;\n }\n }\n return tail;\n },\n prev: function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.log.parts[num -= 1];\n }\n return prev;\n },\n next: function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.log.parts.length)) {\n next = this.log.parts[num += 1];\n }\n return next;\n }\n });\n\n Log.Live.Line = function(part, num, string) {\n this.part = part;\n this.num = num;\n this.id = \"\" + part.num + \"-\" + num;\n this.string = string;\n return this;\n };\n\n $.extend(Log.Live.Line.prototype, {\n prev: function() {\n var line, _ref;\n line = this.part.lines[this.num - 1];\n return line || ((_ref = this.part.prev()) != null ? _ref.lines.slice(-1)[0] : void 0);\n },\n next: function() {\n var line, _ref;\n line = this.part.lines[this.num + 1];\n return line || ((_ref = this.part.next()) != null ? _ref.lines[0] : void 0);\n },\n isNewline: function() {\n return this.string[this.string.length - 1] === \"\\n\";\n },\n isFold: function() {\n return this.string.indexOf('fold') !== -1;\n },\n clone: function() {\n return new Log.Live.Line(this.part, this.num, this.string);\n }\n });\n\n Log.Live.Context = function(log, part) {\n this.log = log;\n this.part = part;\n this.head = part.head();\n this.tail = part.tail();\n this.lines = this.join(this.head.concat(part.lines).concat(this.tail));\n return this;\n };\n\n $.extend(Log.Live.Context.prototype, {\n insert: function() {\n var ids;\n ids = this.head.concat(this.tail).map(function(line) {\n return line.id;\n });\n if (ids.length !== 0) {\n this.log.trigger('remove', ids);\n }\n return this.log.trigger('insert', this.after(), this.nodes());\n },\n nodes: function() {\n var _this = this;\n return this.lines.map(function(line) {\n var fold, string;\n string = line.string;\n if (fold = _this.defold(string)) {\n return $.extend(fold, {\n id: line.id\n });\n } else {\n return {\n id: line.id,\n nodes: _this.deansi(string)\n };\n }\n });\n },\n join: function(all) {\n var line, lines;\n lines = [];\n while (line = all.pop()) {\n if (lines.length === 0 || line.isNewline()) {\n lines.unshift(line.clone());\n } else {\n lines[0].string = line.string + lines[0].string;\n }\n }\n return lines;\n },\n after: function() {\n var line, _ref;\n line = (_ref = this.part.lines[0]) != null ? _ref.prev() : void 0;\n while (line && !line.isNewline() && !line.isFold()) {\n line = line.prev();\n }\n return line != null ? line.id : void 0;\n },\n defold: function(string) {\n var matches;\n if (matches = string.match(/fold:(start|end):([\\w_\\-\\.]+)/)) {\n return {\n type: 'fold',\n event: matches[1],\n name: matches[2]\n };\n }\n },\n deansi: function(string) {\n return Log.Deansi.apply(string);\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/live");minispade.register('log.old/folds', "(function() {(function() {\n\n Log.Folds = function() {\n this.folds = {};\n return this;\n };\n\n Log.Folds.prototype = $.extend(new Log.Listener, {\n insert: function(log, data, pos) {\n var fold, _base, _name;\n if (data.type === 'fold') {\n fold = (_base = this.folds)[_name = data.name] || (_base[_name] = new Log.Folds.Fold);\n fold.receive(data);\n }\n return true;\n }\n });\n\n Log.Folds.Fold = function() {\n return this;\n };\n\n $.extend(Log.Folds.Fold.prototype, {\n receive: function(data) {\n this[data.event] = data.id;\n if (this.start && this.end && !this.active) {\n return this.activate();\n }\n },\n activate: function() {\n var node, _i, _len, _ref;\n console.log(\"F - activate \" + this.start);\n console.log(document.firstChild.innerHTML.replace(/

rcv \" + num + \" \" + (JSON.stringify(string)) + \"\");\n },\n insert: function(log, after, datas) {\n return this.log(\"ins \" + (datas.map(function(data) {\n return data.id;\n }).join(', ')) + \", after: \" + (after || '?') + \", \" + (JSON.stringify(datas)));\n },\n remove: function(log, id) {\n return this.log(\"rem \" + id);\n },\n log: function(line) {\n return $('#events').append(\"\" + line + \"\\n\");\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/instrument");minispade.register('log.old/limit', "(function() {(function() {\n\n Log.Limit = function(max_lines) {\n this.max_lines = max_lines || 1000;\n return this;\n };\n\n Log.Limit.prototype = $.extend(new Log.Listener, {\n count: 0,\n insert: function(log, line, pos) {\n if (line.type === 'paragraph' && !line.hidden) {\n return this.count += 1;\n }\n }\n });\n\n Log.Limit.prototype.__defineGetter__('limited', function() {\n return this.count >= this.max_lines;\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/limit");minispade.register('log.old/renderer/inner_html', "(function() {(function() {\n\n Log.InnerHtmlRenderer = function() {\n this.frag = document.createDocumentFragment();\n this.div = document.createElement('div');\n return this;\n };\n\n Log.InnerHtmlRenderer.prototype = $.extend(new Log.Listener, {\n remove: function(log, ids) {\n var id, node, _i, _len, _ref, _results;\n _results = [];\n for (_i = 0, _len = ids.length; _i < _len; _i++) {\n id = ids[_i];\n node = document.getElementById(id);\n if (node && !((_ref = node.getAttribute('class')) != null ? _ref.match(/fold/) : void 0)) {\n _results.push(node.parentNode.removeChild(node));\n } else {\n _results.push(void 0);\n }\n }\n return _results;\n },\n insert: function(log, after, nodes) {\n var html;\n log = document.getElementById('log');\n html = this.render(nodes);\n if (log.childNodes.length === 0) {\n return log.innerHTML = html;\n } else if (after) {\n after = document.getElementById(after);\n return this.insertAfter(this.fragmentFrom(html), after);\n } else {\n log = document.getElementById('log');\n return log.insertBefore(this.fragmentFrom(html), log.firstChild);\n }\n },\n render: function(nodes) {\n var node;\n return ((function() {\n var _i, _len, _results;\n _results = [];\n for (_i = 0, _len = nodes.length; _i < _len; _i++) {\n node = nodes[_i];\n _results.push(this.renderNode(node));\n }\n return _results;\n }).call(this)).join('');\n },\n renderNode: function(node) {\n var type;\n node.type || (node.type = 'paragraph');\n type = node.type[0].toUpperCase() + node.type.slice(1);\n return this[\"render\" + type](node) || '';\n },\n renderParagraph: function(node) {\n var html, style;\n if (node.hidden) {\n style = ' style=\"display:none\"';\n }\n html = \"

\";\n html += ((function() {\n var _i, _len, _ref, _results;\n _ref = node.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n _results.push(this.renderNode(node));\n }\n return _results;\n }).call(this)).join('');\n return html + '

';\n },\n renderFold: function(node) {\n if (!document.getElementById(node.id)) {\n return \"
\";\n }\n },\n renderSpan: function(node) {\n return \"\" + (this.clean(node.text)) + \"\";\n },\n renderText: function(node) {\n return this.clean(node.text);\n },\n clean: function(text) {\n return text.replace(/\\n/gm, '');\n },\n fragmentFrom: function(html) {\n var div, frag, node;\n frag = this.frag.cloneNode();\n div = this.div.cloneNode();\n div.innerHTML = html;\n while (node = div.firstChild) {\n frag.appendChild(node);\n }\n return frag;\n },\n insertAfter: function(node, after) {\n if (after.nextSibling) {\n return after.parentNode.insertBefore(node, after.nextSibling);\n } else {\n return after.parentNode.appendChild(node);\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/renderer/inner_html");minispade.register('log.old/renderer/jquery', "(function() {(function() {\n\n Log.JqueryRenderer = function() {};\n\n Log.JqueryRenderer.prototype = $.extend(new Log.Listener, {\n remove: function(log, ids) {\n var id, _i, _len, _results;\n _results = [];\n for (_i = 0, _len = ids.length; _i < _len; _i++) {\n id = ids[_i];\n _results.push($(\"#log #\" + id).remove());\n }\n return _results;\n },\n insert: function(log, after, datas) {\n var html,\n _this = this;\n html = datas.map(function(data) {\n return _this.render(data);\n });\n return after && $(\"#log #\" + after).after(html) || $('#log').prepend(html);\n },\n render: function(data) {\n var node, nodes, text;\n nodes = (function() {\n var _i, _len, _ref, _results;\n _ref = data.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n text = node.text.replace(/\\n/gm, '');\n if (node.type === 'span') {\n text = \"\" + text + \"\";\n }\n _results.push(\"

\" + text + \"

\");\n }\n return _results;\n }).call(this);\n return nodes.join(\"\\n\");\n },\n style: function(data) {\n return data.hidden && 'display: none;' || '';\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/renderer/jquery");minispade.register('log/deansi', "(function() {(function() {\n\n Log.Deansi = {\n CLEAR_ANSI: /(\\e|\\033)\\[K|(\\e|\\033)\\[K|(\\e|\\033)\\[\\d+G|(\\e|\\033)\\(B|(\\e|\\033)M|(\\e|\\033)\\[\\?25(l|h)/gm,\n apply: function(string) {\n var nodes,\n _this = this;\n if (!string) {\n return [];\n }\n string = string.replace(this.CLEAR_ANSI, '');\n nodes = ansiparse(string).map(function(part) {\n return _this.node(part);\n });\n if (nodes.length === 0) {\n nodes.push(this.node({\n text: ''\n }));\n }\n return nodes;\n },\n node: function(part) {\n var classes, node;\n node = {\n type: 'span',\n text: part.text\n };\n if (classes = this.classes(part)) {\n node[\"class\"] = classes.join(' ');\n }\n return node;\n },\n classes: function(part) {\n var result;\n result = [];\n result = result.concat(this.colors(part));\n if (result.length > 0) {\n return result;\n }\n },\n colors: function(part) {\n var colors;\n colors = [];\n if (part.foreground) {\n colors.push(part.foreground);\n }\n if (part.background) {\n colors.push(\"bg-\" + part.background);\n }\n if (part.bold) {\n colors.push('bold');\n }\n if (part.italic) {\n colors.push('italic');\n }\n return colors;\n },\n hidden: function(part) {\n if (part.text.match(/\\r/)) {\n part.text = part.text.replace(/^.*\\r/gm, '');\n return true;\n }\n }\n };\n\n}).call(this);\n\n})();\n//@ sourceURL=log/deansi");minispade.register('log/folds', "(function() {(function() {\n\n Log.Folds = function() {\n this.folds = {};\n return this;\n };\n\n Log.extend(Log.Folds.prototype, {\n add: function(data) {\n var fold, _base, _name;\n fold = (_base = this.folds)[_name = data.name] || (_base[_name] = new Log.Folds.Fold);\n fold.receive(data);\n return fold.active;\n }\n });\n\n Log.Folds.Fold = function() {\n return this;\n };\n\n Log.extend(Log.Folds.Fold.prototype, {\n receive: function(data) {\n this[data.event] = data.id;\n if (this.start && this.end && !this.active) {\n return this.activate();\n }\n },\n activate: function() {\n var node, _i, _len, _ref;\n if (Log.DEBUG) {\n console.log(\"F - activate \" + this.start);\n }\n _ref = this.nodes;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n this.fold.appendChild(node);\n }\n this.fold.setAttribute('class', this.classes());\n return this.active = true;\n },\n classes: function() {\n var classes;\n classes = this.fold.getAttribute('class').split(' ');\n classes.push('fold');\n if (this.fold.childNodes.length > 2) {\n classes.push('active');\n }\n return classes.join(' ');\n }\n });\n\n Object.defineProperty(Log.Folds.Fold.prototype, 'fold', {\n get: function() {\n return this._fold || (this._fold = document.getElementById(this.start));\n }\n });\n\n Object.defineProperty(Log.Folds.Fold.prototype, 'nodes', {\n get: function() {\n var node, nodes;\n node = this.fold;\n nodes = [];\n while ((node = node.nextSibling) && node.id !== this.end) {\n nodes.push(node);\n }\n return nodes;\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/folds");minispade.register('log/limit', "(function() {(function() {\n\n Log.Limit = function(max_lines) {\n this.max_lines = max_lines || 1000;\n return this;\n };\n\n Log.Limit.prototype = Log.extend(new Log.Listener, {\n count: 0,\n insert: function(log, node, pos) {\n if (node.type === 'paragraph' && !node.hidden) {\n return this.count += 1;\n }\n }\n });\n\n Object.defineProperty(Log.Limit.prototype, 'limited', {\n get: function() {\n return this.count >= this.max_lines;\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/limit");minispade.register('log/nodes', "(function() {(function() {\n var asteriskRRegexp, newLineAtTheEndRegexp, newLineRegexp, rRegexp;\n\n Log.Node = function(id, num) {\n this.id = id;\n this.num = num;\n this.key = Log.Node.key(this.id);\n this.children = new Log.Nodes(this);\n return this;\n };\n\n Log.extend(Log.Node, {\n key: function(id) {\n if (id) {\n return id.split('-').map(function(i) {\n return '000000'.concat(i).slice(-6);\n }).join('');\n }\n }\n });\n\n Log.extend(Log.Node.prototype, {\n addChild: function(node) {\n return this.children.add(node);\n },\n remove: function() {\n this.log.remove(this.element);\n return this.parent.children.remove(this);\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'log', {\n get: function() {\n var _ref;\n return this._log || (this._log = ((_ref = this.parent) != null ? _ref.log : void 0) || this.parent);\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'firstChild', {\n get: function() {\n return this.children.first;\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'lastChild', {\n get: function() {\n return this.children.last;\n }\n });\n\n Log.Nodes = function(parent) {\n if (parent) {\n this.parent = parent;\n }\n this.items = [];\n this.index = {};\n return this;\n };\n\n Log.extend(Log.Nodes.prototype, {\n add: function(item) {\n var ix, next, prev, _ref, _ref1;\n ix = this.position(item) || 0;\n this.items.splice(ix, 0, item);\n if (this.parent) {\n item.parent = this.parent;\n }\n prev = function(item) {\n while (item && !item.children.last) {\n item = item.prev;\n }\n return item != null ? item.children.last : void 0;\n };\n next = function(item) {\n while (item && !item.children.first) {\n item = item.next;\n }\n return item != null ? item.children.first : void 0;\n };\n if (item.prev = this.items[ix - 1] || prev((_ref = this.parent) != null ? _ref.prev : void 0)) {\n item.prev.next = item;\n }\n if (item.next = this.items[ix + 1] || next((_ref1 = this.parent) != null ? _ref1.next : void 0)) {\n item.next.prev = item;\n }\n return item;\n },\n remove: function(item) {\n this.items.splice(this.items.indexOf(item), 1);\n if (item.next) {\n item.next.prev = item.prev;\n }\n if (item.prev) {\n item.prev.next = item.next;\n }\n if (this.items.length === 0) {\n return this.parent.remove();\n }\n },\n position: function(item) {\n var ix, _i, _ref;\n for (ix = _i = _ref = this.items.length - 1; _i >= 0; ix = _i += -1) {\n if (this.items[ix].key < item.key) {\n return ix + 1;\n }\n }\n },\n indexOf: function() {\n return this.items.indexOf.apply(this.items, arguments);\n },\n slice: function() {\n return this.items.slice.apply(this.items, arguments);\n },\n each: function(func) {\n return this.items.slice().forEach(func);\n },\n map: function(func) {\n return this.items.map(func);\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'first', {\n get: function() {\n return this.items[0];\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'last', {\n get: function() {\n return this.items[this.length - 1];\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'length', {\n get: function() {\n return this.items.length;\n }\n });\n\n Log.Part = function(id, num, string) {\n Log.Node.apply(this, arguments);\n this.string = string || '';\n this.string = this.string.replace(/\\033\\[1000D/gm, '\\r');\n this.string = this.string.replace(/\\r+\\n/gm, '\\n');\n this.strings = this.string.split(/^/gm) || [];\n this.slices = ((function() {\n var _results;\n _results = [];\n while (this.strings.length > 0) {\n _results.push(this.strings.splice(0, Log.SLICE));\n }\n return _results;\n }).call(this));\n return this;\n };\n\n Log.extend(Log.Part, {\n create: function(log, num, string) {\n var part;\n part = new Log.Part(num.toString(), num, string);\n log.addChild(part);\n return part.process(0, -1);\n }\n });\n\n Log.Part.prototype = Log.extend(new Log.Node, {\n remove: function() {},\n process: function(slice, num) {\n var node, span, spans, string, _i, _j, _len, _len1, _ref, _ref1, _ref2,\n _this = this;\n _ref = this.slices[slice] || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n string = _ref[_i];\n if ((_ref1 = this.log.limit) != null ? _ref1.limited : void 0) {\n return;\n }\n spans = [];\n _ref2 = Log.Deansi.apply(string);\n for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {\n node = _ref2[_j];\n span = Log.Span.create(this, \"\" + this.id + \"-\" + (num += 1), num, node.text, node[\"class\"]);\n span.render();\n spans.push(span);\n }\n if (spans[0] && spans[0].line.cr) {\n spans[0].line.clear();\n }\n }\n if (!(slice >= this.slices.length - 1)) {\n return setTimeout((function() {\n return _this.process(slice + 1, num);\n }), Log.TIMEOUT);\n }\n }\n });\n\n asteriskRRegexp = new RegExp(\".*\\r\", 'gm');\n\n newLineAtTheEndRegexp = new RegExp(\"\\n$\");\n\n newLineRegexp = new RegExp(\"\\n\");\n\n rRegexp = new RegExp(\"\\r\");\n\n Log.Span = function(id, num, text, classes) {\n var fold, _ref;\n Log.Node.apply(this, arguments);\n if (fold = text.match(Log.FOLD)) {\n this.fold = true;\n this.event = fold[1];\n this.text = this.name = fold[2];\n } else {\n this.text = text;\n if (this.text.indexOf(\"\\r\") !== -1) {\n this.text = this.text.replace(asteriskRRegexp, '');\n }\n this.text = this.text.replace(newLineAtTheEndRegexp, '');\n this.nl = !!((_ref = text[text.length - 1]) != null ? _ref.match(newLineRegexp) : void 0);\n this.cr = !!text.match(rRegexp);\n this[\"class\"] = this.cr && ['clears'] || classes;\n }\n return this;\n };\n\n Log.extend(Log.Span, {\n create: function(parent, id, num, text, classes) {\n var span;\n span = new Log.Span(id, num, text, classes);\n parent.addChild(span);\n return span;\n },\n render: function(parent, id, num, text, classes) {\n var span;\n span = this.create(parent, id, num, text, classes);\n return span.render();\n }\n });\n\n Log.Span.prototype = Log.extend(new Log.Node, {\n render: function() {\n var tail;\n if (!this.fold && this.prev && !this.prev.fold && !this.prev.nl) {\n if (Log.DEBUG) {\n console.log(\"S.1 insert \" + this.id + \" after prev \" + this.prev.id);\n }\n this.log.insert(this.data, {\n after: this.prev.element\n });\n this.line = this.prev.line;\n } else if (!this.fold && this.next && !this.next.fold) {\n if (Log.DEBUG) {\n console.log(\"S.2 insert \" + this.id + \" before next \" + this.next.id);\n }\n this.log.insert(this.data, {\n before: this.next.element\n });\n this.line = this.next.line;\n } else {\n this.line = Log.Line.create(this.log, [this]);\n this.line.render();\n }\n if (this.nl && (tail = this.tail).length > 0) {\n return this.split(tail);\n }\n },\n remove: function() {\n Log.Node.prototype.remove.apply(this);\n if (this.line) {\n return this.line.remove(this);\n }\n },\n split: function(spans) {\n var line, span, _i, _len;\n if (Log.DEBUG) {\n console.log(\"S.3 split [\" + (spans.map(function(span) {\n return span.id;\n }).join(', ')) + \"]\");\n }\n for (_i = 0, _len = spans.length; _i < _len; _i++) {\n span = spans[_i];\n this.log.remove(span.element);\n }\n line = Log.Line.create(this.log, spans);\n line.render();\n if (line.cr) {\n return line.clear();\n }\n },\n clear: function() {\n if (this.prev && this.isSibling(this.prev) && this.isSequence(this.prev)) {\n this.prev.clear();\n return this.prev.remove();\n }\n },\n isSequence: function(other) {\n return this.parent.num - other.parent.num === this.log.children.indexOf(this.parent) - this.log.children.indexOf(other.parent);\n },\n isSibling: function(other) {\n var _ref, _ref1;\n return ((_ref = this.element) != null ? _ref.parentNode : void 0) === ((_ref1 = other.element) != null ? _ref1.parentNode : void 0);\n },\n siblings: function(type) {\n var siblings, span;\n siblings = [];\n while ((span = (span || this)[type]) && this.isSibling(span)) {\n siblings.push(span);\n }\n return siblings;\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'data', {\n get: function() {\n return {\n id: this.id,\n type: 'span',\n text: this.text,\n \"class\": this[\"class\"]\n };\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'line', {\n get: function() {\n return this._line;\n },\n set: function(line) {\n if (this.line) {\n this.line.remove(this);\n }\n this._line = line;\n return this.line.add(this);\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'element', {\n get: function() {\n return document.getElementById(this.id);\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'head', {\n get: function() {\n return this.siblings('prev').reverse();\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'tail', {\n get: function() {\n return this.siblings('next');\n }\n });\n\n Log.Line = function(log) {\n this.log = log;\n this.spans = [];\n return this;\n };\n\n Log.extend(Log.Line, {\n create: function(log, spans) {\n var line, span, _i, _len;\n if ((span = spans[0]) && span.fold) {\n line = new Log.Fold(log, span.event, span.name);\n } else {\n line = new Log.Line(log);\n }\n for (_i = 0, _len = spans.length; _i < _len; _i++) {\n span = spans[_i];\n span.line = line;\n }\n return line;\n }\n });\n\n Log.extend(Log.Line.prototype, {\n add: function(span) {\n var ix;\n if (span.cr) {\n this.cr = true;\n }\n if (this.spans.indexOf(span) > -1) {\n\n } else if ((ix = this.spans.indexOf(span.prev)) > -1) {\n return this.spans.splice(ix + 1, 0, span);\n } else if ((ix = this.spans.indexOf(span.next)) > -1) {\n return this.spans.splice(ix, 0, span);\n } else {\n return this.spans.push(span);\n }\n },\n remove: function(span) {\n var ix;\n if ((ix = this.spans.indexOf(span)) > -1) {\n return this.spans.splice(ix, 1);\n }\n },\n render: function() {\n var fold;\n if ((fold = this.prev) && fold.event === 'start' && fold.active) {\n if (Log.DEBUG) {\n console.log(\"L.0 insert \" + this.id + \" into fold \" + fold.id);\n }\n fold = this.log.folds.folds[fold.name].fold;\n return this.element = this.log.insert(this.data, {\n into: fold\n });\n } else if (this.prev) {\n if (Log.DEBUG) {\n console.log(\"L.1 insert \" + this.spans[0].id + \" after prev \" + this.prev.id);\n }\n return this.element = this.log.insert(this.data, {\n after: this.prev.element\n });\n } else if (this.next) {\n if (Log.DEBUG) {\n console.log(\"L.2 insert \" + this.spans[0].id + \" before next \" + this.next.id);\n }\n return this.element = this.log.insert(this.data, {\n before: this.next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"L.3 insert \" + this.spans[0].id + \" into #log\");\n }\n return this.element = this.log.insert(this.data);\n }\n },\n clear: function() {\n var cr, _i, _len, _ref, _results;\n _ref = this.crs;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n cr = _ref[_i];\n _results.push(cr.clear());\n }\n return _results;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'id', {\n get: function() {\n var _ref;\n return (_ref = this.spans[0]) != null ? _ref.id : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'data', {\n get: function() {\n return {\n type: 'paragraph',\n nodes: this.nodes\n };\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'nodes', {\n get: function() {\n return this.spans.map(function(span) {\n return span.data;\n });\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'prev', {\n get: function() {\n var _ref;\n return (_ref = this.spans[0].prev) != null ? _ref.line : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'next', {\n get: function() {\n var _ref;\n return (_ref = this.spans[this.spans.length - 1].next) != null ? _ref.line : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'crs', {\n get: function() {\n return this.spans.filter(function(span) {\n return span.cr;\n });\n }\n });\n\n Log.Fold = function(log, event, name) {\n Log.Line.apply(this, arguments);\n this.fold = true;\n this.event = event;\n this.name = name;\n return this;\n };\n\n Log.Fold.prototype = Log.extend(new Log.Line, {\n render: function() {\n var element, _ref;\n if (this.prev) {\n if (Log.DEBUG) {\n console.log(\"F.1 insert \" + this.id + \" after prev \" + this.prev.id);\n }\n element = this.prev.element || this.prev.element.parentNode;\n this.element = this.log.insert(this.data, {\n after: element\n });\n } else if (this.next) {\n if (Log.DEBUG) {\n console.log(\"F.2 insert \" + this.id + \" before next \" + this.next.id);\n }\n element = this.next.element || this.next.element.parentNode;\n this.element = this.log.insert(this.data, {\n before: element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"F.3 insert \" + this.id);\n }\n this.element = this.log.insert(this.data);\n }\n if (this.span.next && ((_ref = this.span.prev) != null ? _ref.isSibling(this.span.next) : void 0)) {\n this.span.prev.split([this.span.next].concat(this.span.next.tail));\n }\n return this.active = this.log.folds.add(this.data);\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'id', {\n get: function() {\n return \"fold-\" + this.event + \"-\" + this.name;\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'span', {\n get: function() {\n return this.spans[0];\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'data', {\n get: function() {\n return {\n type: 'fold',\n id: this.id,\n event: this.event,\n name: this.name\n };\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/nodes");minispade.register('log/renderer', "(function() {(function() {\n\n Log.Renderer = function() {\n this.frag = document.createDocumentFragment();\n this.para = this.createParagraph();\n this.span = this.createSpan();\n this.text = document.createTextNode('');\n this.fold = this.createFold();\n return this;\n };\n\n Log.extend(Log.Renderer.prototype, {\n insert: function(data, pos) {\n var after, before, into, node;\n node = this.render(data);\n if (into = pos != null ? pos.into : void 0) {\n if (typeof into === 'String') {\n into = document.getElementById(pos != null ? pos.into : void 0);\n }\n this.appendTo(node, into);\n } else if (after = pos != null ? pos.after : void 0) {\n if (typeof after === 'String') {\n after = document.getElementById(pos);\n }\n this.insertAfter(node, after);\n } else if (before = pos != null ? pos.before : void 0) {\n if (typeof before === 'String') {\n before = document.getElementById(pos != null ? pos.before : void 0);\n }\n this.insertBefore(node, before);\n } else {\n this.insertBefore(node);\n }\n return node;\n },\n hide: function(node) {\n node.setAttribute('class', this.addClass(node.getAttribute('class'), 'hidden'));\n return node;\n },\n remove: function(node) {\n if (node) {\n node.parentNode.removeChild(node);\n }\n return node;\n },\n render: function(data) {\n var frag, node, type, _i, _len;\n if (data instanceof Array) {\n frag = this.frag.cloneNode(true);\n for (_i = 0, _len = data.length; _i < _len; _i++) {\n node = data[_i];\n node = this.render(node);\n if (node) {\n frag.appendChild(node);\n }\n }\n return frag;\n } else {\n data.type || (data.type = 'paragraph');\n type = data.type[0].toUpperCase() + data.type.slice(1);\n return this[\"render\" + type](data);\n }\n },\n renderParagraph: function(data) {\n var node, para, type, _i, _len, _ref;\n para = this.para.cloneNode(true);\n if (data.id) {\n para.setAttribute('id', data.id);\n }\n if (data.hidden) {\n para.setAttribute('style', 'display: none;');\n }\n _ref = data.nodes || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n type = node.type[0].toUpperCase() + node.type.slice(1);\n node = this[\"render\" + type](node);\n para.appendChild(node);\n }\n return para;\n },\n renderFold: function(data) {\n var fold;\n fold = this.fold.cloneNode(true);\n fold.setAttribute('id', data.id || (\"fold-\" + data.event + \"-\" + data.name));\n fold.setAttribute('class', \"fold-\" + data.event);\n if (data.event === 'start') {\n fold.lastChild.lastChild.nodeValue = data.name;\n } else {\n fold.removeChild(fold.lastChild);\n }\n return fold;\n },\n renderSpan: function(data) {\n var span;\n span = this.span.cloneNode(true);\n if (data.id) {\n span.setAttribute('id', data.id);\n }\n if (data[\"class\"]) {\n span.setAttribute('class', data[\"class\"]);\n }\n span.lastChild.nodeValue = data.text;\n return span;\n },\n renderText: function(data) {\n var text;\n text = this.text.cloneNode(true);\n text.nodeValue = data.text;\n return text;\n },\n createParagraph: function() {\n var para;\n para = document.createElement('p');\n para.appendChild(document.createElement('a'));\n return para;\n },\n createFold: function() {\n var fold;\n fold = document.createElement('div');\n fold.appendChild(this.createSpan());\n fold.lastChild.setAttribute('class', 'fold-name');\n return fold;\n },\n createSpan: function() {\n var span;\n span = document.createElement('span');\n span.appendChild(document.createTextNode(''));\n return span;\n },\n insertBefore: function(node, other) {\n var log;\n if (other) {\n return other.parentNode.insertBefore(node, other);\n } else {\n log = document.getElementById('log');\n return log.insertBefore(node, log.firstChild);\n }\n },\n insertAfter: function(node, other) {\n if (other.nextSibling) {\n return this.insertBefore(node, other.nextSibling);\n } else {\n return this.appendTo(node, other.parentNode);\n }\n },\n appendTo: function(node, other) {\n return other.appendChild(node);\n },\n addClass: function(classes, string) {\n if (classes != null ? classes.indexOf(string) : void 0) {\n return;\n }\n if (classes) {\n return \"\" + classes + \" \" + string;\n } else {\n return string;\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/renderer"); \ No newline at end of file +minispade.register('log', "(function() {(function() {\n\n this.Log = function() {\n this.listeners = [];\n this.renderer = new Log.Renderer;\n this.children = new Log.Nodes(this);\n this.parts = {};\n this.folds = new Log.Folds;\n return this;\n };\n\n Log.extend = function(one, other) {\n var name;\n for (name in other) {\n one[name] = other[name];\n }\n return one;\n };\n\n Log.extend(Log, {\n DEBUG: true,\n SLICE: 500,\n TIMEOUT: 25,\n FOLD: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(options) {\n var listener, log, _i, _len, _ref;\n options || (options = {});\n log = new Log();\n if (options.limit) {\n log.listeners.push(log.limit = new Log.Limit(options.limit));\n }\n _ref = options.listeners || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n listener = _ref[_i];\n log.listeners.push(listener);\n }\n return log;\n }\n });\nminispade.require('log/nodes');\n\n Log.prototype = Log.extend(new Log.Node, {\n set: function(num, string) {\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n this.parts[num] = true;\n return Log.Part.create(this, num, string);\n }\n },\n insert: function(data, pos) {\n this.trigger('insert', data, pos);\n return this.renderer.insert(data, pos);\n },\n remove: function(node) {\n this.trigger('remove', node);\n return this.renderer.remove(node);\n },\n hide: function(node) {\n this.trigger('hide', node);\n return this.renderer.hide(node);\n },\n trigger: function() {\n var args, ix, listener, _i, _len, _ref, _results;\n args = [this].concat(Array.prototype.slice.apply(arguments));\n _ref = this.listeners;\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n listener = _ref[ix];\n _results.push(listener.notify.apply(listener, args));\n }\n return _results;\n }\n });\n\n Log.Listener = function() {};\n\n Log.extend(Log.Listener.prototype, {\n notify: function(log, event) {\n if (this[event]) {\n return this[event].apply(this, [log].concat(Array.prototype.slice.call(arguments, 2)));\n }\n }\n });\nminispade.require('log/folds');\nminispade.require('log/deansi');\nminispade.require('log/limit');\nminispade.require('log/renderer');\n\n}).call(this);\n\n})();\n//@ sourceURL=log");minispade.register('log.old/buffer', "(function() {(function() {\n\n Log.Buffer = function(log, options) {\n this.start = 0;\n this.log = log;\n this.parts = [];\n this.options = $.extend({\n interval: 100,\n timeout: 500\n }, options || {});\n this.schedule();\n return this;\n };\n\n $.extend(Log.Buffer.prototype, {\n set: function(num, string) {\n return this.parts[num] = {\n string: string,\n time: (new Date).getTime()\n };\n },\n flush: function() {\n var num, part, _i, _len, _ref;\n _ref = this.parts;\n for (num = _i = 0, _len = _ref.length; _i < _len; num = ++_i) {\n part = _ref[num];\n if (!this.parts.hasOwnProperty(num)) {\n continue;\n }\n if (!part) {\n break;\n }\n delete this.parts[num];\n this.log.set(num, part.string);\n }\n return this.schedule();\n },\n schedule: function() {\n var _this = this;\n return setTimeout((function() {\n return _this.flush();\n }), this.options.interval);\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/buffer");minispade.register('log.old/engine/chunks', "(function() {(function() {\n\n Log.Chunks = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Chunks.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Chunks.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Chunks.Part = function(engine, num, string) {\n var chunk, ix, line, type;\n this.engine = engine;\n this.num = num;\n this.chunks = (function() {\n var _i, _len, _ref, _results;\n _ref = string.split(/^/m);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n chunk = _ref[ix];\n line = chunk[chunk.length - 1].match(/\\r|\\n/);\n type = line ? 'Line' : 'Chunk';\n _results.push(new Log.Chunks[type](this, ix, chunk));\n }\n return _results;\n }).call(this);\n return this;\n };\n\n $.extend(Log.Chunks.Part.prototype, {\n insert: function() {\n var chunk, _i, _len, _ref, _results;\n _ref = this.chunks;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n chunk = _ref[_i];\n _results.push(chunk.insert());\n }\n return _results;\n },\n prev: function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.engine.parts[num -= 1];\n }\n return prev;\n },\n next: function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.engine.parts.length)) {\n next = this.engine.parts[num += 1];\n }\n return next;\n },\n trigger: function() {\n return this.engine.trigger.apply(this.engine, arguments);\n }\n });\n\n Log.Chunks.Chunk = function(part, num, string) {\n this.part = part;\n this.num = num;\n this.string = string;\n this.id = \"\" + (part != null ? part.num : void 0) + \"-\" + num;\n this.isFold = (string != null ? string.indexOf('fold') : void 0) !== -1;\n if (string) {\n this.nodes = this.parse();\n }\n return this;\n };\n\n $.extend(Log.Chunks.Chunk.prototype, {\n parse: function() {\n return [\n {\n type: 'span',\n id: \"\" + this.id + \"-0\",\n text: this.string\n }\n ];\n },\n insert: function() {\n var next, prev;\n if ((next = this.next()) && next.isLine) {\n return this.trigger('insert', this.nodes, {\n before: next.nodes[0].nodes[0].id\n });\n } else if (next) {\n return this.trigger('insert', this.nodes, {\n before: next.nodes[0].id\n });\n } else if ((prev = this.prev()) && !prev.isLine) {\n return this.trigger('insert', this.nodes, {\n after: prev.nodes[prev.nodes.length - 1].id\n });\n } else {\n return this.trigger('insert', [\n {\n type: 'paragraph',\n id: this.id,\n nodes: this.nodes\n }\n ], {\n before: void 0\n });\n }\n },\n remove: function() {\n var node, _i, _len, _ref, _results;\n _ref = this.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n this.trigger('remove', this.id);\n if (node.nodes) {\n _results.push((function() {\n var _j, _len1, _ref1, _results1;\n _ref1 = node.nodes;\n _results1 = [];\n for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {\n node = _ref1[_j];\n _results1.push(this.trigger('remove', node.id));\n }\n return _results1;\n }).call(this));\n } else {\n _results.push(void 0);\n }\n }\n return _results;\n },\n reinsert: function() {\n this.remove();\n return this.insert();\n },\n prevLine: function() {\n var prev;\n prev = this.prev();\n while (prev && !prev.isLine) {\n prev = prev.prev();\n }\n return prev;\n },\n nextLine: function() {\n var next;\n next = this.next();\n while (next && !next.isLine) {\n next = next.next();\n }\n return next;\n },\n prev: function() {\n var chunk, _ref;\n chunk = this.part.chunks[this.num - 1];\n return chunk || ((_ref = this.part.prev()) != null ? _ref.chunks.slice(-1)[0] : void 0);\n },\n next: function() {\n var chunk, _ref;\n chunk = this.part.chunks[this.num + 1];\n return chunk || ((_ref = this.part.next()) != null ? _ref.chunks[0] : void 0);\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Chunks.Line = function(part, num, string) {\n Log.Chunks.Chunk.call(this, part, num, string.slice(0, string.length - 1));\n this.isLine = true;\n return this;\n };\n\n Log.Chunks.Line.prototype = $.extend(new Log.Chunks.Chunk, {\n parse: function() {\n return [\n {\n type: 'paragraph',\n id: this.id,\n nodes: [\n {\n type: 'span',\n id: \"\" + this.id + \"-0\",\n text: this.string\n }\n ]\n }\n ];\n },\n insert: function() {\n var next, prev;\n if ((prev = this.prev()) && !prev.isLine) {\n this.trigger('insert', this.nodes[0].nodes, {\n after: prev.nodes[0].id\n });\n document.getElementById(this.nodes[0].nodes[0].id).parentNode.setAttribute('id', this.id);\n if (this.isLine && (next = this.next())) {\n return next.reinsert();\n }\n } else if (prev) {\n return this.trigger('insert', this.nodes, {\n after: prev.id\n });\n } else if (next = this.nextLine()) {\n return this.trigger('insert', this.nodes, {\n before: next.id\n });\n } else {\n return this.trigger('insert', this.nodes, {\n before: void 0\n });\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/chunks");minispade.register('log.old/engine/dom', "(function() {(function() {\n\n Log.Dom = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Dom.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Dom.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Dom.Part = function(engine, num, string) {\n this.engine = engine;\n this.num = num;\n this.string = string.replace(/\\r\\n/gm, '\\n');\n this.nodes = new Log.Dom.Nodes(this);\n return this;\n };\n\n $.extend(Log.Dom.Part.prototype, {\n SLICE: 500,\n insert: function() {\n var ix, lines, next, slices,\n _this = this;\n lines = this.string.split(/^/gm) || [];\n slices = ((function() {\n var _results;\n _results = [];\n while (lines.length > 0) {\n _results.push(lines.splice(0, this.SLICE));\n }\n return _results;\n }).call(this));\n ix = -1;\n next = function() {\n _this.insertSlice(slices.shift(), ix += 1);\n if (slices.length !== 0) {\n return setTimeout(next, 50);\n }\n };\n return next();\n },\n insertSlice: function(lines, start) {\n var ix, line, node, _i, _len, _ref, _ref1, _results;\n _ref = lines || [];\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n if ((_ref1 = this.engine.log.limit) != null ? _ref1.limited : void 0) {\n break;\n }\n node = Log.Dom.Node.create(this, start * this.SLICE + ix, line);\n _results.push(this.nodes.insert(node));\n }\n return _results;\n },\n remove: function() {\n return delete this.engine.parts[this.num];\n },\n trigger: function() {\n return this.engine.trigger.apply(this.engine, arguments);\n }\n });\n\n Log.Dom.Part.prototype.__defineGetter__('prev', function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.engine.parts[num -= 1];\n }\n return prev;\n });\n\n Log.Dom.Part.prototype.__defineGetter__('next', function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.engine.parts.length)) {\n next = this.engine.parts[num += 1];\n }\n return next;\n });\n\n Log.Dom.Nodes = function(part) {\n this.part = part;\n this.nodes = [];\n return this;\n };\n\n $.extend(Log.Dom.Nodes.prototype, {\n at: function(ix) {\n return this.nodes[ix];\n },\n insert: function(node) {\n this.nodes[node.num] = node;\n return node.insert();\n },\n remove: function(node) {\n this.nodes.splice(node.num, 1);\n if (this.nodes.length === 0) {\n return this.part.remove();\n }\n }\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('length', function() {\n return this.nodes.length;\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('first', function() {\n return this.nodes[0];\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('last', function() {\n return this.nodes[this.nodes.length - 1];\n });\n\n Log.Dom.Node = function() {};\n\n $.extend(Log.Dom.Node, {\n FOLDS_PATTERN: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(part, num, string) {\n var fold;\n if (fold = string.match(this.FOLDS_PATTERN)) {\n return new Log.Dom.Fold(part, num, fold[1], fold[2]);\n } else {\n return new Log.Dom.Paragraph(part, num, string);\n }\n },\n reinsert: function(nodes) {\n var node, _i, _j, _len, _len1;\n console.log(\"reinsert: \" + (nodes.map(function(node) {\n return node.id;\n }).join(', ')));\n for (_i = 0, _len = nodes.length; _i < _len; _i++) {\n node = nodes[_i];\n node.remove();\n }\n for (_j = 0, _len1 = nodes.length; _j < _len1; _j++) {\n node = nodes[_j];\n node.part.nodes.insert(node);\n }\n return console.log(document.firstChild.innerHTML.replace(/<\\/p>/gm, '

\\n') + '\\n');\n }\n });\n\n Log.Dom.Node.prototype.__defineGetter__('prev', function() {\n var num, prev, _ref;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.part.nodes.at(num -= 1);\n }\n return prev || ((_ref = this.part.prev) != null ? _ref.nodes.last : void 0);\n });\n\n Log.Dom.Node.prototype.__defineGetter__('next', function() {\n var next, num, _ref;\n num = this.num;\n while (!(next || num >= this.part.nodes.length)) {\n next = this.part.nodes.at(num += 1);\n }\n return next || ((_ref = this.part.next) != null ? _ref.nodes.first : void 0);\n });\n\n Log.Dom.Fold = function(part, num, event, name) {\n this.part = part;\n this.ends = true;\n this.fold = true;\n this.num = num;\n this.id = \"fold-\" + event + \"-\" + name;\n this.data = {\n type: 'fold',\n id: this.id,\n event: event,\n name: name\n };\n return this;\n };\n\n Log.Dom.Fold.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n return fail;\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Dom.Paragraph = function(part, num, string) {\n var span, _ref;\n this.part = part;\n this.num = num;\n this.ends = !!((_ref = string[string.length - 1]) != null ? _ref.match(/\\n/) : void 0);\n this.spans = new Log.Dom.Spans(this, string.replace(/\\n$/, ''));\n this.data = {\n type: 'paragraph',\n num: this.part.num,\n hidden: this.hidden,\n nodes: (function() {\n var _i, _len, _ref1, _results;\n _ref1 = this.spans.content;\n _results = [];\n for (_i = 0, _len = _ref1.length; _i < _len; _i++) {\n span = _ref1[_i];\n _results.push(span.data);\n }\n return _results;\n }).call(this)\n };\n return this;\n };\n\n Log.Dom.Paragraph.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n var next, prev, span, _i, _j, _len, _len1, _ref, _ref1, _ref2, _results;\n if ((prev = this.prev) && !prev.ends && !prev.fold) {\n if (Log.DEBUG) {\n console.log(\"P.1 - move \" + this.id + \"'s spans into prev\");\n }\n _ref = this.spans.content;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n prev.spans.append(span);\n }\n return this.part.nodes.remove(this);\n } else if ((next = this.next) && !this.ends && !next.fold) {\n if (Log.DEBUG) {\n console.log(\"P.2 - move \" + this.id + \"'s spans into next\");\n }\n _ref1 = this.spans.reverse();\n _results = [];\n for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {\n span = _ref1[_j];\n _results.push(next.prepend(span));\n }\n return _results;\n } else if ((prev != null ? prev.fold : void 0) && (prev != null ? (_ref2 = prev.element.getAttribute('class')) != null ? _ref2.match(' fold') : void 0 : void 0)) {\n if (Log.DEBUG) {\n console.log(\"P.3 - append \" + this.id + \" to fold \" + prev.id);\n }\n return this.element = this.trigger('insert', this.data, {\n after: prev.element.firstChild\n });\n } else if (prev) {\n if (Log.DEBUG) {\n console.log(\"P.4 - insert \" + this.id + \" after the parentNode of the last node of prev, id \" + prev.id);\n }\n return this.element = this.trigger('insert', this.data, {\n after: prev.element\n });\n } else if (next) {\n if (Log.DEBUG) {\n console.log(\"P.5 - insert \" + this.id + \" before the parentNode of the first node of next, id \" + next.id);\n }\n return this.element = this.trigger('insert', this.data, {\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"P.6 - insert \" + this.id + \" at the beginning of #log\");\n }\n return this.element = this.trigger('insert', this.data);\n }\n },\n remove: function() {\n var element, span, _i, _len, _ref;\n element = this.element;\n _ref = this.spans;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n this.trigger('remove', span.element);\n }\n if (!(element.childNodes.length > 1)) {\n this.trigger('remove', element);\n }\n return this.part.nodes.remove(this);\n },\n trigger: function() {\n return this.part.trigger.apply(this.part, arguments);\n }\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('id', function() {\n return \"\" + this.part.num + \"-\" + this.num;\n });\n\n Log.Dom.Paragraph.prototype.__defineSetter__('element', function(element) {\n var child, span, _i, _len, _ref, _results;\n child = element.firstChild;\n _ref = this.spans.content;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n _results.push(span.element = child = child.nextSibling);\n }\n return _results;\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('element', function() {\n return this.spans.first.element.parentNode;\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('tail', function() {\n var next, parent, tail, _ref;\n parent = this.element.parentNode;\n next = this;\n tail = [];\n while ((next = next.next) && !next.fold && ((_ref = next.element) != null ? _ref.parentNode : void 0) === parent) {\n tail.push(next);\n }\n return tail;\n });\n\n Log.Dom.Spans = function(parent, string) {\n this.parent = parent;\n this.content = this.parse(parent, string);\n return this;\n };\n\n $.extend(Log.Dom.Spans.prototype, {\n parse: function(parent, string) {\n var ix, span, _i, _len, _ref, _results;\n _ref = Log.Deansi.apply(string);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n span = _ref[ix];\n _results.push(new Log.Dom.Span(parent, ix, span));\n }\n return _results;\n },\n at: function(ix) {\n return this.content[ix];\n },\n indexOf: function(span) {\n return this.content.indexOf(span);\n },\n append: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.push(span);\n return span.insert();\n },\n prepend: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.unshift(span);\n return span.insert();\n },\n reverse: function() {\n return this.content.reverse();\n },\n remove: function(span) {\n return this.content.splice(this.content.indexOf(span), 1);\n }\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('last', function() {\n return this.content[this.length - 1];\n });\n\n Log.Dom.Span = function(parent, num, data) {\n this.parent = parent;\n this.num = num;\n this.id = \"\" + parent.id + \"-\" + num;\n this.data = $.extend(data, {\n id: this.id\n });\n if (data.text.match(/\\r/)) {\n this.hidden = true;\n }\n this.data.text = data.text.replace(/^.*\\r/gm, '');\n if (this.hidden) {\n this.data[\"class\"] = ['hidden'];\n }\n return this;\n };\n\n $.extend(Log.Dom.Span.prototype, {\n insert: function() {\n var next, prev;\n if (prev = this.prev) {\n if (Log.DEBUG) {\n console.log(\"S.1 - insert \" + this.id + \" after prev \" + prev.id);\n }\n return this.insertAt({\n after: prev.element\n });\n } else if (next = this.next) {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" before next \" + next.id);\n }\n return this.insertAt({\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" into parent \" + this.parent.id);\n }\n return this.insertAt({\n into: this.parent.element\n });\n }\n },\n insertAt: function(pos) {\n var span, _i, _len, _ref, _results;\n this.element = this.trigger('insert', this.data, pos);\n if (this.hidden) {\n _ref = this.head;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n span = _ref[_i];\n _results.push(span.hide());\n }\n return _results;\n } else {\n if (this.tail.some(function(span) {\n return span.hidden;\n })) {\n return this.hide();\n }\n }\n },\n hide: function() {\n if (!this.hidden) {\n this.trigger('hide', this.id);\n }\n return this.hidden = true;\n },\n siblings: function(direction) {\n var siblings, span, _ref;\n siblings = [];\n span = this;\n while ((span = span[direction]) && ((_ref = span.element) != null ? _ref.parentNode : void 0) === this.element.parentNode) {\n siblings.unshift(span);\n }\n return siblings;\n },\n trigger: function() {\n return this.parent.trigger.apply(this.parent, arguments);\n }\n });\n\n Log.Dom.Span.prototype.__defineGetter__('head', function() {\n return this.siblings('prev');\n });\n\n Log.Dom.Span.prototype.__defineGetter__('tail', function() {\n return this.siblings('next');\n });\n\n Log.Dom.Span.prototype.__defineGetter__('prev', function() {\n var span, _ref, _ref1;\n span = this.parent.spans.at(this.parent.spans.indexOf(this) - 1);\n return span || ((_ref = this.parent.prev) != null ? (_ref1 = _ref.spans) != null ? _ref1.last : void 0 : void 0);\n });\n\n Log.Dom.Span.prototype.__defineGetter__('next', function() {\n var span, _ref, _ref1;\n span = this.parent.spans.at(this.parent.spans.indexOf(this) + 1);\n return span || ((_ref = this.parent.next) != null ? (_ref1 = _ref.spans) != null ? _ref1.first : void 0 : void 0);\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/dom");minispade.register('log.old/engine/dom2', "(function() {(function() {\n\n Log.Dom = function(log) {\n this.log = log;\n this.parts = [];\n this.nodes = new Log.Dom.Nodes(this);\n return this;\n };\n\n $.extend(Log.Dom.prototype, {\n set: function(num, string) {\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n this.parts[num] = true;\n return this.insert(num, string);\n }\n },\n SLICE: 500,\n insert: function(num, string) {\n var ix, lines, next, slices,\n _this = this;\n lines = string.split(/^/gm) || [];\n slices = ((function() {\n var _results;\n _results = [];\n while (lines.length > 0) {\n _results.push(lines.splice(0, this.SLICE));\n }\n return _results;\n }).call(this));\n ix = -1;\n next = function() {\n _this.insertSlice(num, slices.shift(), ix += 1);\n if (slices.length !== 0) {\n return setTimeout(next, 50);\n }\n };\n return next();\n },\n insertSlice: function(num, lines, start) {\n var ix, line, _i, _len, _ref, _ref1, _results;\n _ref = lines || [];\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n if ((_ref1 = this.log.limit) != null ? _ref1.limited : void 0) {\n break;\n }\n _results.push(this.nodes.insert(Log.Dom.Node.create(this, [num, start * this.SLICE + ix], line)));\n }\n return _results;\n }\n });\n\n Log.Dom.Nodes = function(parent) {\n this.parent = parent;\n this.content = [];\n return this;\n };\n\n $.extend(Log.Dom.Nodes.prototype, {\n at: function(ix) {\n return this.content[ix];\n },\n insert: function(node) {\n return this.content[node.num] = node;\n },\n remove: function(node) {\n return this.content.splice(this.content.indexOf(node), 1);\n }\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('length', function() {\n return this.content.length;\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Nodes.prototype.__defineGetter__('last', function() {\n return this.content[this.content.length - 1];\n });\n\n Log.Dom.Node = function() {};\n\n $.extend(Log.Dom.Node, {\n FOLDS_PATTERN: /fold:(start|end):([\\w_\\-\\.]+)/,\n create: function(parent, ids, string) {\n var fold;\n if (fold = string.match(this.FOLDS_PATTERN)) {\n return new Log.Dom.Fold(parent, ids, fold[1], fold[2]);\n } else {\n return new Log.Dom.Paragraph(parent, ids, string);\n }\n }\n });\n\n Log.Dom.Node.prototype.__defineGetter__('prev', function() {\n var num, prev, _ref;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.parent.nodes.at(num -= 1);\n }\n return prev || ((_ref = this.parent.prev) != null ? _ref.nodes.last : void 0);\n });\n\n Log.Dom.Node.prototype.__defineGetter__('next', function() {\n var next, num, _ref;\n num = this.num;\n while (!(next || num >= this.parent.nodes.length)) {\n next = this.parent.nodes.at(num += 1);\n }\n return next || ((_ref = this.parent.next) != null ? _ref.nodes.first : void 0);\n });\n\n Log.Dom.Paragraph = function(parent, ids, string) {\n var _ref;\n this.parent = parent;\n this.ids = ids;\n this.id = ids.join('-');\n this.ends = !!((_ref = string[string.length - 1]) != null ? _ref.match(/\\n/) : void 0);\n this.spans = new Log.Dom.Spans(this, ids, string.replace(/\\n$/, ''));\n return this;\n };\n\n Log.Dom.Paragraph.prototype = $.extend(new Log.Dom.Node, {\n insert: function() {\n var next, prev,\n _this = this;\n if ((prev = this.prev) && !prev.ends && !prev.fold) {\n if (Log.DEBUG) {\n console.log(\"P.1 - move \" + this.id + \"'s spans into prev (\" + prev.id + \")\");\n }\n this.spans.content.slice().forEach(function(span) {\n return prev.spans.insert(span);\n });\n return this.remove();\n } else if ((next = this.next) && !this.ends && !next.fold) {\n if (Log.DEBUG) {\n console.log(\"P.2 - move next's (\" + next.id + \") spans into \" + this.id);\n }\n this.next.spans.content.slice().forEach(function(span) {\n return _this.spans.insert(span);\n });\n return next.remove();\n }\n },\n remove: function() {\n return this.parent.nodes.remove(this);\n }\n });\n\n Log.Dom.Paragraph.prototype.__defineGetter__('id', function() {\n return \"\" + this.parent.num + \"-\" + this.num;\n });\n\n Log.Dom.Spans = function(parent, ids, string) {\n this.parent = parent;\n this.content = this.parse(parent, ids, string);\n return this;\n };\n\n $.extend(Log.Dom.Spans.prototype, {\n parse: function(parent, ids, string) {\n var ix, span, _i, _len, _ref, _results;\n _ref = Log.Deansi.apply(string);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n span = _ref[ix];\n _results.push(new Log.Dom.Span(parent, ids.concat([ix]), span));\n }\n return _results;\n },\n at: function(ix) {\n return this.content[ix];\n },\n indexOf: function(span) {\n return this.content.indexOf(span);\n },\n insert: function(span) {\n var head, ix, prev;\n console.log('insert', span.id);\n span.parent.spans.remove(span);\n span.parent = this.parent;\n head = this.content.filter(function(s) {\n return s.id < span.id;\n });\n if (prev = head[head.length - 1]) {\n ix = this.content.indexOf(prev) + 1;\n }\n return this.content.splice(ix || 0, 0, span);\n },\n prepend: function(span) {\n span.parent.spans.remove(span);\n span.parent = this.parent;\n this.content.unshift(span);\n return span.insert();\n },\n remove: function(span) {\n return this.content.splice(this.content.indexof(span), 1);\n }\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('first', function() {\n return this.content[0];\n });\n\n Log.Dom.Spans.prototype.__defineGetter__('last', function() {\n return this.content[this.content.length - 1];\n });\n\n Log.Dom.Span = function(parent, ids, data) {\n this.parent = parent;\n this.ids = ids;\n this.id = ids.join('-');\n this.data = $.extend(data, {\n id: this.id\n });\n if (data.text.match(/\\r/)) {\n this.hidden = true;\n }\n this.data.text = data.text.replace(/^.*\\r/gm, '');\n if (this.hidden) {\n this.data[\"class\"] = ['hidden'];\n }\n return this;\n };\n\n $.extend(Log.Dom.Span.prototype, {\n insert: function() {\n var next, prev;\n if (prev = this.prev) {\n if (Log.DEBUG) {\n console.log(\"S.1 - insert \" + this.id + \" after prev \" + prev.id);\n }\n return this.insertAt({\n after: prev.element\n });\n } else if (next = this.next) {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" before next \" + next.id);\n }\n return this.insertAt({\n before: next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"S.2 - insert \" + this.id + \" into parent \" + this.parent.id);\n }\n return this.insertAt({\n into: this.parent.element\n });\n }\n },\n insertAt: function(pos) {\n return this.element = this.trigger('insert', this.data, pos);\n }\n });\n\n Log.Dom.Span.prototype.__defineGetter__('prev', function() {\n var span, _ref, _ref1;\n console.log(this.num);\n span = this.parent.spans.at(this.num - 1);\n return span || ((_ref = this.parent.prev) != null ? (_ref1 = _ref.spans) != null ? _ref1.last : void 0 : void 0);\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/dom2");minispade.register('log.old/engine/live', "(function() {(function() {\n\n Log.Live = function(log) {\n this.log = log;\n this.parts = [];\n return this;\n };\n\n $.extend(Log.Live.prototype, {\n set: function(num, string) {\n var part;\n if (this.parts[num]) {\n return console.log(\"part \" + num + \" exists\");\n } else {\n part = new Log.Live.Part(this, num, string);\n this.parts[num] = part;\n return this.parts[num].insert();\n }\n },\n trigger: function() {\n return this.log.trigger.apply(this.log, arguments);\n }\n });\n\n Log.Live.Part = function(log, num, string) {\n var ix, line;\n this.log = log;\n this.num = num;\n this.lines = (function() {\n var _i, _len, _ref, _results;\n _ref = string.split(/^/m);\n _results = [];\n for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {\n line = _ref[ix];\n _results.push(new Log.Live.Line(this, ix, line));\n }\n return _results;\n }).call(this);\n return this;\n };\n\n $.extend(Log.Live.Part.prototype, {\n insert: function() {\n return new Log.Live.Context(this.log, this).insert();\n },\n head: function() {\n var head, line;\n head = [];\n line = this.lines[0];\n while ((line = line != null ? line.prev() : void 0) && !line.isNewline()) {\n head.unshift(line);\n }\n return head;\n },\n tail: function() {\n var line, tail;\n tail = [];\n line = this.lines[this.lines.length - 1];\n while (line = line != null ? line.next() : void 0) {\n tail.push(line);\n if (line != null ? line.isNewline() : void 0) {\n break;\n }\n }\n return tail;\n },\n prev: function() {\n var num, prev;\n num = this.num;\n while (!(prev || num < 0)) {\n prev = this.log.parts[num -= 1];\n }\n return prev;\n },\n next: function() {\n var next, num;\n num = this.num;\n while (!(next || num >= this.log.parts.length)) {\n next = this.log.parts[num += 1];\n }\n return next;\n }\n });\n\n Log.Live.Line = function(part, num, string) {\n this.part = part;\n this.num = num;\n this.id = \"\" + part.num + \"-\" + num;\n this.string = string;\n return this;\n };\n\n $.extend(Log.Live.Line.prototype, {\n prev: function() {\n var line, _ref;\n line = this.part.lines[this.num - 1];\n return line || ((_ref = this.part.prev()) != null ? _ref.lines.slice(-1)[0] : void 0);\n },\n next: function() {\n var line, _ref;\n line = this.part.lines[this.num + 1];\n return line || ((_ref = this.part.next()) != null ? _ref.lines[0] : void 0);\n },\n isNewline: function() {\n return this.string[this.string.length - 1] === \"\\n\";\n },\n isFold: function() {\n return this.string.indexOf('fold') !== -1;\n },\n clone: function() {\n return new Log.Live.Line(this.part, this.num, this.string);\n }\n });\n\n Log.Live.Context = function(log, part) {\n this.log = log;\n this.part = part;\n this.head = part.head();\n this.tail = part.tail();\n this.lines = this.join(this.head.concat(part.lines).concat(this.tail));\n return this;\n };\n\n $.extend(Log.Live.Context.prototype, {\n insert: function() {\n var ids;\n ids = this.head.concat(this.tail).map(function(line) {\n return line.id;\n });\n if (ids.length !== 0) {\n this.log.trigger('remove', ids);\n }\n return this.log.trigger('insert', this.after(), this.nodes());\n },\n nodes: function() {\n var _this = this;\n return this.lines.map(function(line) {\n var fold, string;\n string = line.string;\n if (fold = _this.defold(string)) {\n return $.extend(fold, {\n id: line.id\n });\n } else {\n return {\n id: line.id,\n nodes: _this.deansi(string)\n };\n }\n });\n },\n join: function(all) {\n var line, lines;\n lines = [];\n while (line = all.pop()) {\n if (lines.length === 0 || line.isNewline()) {\n lines.unshift(line.clone());\n } else {\n lines[0].string = line.string + lines[0].string;\n }\n }\n return lines;\n },\n after: function() {\n var line, _ref;\n line = (_ref = this.part.lines[0]) != null ? _ref.prev() : void 0;\n while (line && !line.isNewline() && !line.isFold()) {\n line = line.prev();\n }\n return line != null ? line.id : void 0;\n },\n defold: function(string) {\n var matches;\n if (matches = string.match(/fold:(start|end):([\\w_\\-\\.]+)/)) {\n return {\n type: 'fold',\n event: matches[1],\n name: matches[2]\n };\n }\n },\n deansi: function(string) {\n return Log.Deansi.apply(string);\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/engine/live");minispade.register('log.old/folds', "(function() {(function() {\n\n Log.Folds = function() {\n this.folds = {};\n return this;\n };\n\n Log.Folds.prototype = $.extend(new Log.Listener, {\n insert: function(log, data, pos) {\n var fold, _base, _name;\n if (data.type === 'fold') {\n fold = (_base = this.folds)[_name = data.name] || (_base[_name] = new Log.Folds.Fold);\n fold.receive(data);\n }\n return true;\n }\n });\n\n Log.Folds.Fold = function() {\n return this;\n };\n\n $.extend(Log.Folds.Fold.prototype, {\n receive: function(data) {\n this[data.event] = data.id;\n if (this.start && this.end && !this.active) {\n return this.activate();\n }\n },\n activate: function() {\n var node, _i, _len, _ref;\n console.log(\"F - activate \" + this.start);\n console.log(document.firstChild.innerHTML.replace(/

rcv \" + num + \" \" + (JSON.stringify(string)) + \"\");\n },\n insert: function(log, after, datas) {\n return this.log(\"ins \" + (datas.map(function(data) {\n return data.id;\n }).join(', ')) + \", after: \" + (after || '?') + \", \" + (JSON.stringify(datas)));\n },\n remove: function(log, id) {\n return this.log(\"rem \" + id);\n },\n log: function(line) {\n return $('#events').append(\"\" + line + \"\\n\");\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/instrument");minispade.register('log.old/limit', "(function() {(function() {\n\n Log.Limit = function(max_lines) {\n this.max_lines = max_lines || 1000;\n return this;\n };\n\n Log.Limit.prototype = $.extend(new Log.Listener, {\n count: 0,\n insert: function(log, line, pos) {\n if (line.type === 'paragraph' && !line.hidden) {\n return this.count += 1;\n }\n }\n });\n\n Log.Limit.prototype.__defineGetter__('limited', function() {\n return this.count >= this.max_lines;\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/limit");minispade.register('log.old/renderer/inner_html', "(function() {(function() {\n\n Log.InnerHtmlRenderer = function() {\n this.frag = document.createDocumentFragment();\n this.div = document.createElement('div');\n return this;\n };\n\n Log.InnerHtmlRenderer.prototype = $.extend(new Log.Listener, {\n remove: function(log, ids) {\n var id, node, _i, _len, _ref, _results;\n _results = [];\n for (_i = 0, _len = ids.length; _i < _len; _i++) {\n id = ids[_i];\n node = document.getElementById(id);\n if (node && !((_ref = node.getAttribute('class')) != null ? _ref.match(/fold/) : void 0)) {\n _results.push(node.parentNode.removeChild(node));\n } else {\n _results.push(void 0);\n }\n }\n return _results;\n },\n insert: function(log, after, nodes) {\n var html;\n log = document.getElementById('log');\n html = this.render(nodes);\n if (log.childNodes.length === 0) {\n return log.innerHTML = html;\n } else if (after) {\n after = document.getElementById(after);\n return this.insertAfter(this.fragmentFrom(html), after);\n } else {\n log = document.getElementById('log');\n return log.insertBefore(this.fragmentFrom(html), log.firstChild);\n }\n },\n render: function(nodes) {\n var node;\n return ((function() {\n var _i, _len, _results;\n _results = [];\n for (_i = 0, _len = nodes.length; _i < _len; _i++) {\n node = nodes[_i];\n _results.push(this.renderNode(node));\n }\n return _results;\n }).call(this)).join('');\n },\n renderNode: function(node) {\n var type;\n node.type || (node.type = 'paragraph');\n type = node.type[0].toUpperCase() + node.type.slice(1);\n return this[\"render\" + type](node) || '';\n },\n renderParagraph: function(node) {\n var html, style;\n if (node.hidden) {\n style = ' style=\"display:none\"';\n }\n html = \"

\";\n html += ((function() {\n var _i, _len, _ref, _results;\n _ref = node.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n _results.push(this.renderNode(node));\n }\n return _results;\n }).call(this)).join('');\n return html + '

';\n },\n renderFold: function(node) {\n if (!document.getElementById(node.id)) {\n return \"
\";\n }\n },\n renderSpan: function(node) {\n return \"\" + (this.clean(node.text)) + \"\";\n },\n renderText: function(node) {\n return this.clean(node.text);\n },\n clean: function(text) {\n return text.replace(/\\n/gm, '');\n },\n fragmentFrom: function(html) {\n var div, frag, node;\n frag = this.frag.cloneNode();\n div = this.div.cloneNode();\n div.innerHTML = html;\n while (node = div.firstChild) {\n frag.appendChild(node);\n }\n return frag;\n },\n insertAfter: function(node, after) {\n if (after.nextSibling) {\n return after.parentNode.insertBefore(node, after.nextSibling);\n } else {\n return after.parentNode.appendChild(node);\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/renderer/inner_html");minispade.register('log.old/renderer/jquery', "(function() {(function() {\n\n Log.JqueryRenderer = function() {};\n\n Log.JqueryRenderer.prototype = $.extend(new Log.Listener, {\n remove: function(log, ids) {\n var id, _i, _len, _results;\n _results = [];\n for (_i = 0, _len = ids.length; _i < _len; _i++) {\n id = ids[_i];\n _results.push($(\"#log #\" + id).remove());\n }\n return _results;\n },\n insert: function(log, after, datas) {\n var html,\n _this = this;\n html = datas.map(function(data) {\n return _this.render(data);\n });\n return after && $(\"#log #\" + after).after(html) || $('#log').prepend(html);\n },\n render: function(data) {\n var node, nodes, text;\n nodes = (function() {\n var _i, _len, _ref, _results;\n _ref = data.nodes;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n text = node.text.replace(/\\n/gm, '');\n if (node.type === 'span') {\n text = \"\" + text + \"\";\n }\n _results.push(\"

\" + text + \"

\");\n }\n return _results;\n }).call(this);\n return nodes.join(\"\\n\");\n },\n style: function(data) {\n return data.hidden && 'display: none;' || '';\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log.old/renderer/jquery");minispade.register('log/deansi', "(function() {(function() {\n\n Log.Deansi = {\n CLEAR_ANSI: /(\\e|\\033)\\[K|(\\e|\\033)\\[K|(\\e|\\033)\\[\\d+G|(\\e|\\033)\\(B|(\\e|\\033)M|(\\e|\\033)\\[\\?25(l|h)/gm,\n apply: function(string) {\n var nodes,\n _this = this;\n if (!string) {\n return [];\n }\n string = string.replace(this.CLEAR_ANSI, '');\n nodes = ansiparse(string).map(function(part) {\n return _this.node(part);\n });\n if (nodes.length === 0) {\n nodes.push(this.node({\n text: ''\n }));\n }\n return nodes;\n },\n node: function(part) {\n var classes, node;\n node = {\n type: 'span',\n text: part.text\n };\n if (classes = this.classes(part)) {\n node[\"class\"] = classes.join(' ');\n }\n return node;\n },\n classes: function(part) {\n var result;\n result = [];\n result = result.concat(this.colors(part));\n if (result.length > 0) {\n return result;\n }\n },\n colors: function(part) {\n var colors;\n colors = [];\n if (part.foreground) {\n colors.push(part.foreground);\n }\n if (part.background) {\n colors.push(\"bg-\" + part.background);\n }\n if (part.bold) {\n colors.push('bold');\n }\n if (part.italic) {\n colors.push('italic');\n }\n return colors;\n },\n hidden: function(part) {\n if (part.text.match(/\\r/)) {\n part.text = part.text.replace(/^.*\\r/gm, '');\n return true;\n }\n }\n };\n\n}).call(this);\n\n})();\n//@ sourceURL=log/deansi");minispade.register('log/folds', "(function() {(function() {\n\n Log.Folds = function() {\n this.folds = {};\n return this;\n };\n\n Log.extend(Log.Folds.prototype, {\n add: function(data) {\n var fold, _base, _name;\n fold = (_base = this.folds)[_name = data.name] || (_base[_name] = new Log.Folds.Fold);\n fold.receive(data);\n return fold.active;\n }\n });\n\n Log.Folds.Fold = function() {\n return this;\n };\n\n Log.extend(Log.Folds.Fold.prototype, {\n receive: function(data) {\n this[data.event] = data.id;\n if (this.start && this.end && !this.active) {\n return this.activate();\n }\n },\n activate: function() {\n var node, _i, _len, _ref;\n if (Log.DEBUG) {\n console.log(\"F - activate \" + this.start);\n }\n _ref = this.nodes;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n this.fold.appendChild(node);\n }\n this.fold.setAttribute('class', this.classes());\n return this.active = true;\n },\n classes: function() {\n var classes;\n classes = this.fold.getAttribute('class').split(' ');\n classes.push('fold');\n if (this.fold.childNodes.length > 2) {\n classes.push('active');\n }\n return classes.join(' ');\n }\n });\n\n Object.defineProperty(Log.Folds.Fold.prototype, 'fold', {\n get: function() {\n return this._fold || (this._fold = document.getElementById(this.start));\n }\n });\n\n Object.defineProperty(Log.Folds.Fold.prototype, 'nodes', {\n get: function() {\n var node, nodes;\n node = this.fold;\n nodes = [];\n while ((node = node.nextSibling) && node.id !== this.end) {\n nodes.push(node);\n }\n return nodes;\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/folds");minispade.register('log/limit', "(function() {(function() {\n\n Log.Limit = function(max_lines) {\n this.max_lines = max_lines || 1000;\n return this;\n };\n\n Log.Limit.prototype = Log.extend(new Log.Listener, {\n count: 0,\n insert: function(log, node, pos) {\n if (node.type === 'paragraph' && !node.hidden) {\n return this.count += 1;\n }\n }\n });\n\n Object.defineProperty(Log.Limit.prototype, 'limited', {\n get: function() {\n return this.count >= this.max_lines;\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/limit");minispade.register('log/nodes', "(function() {(function() {\n var newLineAtTheEndRegexp, newLineRegexp, rRegexp, removeCarriageReturns;\n\n Log.Node = function(id, num) {\n this.id = id;\n this.num = num;\n this.key = Log.Node.key(this.id);\n this.children = new Log.Nodes(this);\n return this;\n };\n\n Log.extend(Log.Node, {\n key: function(id) {\n if (id) {\n return id.split('-').map(function(i) {\n return '000000'.concat(i).slice(-6);\n }).join('');\n }\n }\n });\n\n Log.extend(Log.Node.prototype, {\n addChild: function(node) {\n return this.children.add(node);\n },\n remove: function() {\n this.log.remove(this.element);\n return this.parent.children.remove(this);\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'log', {\n get: function() {\n var _ref;\n return this._log || (this._log = ((_ref = this.parent) != null ? _ref.log : void 0) || this.parent);\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'firstChild', {\n get: function() {\n return this.children.first;\n }\n });\n\n Object.defineProperty(Log.Node.prototype, 'lastChild', {\n get: function() {\n return this.children.last;\n }\n });\n\n Log.Nodes = function(parent) {\n if (parent) {\n this.parent = parent;\n }\n this.items = [];\n this.index = {};\n return this;\n };\n\n Log.extend(Log.Nodes.prototype, {\n add: function(item) {\n var ix, next, prev, _ref, _ref1;\n ix = this.position(item) || 0;\n this.items.splice(ix, 0, item);\n if (this.parent) {\n item.parent = this.parent;\n }\n prev = function(item) {\n while (item && !item.children.last) {\n item = item.prev;\n }\n return item != null ? item.children.last : void 0;\n };\n next = function(item) {\n while (item && !item.children.first) {\n item = item.next;\n }\n return item != null ? item.children.first : void 0;\n };\n if (item.prev = this.items[ix - 1] || prev((_ref = this.parent) != null ? _ref.prev : void 0)) {\n item.prev.next = item;\n }\n if (item.next = this.items[ix + 1] || next((_ref1 = this.parent) != null ? _ref1.next : void 0)) {\n item.next.prev = item;\n }\n return item;\n },\n remove: function(item) {\n this.items.splice(this.items.indexOf(item), 1);\n if (item.next) {\n item.next.prev = item.prev;\n }\n if (item.prev) {\n item.prev.next = item.next;\n }\n if (this.items.length === 0) {\n return this.parent.remove();\n }\n },\n position: function(item) {\n var ix, _i, _ref;\n for (ix = _i = _ref = this.items.length - 1; _i >= 0; ix = _i += -1) {\n if (this.items[ix].key < item.key) {\n return ix + 1;\n }\n }\n },\n indexOf: function() {\n return this.items.indexOf.apply(this.items, arguments);\n },\n slice: function() {\n return this.items.slice.apply(this.items, arguments);\n },\n each: function(func) {\n return this.items.slice().forEach(func);\n },\n map: function(func) {\n return this.items.map(func);\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'first', {\n get: function() {\n return this.items[0];\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'last', {\n get: function() {\n return this.items[this.length - 1];\n }\n });\n\n Object.defineProperty(Log.Nodes.prototype, 'length', {\n get: function() {\n return this.items.length;\n }\n });\n\n Log.Part = function(id, num, string) {\n Log.Node.apply(this, arguments);\n this.string = string || '';\n this.string = this.string.replace(/\\033\\[1000D/gm, '\\r');\n this.string = this.string.replace(/\\r+\\n/gm, '\\n');\n this.strings = this.string.split(/^/gm) || [];\n this.slices = ((function() {\n var _results;\n _results = [];\n while (this.strings.length > 0) {\n _results.push(this.strings.splice(0, Log.SLICE));\n }\n return _results;\n }).call(this));\n return this;\n };\n\n Log.extend(Log.Part, {\n create: function(log, num, string) {\n var part;\n part = new Log.Part(num.toString(), num, string);\n log.addChild(part);\n return part.process(0, -1);\n }\n });\n\n Log.Part.prototype = Log.extend(new Log.Node, {\n remove: function() {},\n process: function(slice, num) {\n var node, span, spans, string, _i, _j, _len, _len1, _ref, _ref1, _ref2,\n _this = this;\n _ref = this.slices[slice] || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n string = _ref[_i];\n if ((_ref1 = this.log.limit) != null ? _ref1.limited : void 0) {\n return;\n }\n spans = [];\n _ref2 = Log.Deansi.apply(string);\n for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {\n node = _ref2[_j];\n span = Log.Span.create(this, \"\" + this.id + \"-\" + (num += 1), num, node.text, node[\"class\"]);\n span.render();\n spans.push(span);\n }\n if (spans[0] && spans[0].line.cr) {\n spans[0].line.clear();\n }\n }\n if (!(slice >= this.slices.length - 1)) {\n return setTimeout((function() {\n return _this.process(slice + 1, num);\n }), Log.TIMEOUT);\n }\n }\n });\n\n newLineAtTheEndRegexp = new RegExp(\"\\n$\");\n\n newLineRegexp = new RegExp(\"\\n\");\n\n rRegexp = new RegExp(\"\\r\");\n\n removeCarriageReturns = function(string) {\n var index;\n index = string.lastIndexOf(\"\\r\");\n if (index === -1) {\n return string;\n }\n return string.substr(index + 1);\n };\n\n Log.Span = function(id, num, text, classes) {\n var fold, _ref;\n Log.Node.apply(this, arguments);\n if (fold = text.match(Log.FOLD)) {\n this.fold = true;\n this.event = fold[1];\n this.text = this.name = fold[2];\n } else {\n this.text = text;\n this.text = removeCarriageReturns(this.text);\n this.text = this.text.replace(newLineAtTheEndRegexp, '');\n this.nl = !!((_ref = text[text.length - 1]) != null ? _ref.match(newLineRegexp) : void 0);\n this.cr = !!text.match(rRegexp);\n this[\"class\"] = this.cr && ['clears'] || classes;\n }\n return this;\n };\n\n Log.extend(Log.Span, {\n create: function(parent, id, num, text, classes) {\n var span;\n span = new Log.Span(id, num, text, classes);\n parent.addChild(span);\n return span;\n },\n render: function(parent, id, num, text, classes) {\n var span;\n span = this.create(parent, id, num, text, classes);\n return span.render();\n }\n });\n\n Log.Span.prototype = Log.extend(new Log.Node, {\n render: function() {\n var tail;\n if (!this.fold && this.prev && !this.prev.fold && !this.prev.nl) {\n if (Log.DEBUG) {\n console.log(\"S.1 insert \" + this.id + \" after prev \" + this.prev.id);\n }\n this.log.insert(this.data, {\n after: this.prev.element\n });\n this.line = this.prev.line;\n } else if (!this.fold && this.next && !this.next.fold) {\n if (Log.DEBUG) {\n console.log(\"S.2 insert \" + this.id + \" before next \" + this.next.id);\n }\n this.log.insert(this.data, {\n before: this.next.element\n });\n this.line = this.next.line;\n } else {\n this.line = Log.Line.create(this.log, [this]);\n this.line.render();\n }\n if (this.nl && (tail = this.tail).length > 0) {\n return this.split(tail);\n }\n },\n remove: function() {\n Log.Node.prototype.remove.apply(this);\n if (this.line) {\n return this.line.remove(this);\n }\n },\n split: function(spans) {\n var line, span, _i, _len;\n if (Log.DEBUG) {\n console.log(\"S.3 split [\" + (spans.map(function(span) {\n return span.id;\n }).join(', ')) + \"]\");\n }\n for (_i = 0, _len = spans.length; _i < _len; _i++) {\n span = spans[_i];\n this.log.remove(span.element);\n }\n line = Log.Line.create(this.log, spans);\n line.render();\n if (line.cr) {\n return line.clear();\n }\n },\n clear: function() {\n if (this.prev && this.isSibling(this.prev) && this.isSequence(this.prev)) {\n this.prev.clear();\n return this.prev.remove();\n }\n },\n isSequence: function(other) {\n return this.parent.num - other.parent.num === this.log.children.indexOf(this.parent) - this.log.children.indexOf(other.parent);\n },\n isSibling: function(other) {\n var _ref, _ref1;\n return ((_ref = this.element) != null ? _ref.parentNode : void 0) === ((_ref1 = other.element) != null ? _ref1.parentNode : void 0);\n },\n siblings: function(type) {\n var siblings, span;\n siblings = [];\n while ((span = (span || this)[type]) && this.isSibling(span)) {\n siblings.push(span);\n }\n return siblings;\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'data', {\n get: function() {\n return {\n id: this.id,\n type: 'span',\n text: this.text,\n \"class\": this[\"class\"]\n };\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'line', {\n get: function() {\n return this._line;\n },\n set: function(line) {\n if (this.line) {\n this.line.remove(this);\n }\n this._line = line;\n return this.line.add(this);\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'element', {\n get: function() {\n return document.getElementById(this.id);\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'head', {\n get: function() {\n return this.siblings('prev').reverse();\n }\n });\n\n Object.defineProperty(Log.Span.prototype, 'tail', {\n get: function() {\n return this.siblings('next');\n }\n });\n\n Log.Line = function(log) {\n this.log = log;\n this.spans = [];\n return this;\n };\n\n Log.extend(Log.Line, {\n create: function(log, spans) {\n var line, span, _i, _len;\n if ((span = spans[0]) && span.fold) {\n line = new Log.Fold(log, span.event, span.name);\n } else {\n line = new Log.Line(log);\n }\n for (_i = 0, _len = spans.length; _i < _len; _i++) {\n span = spans[_i];\n span.line = line;\n }\n return line;\n }\n });\n\n Log.extend(Log.Line.prototype, {\n add: function(span) {\n var ix;\n if (span.cr) {\n this.cr = true;\n }\n if (this.spans.indexOf(span) > -1) {\n\n } else if ((ix = this.spans.indexOf(span.prev)) > -1) {\n return this.spans.splice(ix + 1, 0, span);\n } else if ((ix = this.spans.indexOf(span.next)) > -1) {\n return this.spans.splice(ix, 0, span);\n } else {\n return this.spans.push(span);\n }\n },\n remove: function(span) {\n var ix;\n if ((ix = this.spans.indexOf(span)) > -1) {\n return this.spans.splice(ix, 1);\n }\n },\n render: function() {\n var fold;\n if ((fold = this.prev) && fold.event === 'start' && fold.active) {\n if (Log.DEBUG) {\n console.log(\"L.0 insert \" + this.id + \" into fold \" + fold.id);\n }\n fold = this.log.folds.folds[fold.name].fold;\n return this.element = this.log.insert(this.data, {\n into: fold\n });\n } else if (this.prev) {\n if (Log.DEBUG) {\n console.log(\"L.1 insert \" + this.spans[0].id + \" after prev \" + this.prev.id);\n }\n return this.element = this.log.insert(this.data, {\n after: this.prev.element\n });\n } else if (this.next) {\n if (Log.DEBUG) {\n console.log(\"L.2 insert \" + this.spans[0].id + \" before next \" + this.next.id);\n }\n return this.element = this.log.insert(this.data, {\n before: this.next.element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"L.3 insert \" + this.spans[0].id + \" into #log\");\n }\n return this.element = this.log.insert(this.data);\n }\n },\n clear: function() {\n var cr, _i, _len, _ref, _results;\n _ref = this.crs;\n _results = [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n cr = _ref[_i];\n _results.push(cr.clear());\n }\n return _results;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'id', {\n get: function() {\n var _ref;\n return (_ref = this.spans[0]) != null ? _ref.id : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'data', {\n get: function() {\n return {\n type: 'paragraph',\n nodes: this.nodes\n };\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'nodes', {\n get: function() {\n return this.spans.map(function(span) {\n return span.data;\n });\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'prev', {\n get: function() {\n var _ref;\n return (_ref = this.spans[0].prev) != null ? _ref.line : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'next', {\n get: function() {\n var _ref;\n return (_ref = this.spans[this.spans.length - 1].next) != null ? _ref.line : void 0;\n }\n });\n\n Object.defineProperty(Log.Line.prototype, 'crs', {\n get: function() {\n return this.spans.filter(function(span) {\n return span.cr;\n });\n }\n });\n\n Log.Fold = function(log, event, name) {\n Log.Line.apply(this, arguments);\n this.fold = true;\n this.event = event;\n this.name = name;\n return this;\n };\n\n Log.Fold.prototype = Log.extend(new Log.Line, {\n render: function() {\n var element, _ref;\n if (this.prev) {\n if (Log.DEBUG) {\n console.log(\"F.1 insert \" + this.id + \" after prev \" + this.prev.id);\n }\n element = this.prev.element || this.prev.element.parentNode;\n this.element = this.log.insert(this.data, {\n after: element\n });\n } else if (this.next) {\n if (Log.DEBUG) {\n console.log(\"F.2 insert \" + this.id + \" before next \" + this.next.id);\n }\n element = this.next.element || this.next.element.parentNode;\n this.element = this.log.insert(this.data, {\n before: element\n });\n } else {\n if (Log.DEBUG) {\n console.log(\"F.3 insert \" + this.id);\n }\n this.element = this.log.insert(this.data);\n }\n if (this.span.next && ((_ref = this.span.prev) != null ? _ref.isSibling(this.span.next) : void 0)) {\n this.span.prev.split([this.span.next].concat(this.span.next.tail));\n }\n return this.active = this.log.folds.add(this.data);\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'id', {\n get: function() {\n return \"fold-\" + this.event + \"-\" + this.name;\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'span', {\n get: function() {\n return this.spans[0];\n }\n });\n\n Object.defineProperty(Log.Fold.prototype, 'data', {\n get: function() {\n return {\n type: 'fold',\n id: this.id,\n event: this.event,\n name: this.name\n };\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/nodes");minispade.register('log/renderer', "(function() {(function() {\n\n Log.Renderer = function() {\n this.frag = document.createDocumentFragment();\n this.para = this.createParagraph();\n this.span = this.createSpan();\n this.text = document.createTextNode('');\n this.fold = this.createFold();\n return this;\n };\n\n Log.extend(Log.Renderer.prototype, {\n insert: function(data, pos) {\n var after, before, into, node;\n node = this.render(data);\n if (into = pos != null ? pos.into : void 0) {\n if (typeof into === 'String') {\n into = document.getElementById(pos != null ? pos.into : void 0);\n }\n this.appendTo(node, into);\n } else if (after = pos != null ? pos.after : void 0) {\n if (typeof after === 'String') {\n after = document.getElementById(pos);\n }\n this.insertAfter(node, after);\n } else if (before = pos != null ? pos.before : void 0) {\n if (typeof before === 'String') {\n before = document.getElementById(pos != null ? pos.before : void 0);\n }\n this.insertBefore(node, before);\n } else {\n this.insertBefore(node);\n }\n return node;\n },\n hide: function(node) {\n node.setAttribute('class', this.addClass(node.getAttribute('class'), 'hidden'));\n return node;\n },\n remove: function(node) {\n if (node) {\n node.parentNode.removeChild(node);\n }\n return node;\n },\n render: function(data) {\n var frag, node, type, _i, _len;\n if (data instanceof Array) {\n frag = this.frag.cloneNode(true);\n for (_i = 0, _len = data.length; _i < _len; _i++) {\n node = data[_i];\n node = this.render(node);\n if (node) {\n frag.appendChild(node);\n }\n }\n return frag;\n } else {\n data.type || (data.type = 'paragraph');\n type = data.type[0].toUpperCase() + data.type.slice(1);\n return this[\"render\" + type](data);\n }\n },\n renderParagraph: function(data) {\n var node, para, type, _i, _len, _ref;\n para = this.para.cloneNode(true);\n if (data.id) {\n para.setAttribute('id', data.id);\n }\n if (data.hidden) {\n para.setAttribute('style', 'display: none;');\n }\n _ref = data.nodes || [];\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n node = _ref[_i];\n type = node.type[0].toUpperCase() + node.type.slice(1);\n node = this[\"render\" + type](node);\n para.appendChild(node);\n }\n return para;\n },\n renderFold: function(data) {\n var fold;\n fold = this.fold.cloneNode(true);\n fold.setAttribute('id', data.id || (\"fold-\" + data.event + \"-\" + data.name));\n fold.setAttribute('class', \"fold-\" + data.event);\n if (data.event === 'start') {\n fold.lastChild.lastChild.nodeValue = data.name;\n } else {\n fold.removeChild(fold.lastChild);\n }\n return fold;\n },\n renderSpan: function(data) {\n var span;\n span = this.span.cloneNode(true);\n if (data.id) {\n span.setAttribute('id', data.id);\n }\n if (data[\"class\"]) {\n span.setAttribute('class', data[\"class\"]);\n }\n span.lastChild.nodeValue = data.text;\n return span;\n },\n renderText: function(data) {\n var text;\n text = this.text.cloneNode(true);\n text.nodeValue = data.text;\n return text;\n },\n createParagraph: function() {\n var para;\n para = document.createElement('p');\n para.appendChild(document.createElement('a'));\n return para;\n },\n createFold: function() {\n var fold;\n fold = document.createElement('div');\n fold.appendChild(this.createSpan());\n fold.lastChild.setAttribute('class', 'fold-name');\n return fold;\n },\n createSpan: function() {\n var span;\n span = document.createElement('span');\n span.appendChild(document.createTextNode(''));\n return span;\n },\n insertBefore: function(node, other) {\n var log;\n if (other) {\n return other.parentNode.insertBefore(node, other);\n } else {\n log = document.getElementById('log');\n return log.insertBefore(node, log.firstChild);\n }\n },\n insertAfter: function(node, other) {\n if (other.nextSibling) {\n return this.insertBefore(node, other.nextSibling);\n } else {\n return this.appendTo(node, other.parentNode);\n }\n },\n appendTo: function(node, other) {\n return other.appendChild(node);\n },\n addClass: function(classes, string) {\n if (classes != null ? classes.indexOf(string) : void 0) {\n return;\n }\n if (classes) {\n return \"\" + classes + \" \" + string;\n } else {\n return string;\n }\n }\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=log/renderer"); \ No newline at end of file From 072a5de579bd14aa06208b505f9085a46d4766e4 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 29 Aug 2013 16:33:07 +0200 Subject: [PATCH 13/33] Show first_sync page after routerTransitions This code ensures that we send an event after router is fully initialized, otherwise it could cause an error. --- assets/scripts/app/controllers/current_user.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/scripts/app/controllers/current_user.coffee b/assets/scripts/app/controllers/current_user.coffee index 5a00ad7b..96a950ff 100644 --- a/assets/scripts/app/controllers/current_user.coffee +++ b/assets/scripts/app/controllers/current_user.coffee @@ -4,5 +4,6 @@ Travis.CurrentUserController = Em.ObjectController.extend syncingDidChange: (-> if (user = @get('content')) && user.get('isSyncing') && !user.get('syncedAt') - @container.lookup('router:main').send('renderFirstSync') + Ember.run.scheduleOnce 'routerTransitions', this, -> + @container.lookup('router:main').send('renderFirstSync') ).observes('isSyncing', 'content') From 44c7c627fb02226ddd34e4a2552958c527d481af Mon Sep 17 00:00:00 2001 From: Lukasz Sarnacki Date: Sun, 21 Jul 2013 22:31:29 +0200 Subject: [PATCH 14/33] Replace github refference with link travis-ci/travis-ci#1266 Github issue refference is now replaced with github issue url. This covers following refferences: * #Num * User#Num * User/Project#Num --- assets/scripts/app/helpers/handlebars.coffee | 4 +-- assets/scripts/app/helpers/helpers.coffee | 25 +++++++++++++++- assets/scripts/app/templates/builds/list.hbs | 2 +- assets/scripts/app/templates/builds/show.hbs | 2 +- assets/scripts/app/templates/jobs/show.hbs | 2 +- assets/scripts/spec/unit/helpers_spec.coffee | 31 ++++++++++++++++++++ 6 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 assets/scripts/spec/unit/helpers_spec.coffee diff --git a/assets/scripts/app/helpers/handlebars.coffee b/assets/scripts/app/helpers/handlebars.coffee index f6fbf3f2..8dfda033 100644 --- a/assets/scripts/app/helpers/handlebars.coffee +++ b/assets/scripts/app/helpers/handlebars.coffee @@ -28,8 +28,8 @@ Ember.registerBoundHelper 'formatSha', (sha, options) -> Ember.registerBoundHelper 'pathFrom', (url, options) -> safe Travis.Helpers.pathFrom(url) -Ember.registerBoundHelper 'formatMessage', (message, options) -> - safe Travis.Helpers.formatMessage(message, options) +Ember.Handlebars.helper 'formatMessage', (message, options) -> + safe Travis.Helpers.formatMessage(message, options.hash) Ember.registerBoundHelper 'formatConfig', (config, options) -> safe Travis.Helpers.formatConfig(config) diff --git a/assets/scripts/app/helpers/helpers.coffee b/assets/scripts/app/helpers/helpers.coffee index 2b68dbd4..0fd8a46e 100644 --- a/assets/scripts/app/helpers/helpers.coffee +++ b/assets/scripts/app/helpers/helpers.coffee @@ -37,7 +37,10 @@ require 'config/emoij' formatMessage: (message, options) -> message = message || '' message = message.split(/\n/)[0] if options.short - @_emojize(@_escape(message)).replace /\n/g, '
' + message = @_emojize(@_escape(message)) + if !!options.repo + message = @githubify(message, options.repo.get('owner'), options.repo.get('name')) + message.replace /\n/g, '
' pathFrom: (url) -> (url || '').split('/').pop() @@ -66,6 +69,26 @@ require 'config/emoij' result.push seconds + ' sec' if seconds > 0 if result.length > 0 then result.join(' ') else '-' + githubify: (text, owner, repo) -> + refferences = text.match(@_githubRefferenceRegexp('g')) + if !!refferences + self = this + for refference in refferences + do (refference) -> + text = text.replace refference, (refference) -> + self._githubRefferenceLink(refference, owner, repo) + + text + + _githubRefferenceLink: (refference, owner, repo) -> + [newOwner, newRepo, issue] = refference.match(@_githubRefferenceRegexp())[1..3] + actualOwner = if newOwner? then newOwner else owner + actualRepo = if newRepo? then newRepo else repo + "#{refference}" + + _githubRefferenceRegexp: (flags) -> + new RegExp("([\\w-]+)?\\/?([\\w-]+)?#(\\d+)", flags) + _normalizeDateString: (string) -> if window.JHW string = string.replace('T', ' ').replace(/-/g, '/') diff --git a/assets/scripts/app/templates/builds/list.hbs b/assets/scripts/app/templates/builds/list.hbs index 41e7a038..cbedfa5e 100644 --- a/assets/scripts/app/templates/builds/list.hbs +++ b/assets/scripts/app/templates/builds/list.hbs @@ -32,7 +32,7 @@ {{/if}} - {{{formatMessage commit.message short="true"}}} + {{{formatMessage commit.message short="true" repoBinding=build.repo}}} diff --git a/assets/scripts/app/templates/builds/show.hbs b/assets/scripts/app/templates/builds/show.hbs index 302ba6a1..282c44d1 100644 --- a/assets/scripts/app/templates/builds/show.hbs +++ b/assets/scripts/app/templates/builds/show.hbs @@ -45,7 +45,7 @@ {{/with}}
{{t builds.message}}
-
{{formatMessage build.commit.message}}
+
{{formatMessage build.commit.message repoBinding=build.repo}}
{{#unless isMatrix}}
{{t builds.config}}
diff --git a/assets/scripts/app/templates/jobs/show.hbs b/assets/scripts/app/templates/jobs/show.hbs index 79558480..fd233cc2 100644 --- a/assets/scripts/app/templates/jobs/show.hbs +++ b/assets/scripts/app/templates/jobs/show.hbs @@ -43,7 +43,7 @@ {{/with}}
{{t jobs.message}}
-
{{formatMessage job.commit.message}}
+
{{formatMessage job.commit.message repoBinding=job.repo}}
{{t jobs.config}}
{{formatConfig job.config}}
diff --git a/assets/scripts/spec/unit/helpers_spec.coffee b/assets/scripts/spec/unit/helpers_spec.coffee new file mode 100644 index 00000000..1a5eea80 --- /dev/null +++ b/assets/scripts/spec/unit/helpers_spec.coffee @@ -0,0 +1,31 @@ +module "Travis.Helpers.githubify" + +test 'replaces #Num with github issues link', -> + message = 'Solved #11hey' + result = Travis.Helpers.githubify(message, 'travis-ci', 'travis-web') + expected = 'Solved
#11hey' + + equal(result, expected, "#num should be converted to a link") + +test 'replaces User#Num with github issues link to forked repo', -> + message = 'Solved test#11hey' + result = Travis.Helpers.githubify(message, 'travis-ci', 'travis-web') + expected = 'Solved test#11hey' + + equal(result, expected, "user#num should be converted to a link") + +test 'replaces User#Num with github issues link to another repo', -> + message = 'Solved test/testing#11hey' + result = Travis.Helpers.githubify(message, 'travis-ci', 'travis-web') + expected = 'Solved test/testing#11hey' + + equal(result, expected, "owner/repo#num should be converted to a link") + +test 'replaces multiple refferences with github issues links', -> + message = 'Try #1 and test#2 and test/testing#3' + result = Travis.Helpers.githubify(message, 'travis-ci', 'travis-web') + expected = 'Try #1 and ' + expected += 'test#2 and ' + expected += 'test/testing#3' + + equal(result, expected, "references should be converted to links") From 4ab6a90b66218580aa005825cfdd5d654299c62a Mon Sep 17 00:00:00 2001 From: Lukasz Sarnacki Date: Sun, 21 Jul 2013 23:41:18 +0200 Subject: [PATCH 15/33] Simplified code in github refference linking --- assets/scripts/app/helpers/helpers.coffee | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/assets/scripts/app/helpers/helpers.coffee b/assets/scripts/app/helpers/helpers.coffee index 0fd8a46e..517f88d4 100644 --- a/assets/scripts/app/helpers/helpers.coffee +++ b/assets/scripts/app/helpers/helpers.coffee @@ -70,24 +70,17 @@ require 'config/emoij' if result.length > 0 then result.join(' ') else '-' githubify: (text, owner, repo) -> - refferences = text.match(@_githubRefferenceRegexp('g')) - if !!refferences - self = this - for refference in refferences - do (refference) -> - text = text.replace refference, (refference) -> - self._githubRefferenceLink(refference, owner, repo) - + self = this + text = text.replace @_githubRefferenceRegexp, (refference, matchedOwner, matchedRepo, matchedNumber) -> + self._githubRefferenceLink(refference, { owner: owner, repo: repo }, { owner: matchedOwner, repo: matchedRepo, number: matchedNumber } ) text - _githubRefferenceLink: (refference, owner, repo) -> - [newOwner, newRepo, issue] = refference.match(@_githubRefferenceRegexp())[1..3] - actualOwner = if newOwner? then newOwner else owner - actualRepo = if newRepo? then newRepo else repo - "#{refference}" + _githubRefferenceLink: (refference, current, matched) -> + owner = matched.owner || current.owner + repo = matched.repo || current.repo + "#{refference}" - _githubRefferenceRegexp: (flags) -> - new RegExp("([\\w-]+)?\\/?([\\w-]+)?#(\\d+)", flags) + _githubRefferenceRegexp: new RegExp("([\\w-]+)?\\/?([\\w-]+)?#(\\d+)", 'g') _normalizeDateString: (string) -> if window.JHW From 226c5bb42b13794156924ac04566f0d764500314 Mon Sep 17 00:00:00 2001 From: Lukasz Sarnacki Date: Mon, 22 Jul 2013 01:24:50 +0200 Subject: [PATCH 16/33] More github references replaced with links Github links will be created for following references: * @username * gh-1 (for issue #1) --- assets/scripts/app/helpers/helpers.coffee | 17 +++++++---- assets/scripts/spec/unit/helpers_spec.coffee | 31 +++++++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/assets/scripts/app/helpers/helpers.coffee b/assets/scripts/app/helpers/helpers.coffee index 517f88d4..53daa7b6 100644 --- a/assets/scripts/app/helpers/helpers.coffee +++ b/assets/scripts/app/helpers/helpers.coffee @@ -71,16 +71,23 @@ require 'config/emoij' githubify: (text, owner, repo) -> self = this - text = text.replace @_githubRefferenceRegexp, (refference, matchedOwner, matchedRepo, matchedNumber) -> - self._githubRefferenceLink(refference, { owner: owner, repo: repo }, { owner: matchedOwner, repo: matchedRepo, number: matchedNumber } ) + text = text.replace @_githubReferenceRegexp, (reference, matchedOwner, matchedRepo, matchedNumber) -> + self._githubReferenceLink(reference, { owner: owner, repo: repo }, { owner: matchedOwner, repo: matchedRepo, number: matchedNumber } ) + text = text.replace @_githubUserRegexp, (reference, username) -> + self._githubUserLink(reference, username) text - _githubRefferenceLink: (refference, current, matched) -> + _githubReferenceLink: (reference, current, matched) -> owner = matched.owner || current.owner repo = matched.repo || current.repo - "#{refference}" + "#{reference}" - _githubRefferenceRegexp: new RegExp("([\\w-]+)?\\/?([\\w-]+)?#(\\d+)", 'g') + _githubReferenceRegexp: new RegExp("([\\w-]+)?\\/?([\\w-]+)?(?:#|gh-)(\\d+)", 'g') + + _githubUserRegexp: new RegExp("@([\\w-]+)", 'g') + + _githubUserLink: (reference, username) -> + "#{reference}" _normalizeDateString: (string) -> if window.JHW diff --git a/assets/scripts/spec/unit/helpers_spec.coffee b/assets/scripts/spec/unit/helpers_spec.coffee index 1a5eea80..829c8a13 100644 --- a/assets/scripts/spec/unit/helpers_spec.coffee +++ b/assets/scripts/spec/unit/helpers_spec.coffee @@ -14,14 +14,21 @@ test 'replaces User#Num with github issues link to forked repo', -> equal(result, expected, "user#num should be converted to a link") -test 'replaces User#Num with github issues link to another repo', -> - message = 'Solved test/testing#11hey' +test 'replaces User/Project#Num with github issues link to another repo', -> + message = 'Solved test_1-a2/test-a_11#11hey' result = Travis.Helpers.githubify(message, 'travis-ci', 'travis-web') - expected = 'Solved test/testing#11hey' + expected = 'Solved test_1-a2/test-a_11#11hey' equal(result, expected, "owner/repo#num should be converted to a link") -test 'replaces multiple refferences with github issues links', -> +test 'replaces gh-Num with github issues link', -> + message = 'Solved gh-22hey' + result = Travis.Helpers.githubify(message, 'travis-ci', 'travis-web') + expected = 'Solved gh-22hey' + + equal(result, expected, "gh-Num should be converted to a link") + +test 'replaces multiple references with github issues links', -> message = 'Try #1 and test#2 and test/testing#3' result = Travis.Helpers.githubify(message, 'travis-ci', 'travis-web') expected = 'Try #1 and ' @@ -29,3 +36,19 @@ test 'replaces multiple refferences with github issues links', -> expected += 'test/testing#3' equal(result, expected, "references should be converted to links") + +test 'replaces multiple references with github issues links', -> + message = 'Try #1 and test#2 and test/testing#3' + result = Travis.Helpers.githubify(message, 'travis-ci', 'travis-web') + expected = 'Try #1 and ' + expected += 'test#2 and ' + expected += 'test/testing#3' + + equal(result, expected, "references should be converted to links") + +test 'replaces @user with github user link', -> + message = 'It is for you @tender_love1' + result = Travis.Helpers.githubify(message, 'travis-ci', 'travis-web') + expected = 'It is for you @tender_love1' + + equal(result, expected, "@user should be converted to a link") From 4b7f9bb14a9f77dffeb5f21569fee8acb04ce23d Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Sun, 1 Sep 2013 14:22:48 +0200 Subject: [PATCH 17/33] Delegate CurrentUserController#updateLocale to user instance --- assets/scripts/app/controllers/current_user.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/assets/scripts/app/controllers/current_user.coffee b/assets/scripts/app/controllers/current_user.coffee index 96a950ff..c8226503 100644 --- a/assets/scripts/app/controllers/current_user.coffee +++ b/assets/scripts/app/controllers/current_user.coffee @@ -1,3 +1,9 @@ +delegate = (name, options) -> + options ||= options + -> + target = @get(options.to) + target[name].apply(target, arguments) + Travis.CurrentUserController = Em.ObjectController.extend sync: -> @get('content').sync() @@ -7,3 +13,5 @@ Travis.CurrentUserController = Em.ObjectController.extend Ember.run.scheduleOnce 'routerTransitions', this, -> @container.lookup('router:main').send('renderFirstSync') ).observes('isSyncing', 'content') + + updateLocale: delegate('updateLocale', to: 'content') From daf8e000f3a56e9c7924bc6a8d06f6f5f8d3dbb8 Mon Sep 17 00:00:00 2001 From: Mathias Meyer Date: Tue, 3 Sep 2013 14:28:59 +0200 Subject: [PATCH 18/33] Remove
at the end of the profile page line. --- locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.yml b/locales/en.yml index 11011db8..4717ddf8 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -118,7 +118,7 @@ en: locale: locale message: config: how to configure custom build options - your_repos: ! " Flick the switches below to turn on the Travis service hook for your projects, then push to GitHub.
" + your_repos: ! " Flick the switches below to turn on the Travis service hook for your projects, then push to GitHub." messages: notice: ! "To get started, please read our Getting Started guide.\n It will only take a couple of minutes." token: Token From 2b63b8ca71d3de9f1950198741dd35ad190baf8d Mon Sep 17 00:00:00 2001 From: Mathias Meyer Date: Tue, 3 Sep 2013 14:33:37 +0200 Subject: [PATCH 19/33] Remove the Ruby hint from all the locales. What was that about again? So weird. --- locales/de.yml | 2 +- locales/es.yml | 2 +- locales/fr.yml | 4 +--- locales/nb.yml | 2 +- locales/nl.yml | 4 +--- locales/pl.yml | 4 +--- locales/pt-BR.yml | 2 +- locales/ru.yml | 4 +--- 8 files changed, 8 insertions(+), 16 deletions(-) diff --git a/locales/de.yml b/locales/de.yml index 6766e5a4..4d19d12d 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -118,7 +118,7 @@ de: locale: Sprache message: config: Wie du eigene Build-Optionen konfigurieren kannst - your_repos: Lege die Schalter unten um, um den Travis Dienst-Hook für deine Projekte zu aktivieren, dann pushe zu GitHub.
\n Um gegen mehrere Rubine zu testen, sehe + your_repos: Lege die Schalter unten um, um den Travis Dienst-Hook für deine Projekte zu aktivieren, dann pushe zu GitHub. messages: notice: Bitte lese dir unser Getting Started-Handbuch durch, um loszulegen.\\n Es dauert nur ein paar Minuten. token: diff --git a/locales/es.yml b/locales/es.yml index c004b639..8b729b6b 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -118,7 +118,7 @@ es: locale: message: config: como configurar tus propias opciones para el Build - your_repos: ! " Activa los interruptores para inicial el Travis service hook para tus proyectos, y haz un Push en GitHub.
\n Para probar varias versiones de ruby, mira" + your_repos: ! " Activa los interruptores para inicial el Travis service hook para tus proyectos, y haz un Push en GitHub." messages: notice: ! "Para comenzar, por favor lee nuestra Guía de Inicio .\n Solo tomará unos pocos minutos." token: Token diff --git a/locales/fr.yml b/locales/fr.yml index ea556788..9d9477ed 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -118,9 +118,7 @@ fr: locale: message: config: comment configurer des options de version personnalisées - your_repos: ! 'Utilisez les boutons ci-dessous pour activer Travis sur vos projets puis déployez sur GitHub.
- - Pour tester sur plus de versions de ruby, voir' + your_repos: ! 'Utilisez les boutons ci-dessous pour activer Travis sur vos projets puis déployez sur GitHub.' messages: notice: ! "Pour commencer, veuillez lire notre guide de démarrage.\n Cela ne vous prendra que quelques minutes." token: Jeton diff --git a/locales/nb.yml b/locales/nb.yml index c8ce62af..0345a7c0 100644 --- a/locales/nb.yml +++ b/locales/nb.yml @@ -118,7 +118,7 @@ nb: locale: message: config: hvordan sette opp egne jobbinnstillinger - your_repos: ! "Slå\x10 på Travis for prosjektene dine ved å dra i bryterne under, og send koden til Github.
\nFor å teste mot flere versjoner av ruby, se " + your_repos: ! "Slå\x10 på Travis for prosjektene dine ved å dra i bryterne under, og send koden til Github." messages: notice: For å komme i gang, vennligst les kom-i-gang-veivisereren vår. Det tar bare et par minutter. token: Kode diff --git a/locales/nl.yml b/locales/nl.yml index b6a50d96..a49ed57f 100644 --- a/locales/nl.yml +++ b/locales/nl.yml @@ -118,9 +118,7 @@ nl: locale: Taal message: config: hoe eigen bouw-opties in te stellen - your_repos: ! 'Zet de schakelaars hieronder aan om de Travis hook voor uw projecten te activeren en push daarna naar Github
- - Om te testen tegen meerdere rubies, zie' + your_repos: ! 'Zet de schakelaars hieronder aan om de Travis hook voor uw projecten te activeren en push daarna naar Github.' messages: notice: Om te beginnen kunt u onze startersgids lezen.\n Het zal maar enkele minuten van uw tijd vergen. token: Token diff --git a/locales/pl.yml b/locales/pl.yml index fd4ad095..602326e7 100644 --- a/locales/pl.yml +++ b/locales/pl.yml @@ -121,9 +121,7 @@ pl: locale: język message: config: jak skonfigurować niestandardowe opcje builda - your_repos: ! 'Przesuń suwak poniżej, aby włączyć Travisa dla twoich projektów, a następnie umieść swój kod na GitHubie.
- - Aby testować swój kod przy użyciu wielu wersji Rubiego, zobacz' + your_repos: ! 'Przesuń suwak poniżej, aby włączyć Travisa dla twoich projektów, a następnie umieść swój kod na GitHubie.' messages: notice: ! "Aby zacząć, przeczytaj nasz Przewodnik.\n Zajmie ci to tylko kilka minut." token: Token diff --git a/locales/pt-BR.yml b/locales/pt-BR.yml index 93e34f24..e4108c50 100644 --- a/locales/pt-BR.yml +++ b/locales/pt-BR.yml @@ -118,7 +118,7 @@ pt-BR: locale: message: config: como configurar opções de build - your_repos: Use os botões abaixo para ligar ou desligar o hook de serviço do Travis para seus projetos, e então, faça um push para o Github.
Para testar com múltiplas versões do Ruby, leia + your_repos: Use os botões abaixo para ligar ou desligar o hook de serviço do Travis para seus projetos, e então, faça um push para o Github. messages: notice: Para começar, leia nosso Guia de início. Só leva alguns minutinhos. token: Token diff --git a/locales/ru.yml b/locales/ru.yml index b647458e..b2a39dc5 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -124,9 +124,7 @@ ru: locale: Язык message: config: как настроить специальные опции билда - your_repos: ! 'Используйте переключатели, чтобы включить Travis service hook для вашего проекта, а потом отправьте код на GitHub.
- - Для тестирования на нескольких версиях Ruby смотрите' + your_repos: ! 'Используйте переключатели, чтобы включить Travis service hook для вашего проекта, а потом отправьте код на GitHub.' messages: notice: Перед началом, пожалуйста, прочтите Руководство для быстрого старта. Это займет всего несколько минут. token: Токен From 12aaeeef2dd7fe155b99a10e6982b2d536e0839b Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 4 Sep 2013 21:15:54 +0200 Subject: [PATCH 20/33] Add records to record arrays in a runloop When adding records just after loading them, the elements in the UI might have to be updated, which may trigger `get` on associations right away. As an effect, even if we load a few records on an event (like repository on build:started event), ember model may end up fetching the record. This commit fixes such occurrences by adding record to record arrays in a run loop, so newly created records will be added at once, after the event was served. --- assets/scripts/lib/travis/model.coffee | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/assets/scripts/lib/travis/model.coffee b/assets/scripts/lib/travis/model.coffee index 914970b6..4c37fe50 100644 --- a/assets/scripts/lib/travis/model.coffee +++ b/assets/scripts/lib/travis/model.coffee @@ -172,12 +172,23 @@ Array.prototype.diff = (a) -> delete this.recordCache[key] loadRecordForReference: (reference) -> - record = this.create({ _reference: reference }) - this.recordCache = {} unless this.recordCache - this.sideloadedData = {} unless this.sideloadedData - this.recordCache[reference.id] = record + record = @create({ _reference: reference }) + @recordCache = {} unless @recordCache + @sideloadedData = {} unless @sideloadedData + @recordCache[reference.id] = record reference.record = record - record.load(reference.id, this.sideloadedData[reference.id]) + record.load(reference.id, @sideloadedData[reference.id]) # TODO: find a nicer way to not add record to record arrays twice - if !this._findAllRecordArray || !this._findAllRecordArray.contains(record) - this.addToRecordArrays(record) + if @currentRecordsToAdd + @currentRecordsToAdd.pushObject(record) unless @currentRecordsToAdd.contains(record) + else + @currentRecordsToAdd = [record] + + Ember.run.scheduleOnce('data', this, @_batchAddToRecordArrays); + + _batchAddToRecordArrays: -> + for record in @currentRecordsToAdd + if !@_findAllRecordArray || !@_findAllRecordArray.contains(record) + @addToRecordArrays(record) + + @currentRecordsToAdd = null From 408cce2d4f4735e76558d10223ba3d61ed8bf584 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 6 Sep 2013 19:36:34 +0200 Subject: [PATCH 21/33] Update ember and ember-model --- assets/scripts/vendor/ember-model.js | 775 ++- assets/scripts/vendor/ember.js | 9002 ++++++++++++++++++-------- 2 files changed, 6753 insertions(+), 3024 deletions(-) diff --git a/assets/scripts/vendor/ember-model.js b/assets/scripts/vendor/ember-model.js index a5eea67b..c47a64c7 100644 --- a/assets/scripts/vendor/ember-model.js +++ b/assets/scripts/vendor/ember-model.js @@ -2,26 +2,29 @@ function mustImplement(message) { var fn = function() { - throw new Error(message); + var className = this.constructor.toString(); + + throw new Error(message.replace('{{className}}', className)); }; fn.isUnimplemented = true; return fn; } Ember.Adapter = Ember.Object.extend({ - find: mustImplement('Ember.Adapter subclasses must implement find'), - findQuery: mustImplement('Ember.Adapter subclasses must implement findQuery'), - findMany: mustImplement('Ember.Adapter subclasses must implement findMany'), - findAll: mustImplement('Ember.Adapter subclasses must implement findAll'), - createRecord: mustImplement('Ember.Adapter subclasses must implement createRecord'), - saveRecord: mustImplement('Ember.Adapter subclasses must implement saveRecord'), - deleteRecord: mustImplement('Ember.Adapter subclasses must implement deleteRecord'), + find: mustImplement('{{className}} must implement find'), + findQuery: mustImplement('{{className}} must implement findQuery'), + findMany: mustImplement('{{className}} must implement findMany'), + findAll: mustImplement('{{className}} must implement findAll'), + createRecord: mustImplement('{{className}} must implement createRecord'), + saveRecord: mustImplement('{{className}} must implement saveRecord'), + deleteRecord: mustImplement('{{className}} must implement deleteRecord'), load: function(record, id, data) { record.load(id, data); } }); + })(); (function() { @@ -143,12 +146,23 @@ Ember.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, { }, reload: function() { - var modelClass = this.get('modelClass'); - Ember.assert("Reload can only be called on findAll RecordArrays", - modelClass && modelClass._findAllRecordArray === this); + var modelClass = this.get('modelClass'), + self = this, + promises; set(this, 'isLoaded', false); - modelClass.adapter.findAll(modelClass, this); + if (modelClass._findAllRecordArray === this) { + modelClass.adapter.findAll(modelClass, this); + } else if (this._query) { + modelClass.adapter.findQuery(modelClass, this, this._query); + } else { + promises = this.map(function(record) { + return record.reload(); + }); + Ember.RSVP.all(promises).then(function(data) { + self.notifyLoaded(); + }); + } } }); @@ -171,13 +185,13 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ throw new Error('FilteredRecordArrays must be created with filterProperties'); } - this._registeredClientIds = Ember.A([]); - var modelClass = get(this, 'modelClass'); modelClass.registerRecordArray(this); this.registerObservers(); this.updateFilter(); + + this._super(); }, updateFilter: function() { @@ -186,8 +200,6 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ get(this, 'modelClass').forEachCachedRecord(function(record) { if (self.filterFunction(record)) { results.push(record); - } else { - results.removeObject(record); } }); this.set('content', Ember.A(results)); @@ -195,10 +207,8 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ updateFilterForRecord: function(record) { var results = get(this, 'content'); - if (this.filterFunction(record)) { - if(!results.contains(record)) { - results.pushObject(record); - } + if (this.filterFunction(record) && !results.contains(record)) { + results.pushObject(record); } else { results.removeObject(record); } @@ -213,14 +223,10 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ registerObserversOnRecord: function(record) { var self = this, - filterProperties = get(this, 'filterProperties'), - clientId = record._reference.clientId; + filterProperties = get(this, 'filterProperties'); - if(!this._registeredClientIds.contains(clientId)) { - for (var i = 0, l = get(filterProperties, 'length'); i < l; i++) { - record.addObserver(filterProperties[i], self, 'updateFilterForRecord'); - } - this._registeredClientIds.pushObject(clientId); + for (var i = 0, l = get(filterProperties, 'length'); i < l; i++) { + record.addObserver(filterProperties[i], self, 'updateFilterForRecord'); } } }); @@ -229,10 +235,31 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ (function() { -var get = Ember.get; +var get = Ember.get, set = Ember.set; Ember.ManyArray = Ember.RecordArray.extend({ _records: null, + originalContent: null, + + isDirty: function() { + var originalContent = get(this, 'originalContent'), + originalContentLength = get(originalContent, 'length'), + content = get(this, 'content'), + contentLength = get(content, 'length'); + + if (originalContentLength !== contentLength) { return true; } + + var isDirty = false; + + for (var i = 0, l = contentLength; i < l; i++) { + if (!originalContent.contains(content[i])) { + isDirty = true; + break; + } + } + + return isDirty; + }.property('content.[]', 'originalContent'), objectAtContent: function(idx) { var content = get(this, 'content'); @@ -255,6 +282,48 @@ Ember.ManyArray = Ember.RecordArray.extend({ }, this); this._super(index, removed, added); + }, + + _contentWillChange: function() { + var content = get(this, 'content'); + if (content) { + content.removeArrayObserver(this); + this._setupOriginalContent(content); + } + }.observesBefore('content'), + + _contentDidChange: function() { + var content = get(this, 'content'); + if (content) { + content.addArrayObserver(this); + this.arrayDidChange(content, 0, 0, get(content, 'length')); + } + }.observes('content'), + + arrayWillChange: function(item, idx, removedCnt, addedCnt) {}, + + arrayDidChange: function(item, idx, removedCnt, addedCnt) { + var parent = get(this, 'parent'), relationshipKey = get(this, 'relationshipKey'), + isDirty = get(this, 'isDirty'); + + if (isDirty) { + parent._relationshipBecameDirty(relationshipKey); + } else { + parent._relationshipBecameClean(relationshipKey); + } + }, + + _setupOriginalContent: function(content) { + content = content || get(this, 'content'); + if (content) { + set(this, 'originalContent', content.slice()); + } + }, + + init: function() { + this._super(); + this._setupOriginalContent(); + this._contentDidChange(); } }); @@ -268,7 +337,7 @@ Ember.HasManyArray = Ember.ManyArray.extend({ if (reference.record) { record = reference.record; } else { - record = klass.findById(reference.id); + record = klass.find(reference.id); } return record; @@ -357,36 +426,6 @@ function hasCachedValue(object, key) { } } -function extractDirty(object, attrsOrRelations, dirtyAttributes) { - var key, desc, descMeta, type, dataValue, cachedValue, isDirty, dataType; - for (var i = 0, l = attrsOrRelations.length; i < l; i++) { - key = attrsOrRelations[i]; - if (!hasCachedValue(object, key)) { continue; } - cachedValue = object.cacheFor(key); - dataValue = get(object, '_data.' + object.dataKey(key)); - desc = meta(object).descs[key]; - descMeta = desc && desc.meta(); - type = descMeta.type; - dataType = Ember.Model.dataTypes[type]; - - if (type && type.isEqual) { - isDirty = !type.isEqual(dataValue, cachedValue); - } else if (dataType && dataType.isEqual) { - isDirty = !dataType.isEqual(dataValue, cachedValue); - } else if (dataValue && cachedValue instanceof Ember.Model) { // belongsTo case - isDirty = get(cachedValue, 'isDirty'); - } else if (dataValue !== cachedValue) { - isDirty = true; - } else { - isDirty = false; - } - - if (isDirty) { - dirtyAttributes.push(key); - } - } -} - Ember.run.queues.push('data'); Ember.Model = Ember.Object.extend(Ember.Evented, { @@ -407,24 +446,20 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { return value; }, - isDirty: Ember.computed(function() { - var attributes = this.attributes, - relationships = this.relationships, - dirtyAttributes = Ember.A(); // just for removeObject + isDirty: function() { + var dirtyAttributes = get(this, '_dirtyAttributes'); + return dirtyAttributes && dirtyAttributes.length !== 0 || false; + }.property('_dirtyAttributes.length'), - extractDirty(this, attributes, dirtyAttributes); - if (relationships) { - extractDirty(this, relationships, dirtyAttributes); - } + _relationshipBecameDirty: function(name) { + var dirtyAttributes = get(this, '_dirtyAttributes'); + if (!dirtyAttributes.contains(name)) { dirtyAttributes.pushObject(name); } + }, - if (dirtyAttributes.length) { - this._dirtyAttributes = dirtyAttributes; - return true; - } else { - this._dirtyAttributes = []; - return false; - } - }).property().volatile(), + _relationshipBecameClean: function(name) { + var dirtyAttributes = get(this, '_dirtyAttributes'); + dirtyAttributes.removeObject(name); + }, dataKey: function(key) { var camelizeKeys = get(this.constructor, 'camelizeKeys'); @@ -437,6 +472,9 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { init: function() { this._createReference(); + if (!this._dirtyAttributes) { + set(this, '_dirtyAttributes', []); + } this._super(); this.one('didLoad', function() { @@ -449,7 +487,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { id = this.getPrimaryKey(); if (!reference) { - reference = this.constructor._referenceForId(id); + reference = this.constructor._getOrCreateReferenceForId(id); reference.record = this; this._reference = reference; } @@ -469,6 +507,27 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { var data = {}; data[get(this.constructor, 'primaryKey')] = id; set(this, '_data', Ember.merge(data, hash)); + + // eagerly load embedded data + var relationships = this.constructor._relationships || [], meta = Ember.meta(this), relationshipKey, relationship, relationshipMeta, relationshipData, relationshipType; + for (var i = 0, l = relationships.length; i < l; i++) { + relationshipKey = relationships[i]; + relationship = meta.descs[relationshipKey]; + relationshipMeta = relationship.meta(); + + if (relationshipMeta.options.embedded) { + relationshipType = relationshipMeta.type; + if (typeof relationshipType === "string") { + relationshipType = Ember.get(Ember.lookup, relationshipType); + } + + relationshipData = data[relationshipKey]; + if (relationshipData) { + relationshipType.load(relationshipData); + } + } + } + set(this, 'isLoaded', true); set(this, 'isNew', false); this._createReference(); @@ -478,13 +537,15 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { didDefineProperty: function(proto, key, value) { if (value instanceof Ember.Descriptor) { var meta = value.meta(); + var klass = proto.constructor; if (meta.isAttribute) { - proto.attributes = proto.attributes ? proto.attributes.slice() : []; - proto.attributes.push(key); + if (!klass._attributes) { klass._attributes = []; } + klass._attributes.push(key); } else if (meta.isRelationship) { - proto.relationships = proto.relationships ? proto.relationships.slice() : []; - proto.relationships.push(key); + if (!klass._relationships) { klass._relationships = []; } + klass._relationships.push(key); + meta.relationshipKey = key; } } }, @@ -506,7 +567,9 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { toJSON: function() { var key, meta, json = {}, - properties = this.attributes ? this.getProperties(this.attributes) : {}, + attributes = this.constructor.getAttributes(), + relationships = this.constructor.getRelationships(), + properties = attributes ? this.getProperties(attributes) : {}, rootKey = get(this.constructor, 'rootKey'); for (key in properties) { @@ -520,11 +583,11 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { } } - if (this.relationships) { + if (relationships) { var data, relationshipKey; - for(var i = 0; i < this.relationships.length; i++) { - key = this.relationships[i]; + for(var i = 0; i < relationships.length; i++) { + key = relationships[i]; meta = this.constructor.metaForProperty(key); relationshipKey = meta.options.key || key; @@ -570,15 +633,8 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { }, revert: function() { - if (this.get('isDirty')) { - var data = get(this, '_data') || {}, - reverts = {}; - for (var i = 0; i < this._dirtyAttributes.length; i++) { - var attr = this._dirtyAttributes[i]; - reverts[attr] = data[attr]; - } - setProperties(this, reverts); - } + this.getWithDefault('_dirtyAttributes', []).clear(); + this.notifyPropertyChange('_data'); }, didCreateRecord: function() { @@ -587,10 +643,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { set(this, 'isNew', false); - if (!this.constructor.recordCache) this.constructor.recordCache = {}; - this.constructor.recordCache[id] = this; - - this._copyDirtyAttributesToData(); + set(this, '_dirtyAttributes', []); this.constructor.addToRecordArrays(this); this.trigger('didCreateRecord'); this.didSaveRecord(); @@ -627,7 +680,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { key = dirtyAttributes[i]; data[this.dataKey(key)] = this.cacheFor(key); } - this._dirtyAttributes = []; + set(this, '_dirtyAttributes', []); }, dataDidChange: Ember.observer(function() { @@ -642,11 +695,16 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { _reloadHasManys: function() { if (!this._hasManyArrays) { return; } - - var i; - for(i = 0; i < this._hasManyArrays.length; i++) { - var array = this._hasManyArrays[i]; - set(array, 'content', this._getHasManyContent(get(array, 'key'), get(array, 'modelClass'), get(array, 'embedded'))); + var i, j; + for (i = 0; i < this._hasManyArrays.length; i++) { + var array = this._hasManyArrays[i], + hasManyContent = this._getHasManyContent(get(array, 'key'), get(array, 'modelClass'), get(array, 'embedded')); + for (j = 0; j < array.get('length'); j++) { + if (array.objectAt(j).get('isNew')) { + hasManyContent.addObject(array.objectAt(j)._reference); + } + } + set(array, 'content', hasManyContent); } }, @@ -658,12 +716,12 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { if (embedded) { primaryKey = get(type, 'primaryKey'); mapFunction = function(attrs) { - reference = type._referenceForId(attrs[primaryKey]); + reference = type._getOrCreateReferenceForId(attrs[primaryKey]); reference.data = attrs; return reference; }; } else { - mapFunction = function(id) { return type._referenceForId(id); }; + mapFunction = function(id) { return type._getOrCreateReferenceForId(id); }; } content = Ember.EnumerableUtils.map(content, mapFunction); } @@ -679,26 +737,77 @@ Ember.Model.reopenClass({ _clientIdCounter: 1, - fetch: function() { - return Ember.loadPromise(this.find.apply(this, arguments)); + getAttributes: function() { + this.proto(); // force class "compilation" if it hasn't been done. + var attributes = this._attributes || []; + if (typeof this.superclass.getAttributes === 'function') { + attributes = this.superclass.getAttributes().concat(attributes); + } + return attributes; + }, + + getRelationships: function() { + this.proto(); // force class "compilation" if it hasn't been done. + var relationships = this._relationships || []; + if (typeof this.superclass.getRelationships === 'function') { + relationships = this.superclass.getRelationships().concat(relationships); + } + return relationships; + }, + + fetch: function(id) { + if (!arguments.length) { + return this._findFetchAll(true); + } else if (Ember.isArray(id)) { + return this._findFetchMany(id, true); + } else if (typeof id === 'object') { + return this._findFetchQuery(id, true); + } else { + return this._findFetchById(id, true); + } }, find: function(id) { if (!arguments.length) { - return this.findAll(); + return this._findFetchAll(false); } else if (Ember.isArray(id)) { - return this.findMany(id); + return this._findFetchMany(id, false); } else if (typeof id === 'object') { - return this.findQuery(id); + return this._findFetchQuery(id, false); } else { - return this.findById(id); + return this._findFetchById(id, false); } }, - findMany: function(ids) { - Ember.assert("findMany requires an array", Ember.isArray(ids)); + findQuery: function(params) { + return this._findFetchQuery(params, false); + }, - var records = Ember.RecordArray.create({_ids: ids}); + fetchQuery: function(params) { + return this._findFetchQuery(params, true); + }, + + _findFetchQuery: function(params, isFetch) { + var records = Ember.RecordArray.create({modelClass: this, _query: params}); + + var promise = this.adapter.findQuery(this, records, params); + + return isFetch ? promise : records; + }, + + findMany: function(ids) { + return this._findFetchMany(ids, false); + }, + + fetchMany: function(ids) { + return this._findFetchMany(ids, true); + }, + + _findFetchMany: function(ids, isFetch) { + Ember.assert("findFetchMany requires an array", Ember.isArray(ids)); + + var records = Ember.RecordArray.create({_ids: ids, modelClass: this}), + deferred; if (!this.recordArrays) { this.recordArrays = []; } this.recordArrays.push(records); @@ -711,43 +820,97 @@ Ember.Model.reopenClass({ this._currentBatchRecordArrays = [records]; } + if (isFetch) { + deferred = Ember.Deferred.create(); + Ember.set(deferred, 'resolveWith', records); + + if (!this._currentBatchDeferreds) { this._currentBatchDeferreds = []; } + this._currentBatchDeferreds.push(deferred); + } + Ember.run.scheduleOnce('data', this, this._executeBatch); - return records; + return isFetch ? deferred : records; }, findAll: function() { - if (this._findAllRecordArray) { return this._findAllRecordArray; } + return this._findFetchAll(false); + }, + + fetchAll: function() { + return this._findFetchAll(true); + }, + + _findFetchAll: function(isFetch) { + var self = this; + + if (this._findAllRecordArray) { + if (isFetch) { + return new Ember.RSVP.Promise(function(resolve) { + resolve(self._findAllRecordArray); + }); + } else { + return this._findAllRecordArray; + } + } var records = this._findAllRecordArray = Ember.RecordArray.create({modelClass: this}); - this.adapter.findAll(this, records); + var promise = this.adapter.findAll(this, records); - return records; + // Remove the cached record array if the promise is rejected + if (promise.then) { + promise.then(null, function() { + self._findAllRecordArray = null; + return Ember.RSVP.reject.apply(null, arguments); + }); + } + + return isFetch ? promise : records; + }, + + findById: function(id) { + return this._findFetchById(id, false); + }, + + fetchById: function(id) { + return this._findFetchById(id, true); + }, + + _findFetchById: function(id, isFetch) { + var record = this.cachedRecordForId(id), + isLoaded = get(record, 'isLoaded'), + adapter = get(this, 'adapter'), + deferredOrPromise; + + if (isLoaded) { + if (isFetch) { + return new Ember.RSVP.Promise(function(resolve, reject) { + resolve(record); + }); + } else { + return record; + } + } + + deferredOrPromise = this._fetchById(record, id); + + return isFetch ? deferredOrPromise : record; }, _currentBatchIds: null, _currentBatchRecordArrays: null, - - findById: function(id) { - var record = this.cachedRecordForId(id); - - if (!get(record, 'isLoaded')) { - this._fetchById(record, id); - } - return record; - }, + _currentBatchDeferreds: null, reload: function(id) { var record = this.cachedRecordForId(id); - - this._fetchById(record, id); - - return record; + record.set('isLoaded', false); + return this._fetchById(record, id); }, _fetchById: function(record, id) { - var adapter = get(this, 'adapter'); + var adapter = get(this, 'adapter'), + deferred; if (adapter.findMany && !adapter.findMany.isUnimplemented) { if (this._currentBatchIds) { @@ -757,8 +920,17 @@ Ember.Model.reopenClass({ this._currentBatchRecordArrays = []; } + deferred = Ember.Deferred.create(); + + //Attached the record to the deferred so we can resolove it later. + Ember.set(deferred, 'resolveWith', record); + + if (!this._currentBatchDeferreds) { this._currentBatchDeferreds = []; } + this._currentBatchDeferreds.push(deferred); + Ember.run.scheduleOnce('data', this, this._executeBatch); - // TODO: return a promise here + + return deferred; } else { return adapter.find(record, id); } @@ -767,6 +939,7 @@ Ember.Model.reopenClass({ _executeBatch: function() { var batchIds = this._currentBatchIds, batchRecordArrays = this._currentBatchRecordArrays, + batchDeferreds = this._currentBatchDeferreds, self = this, requestIds = [], promise, @@ -774,6 +947,7 @@ Ember.Model.reopenClass({ this._currentBatchIds = null; this._currentBatchRecordArrays = null; + this._currentBatchDeferreds = null; for (i = 0; i < batchIds.length; i++) { if (!this.cachedRecordForId(batchIds[i]).get('isLoaded')) { @@ -797,29 +971,36 @@ Ember.Model.reopenClass({ for (var i = 0, l = batchRecordArrays.length; i < l; i++) { batchRecordArrays[i].loadForFindMany(self); } + + if (batchDeferreds) { + for (i = 0, l = batchDeferreds.length; i < l; i++) { + var resolveWith = Ember.get(batchDeferreds[i], 'resolveWith'); + batchDeferreds[i].resolve(resolveWith); + } + } + }).then(null, function(errorXHR) { + if (batchDeferreds) { + for (var i = 0, l = batchDeferreds.length; i < l; i++) { + batchDeferreds[i].reject(errorXHR); + } + } }); }, - findQuery: function(params) { - var records = Ember.RecordArray.create(); - - this.adapter.findQuery(this, records, params); - - return records; + getCachedReferenceRecord: function(id){ + var ref = this._getReferenceById(id); + if(ref) return ref.record; + return undefined; }, cachedRecordForId: function(id) { - if (!this.recordCache) { this.recordCache = {}; } - var record; + var record = this.getCachedReferenceRecord(id); - if (this.recordCache[id]) { - record = this.recordCache[id]; - } else { + if (!record) { var primaryKey = get(this, 'primaryKey'), - attrs = {isLoaded: false}; + attrs = {isLoaded: false}; attrs[primaryKey] = id; record = this.create(attrs); - this.recordCache[id] = record; var sideloadedData = this.sideloadedData && this.sideloadedData[id]; if (sideloadedData) { record.load(id, sideloadedData); @@ -829,6 +1010,7 @@ Ember.Model.reopenClass({ return record; }, + addToRecordArrays: function(record) { if (this._findAllRecordArray) { this._findAllRecordArray.pushObject(record); @@ -837,7 +1019,7 @@ Ember.Model.reopenClass({ this.recordArrays.forEach(function(recordArray) { if (recordArray instanceof Ember.FilteredRecordArray) { recordArray.registerObserversOnRecord(record); - recordArray.updateFilterForRecord(record); + recordArray.updateFilter(); } else { recordArray.pushObject(record); } @@ -845,6 +1027,26 @@ Ember.Model.reopenClass({ } }, + unload: function (record) { + this.removeFromRecordArrays(record); + var primaryKey = record.get(get(this, 'primaryKey')); + this.removeFromCache(primaryKey); + }, + + clearCache: function () { + this.sideloadedData = undefined; + this._referenceCache = undefined; + }, + + removeFromCache: function (key) { + if (this.sideloadedData && this.sideloadedData[key]) { + delete this.sideloadedData[key]; + } + if(this._referenceCache && this._referenceCache[key]) { + delete this._referenceCache[key]; + } + }, + removeFromRecordArrays: function(record) { if (this._findAllRecordArray) { this._findAllRecordArray.removeObject(record); @@ -880,25 +1082,39 @@ Ember.Model.reopenClass({ }, forEachCachedRecord: function(callback) { - if (!this.recordCache) { return Ember.A([]); } - var ids = Object.keys(this.recordCache); + if (!this._referenceCache) { this._referenceCache = {}; } + var ids = Object.keys(this._referenceCache); ids.map(function(id) { - return this.recordCache[id]; + return this._getReferenceById(id).record; }, this).forEach(callback); }, load: function(hashes) { + if (Ember.typeOf(hashes) !== 'array') { hashes = [hashes]; } + if (!this.sideloadedData) { this.sideloadedData = {}; } + for (var i = 0, l = hashes.length; i < l; i++) { - var hash = hashes[i]; - this.sideloadedData[hash[get(this, 'primaryKey')]] = hash; + var hash = hashes[i], + primaryKey = hash[get(this, 'primaryKey')], + record = this.getCachedReferenceRecord(primaryKey); + + if (record) { + record.load(primaryKey, hash); + } else { + this.sideloadedData[primaryKey] = hash; + } } }, - _referenceForId: function(id) { - if (!this._idToReference) { this._idToReference = {}; } + _getReferenceById: function(id) { + if (!this._referenceCache) { this._referenceCache = {}; } + return this._referenceCache[id]; + }, + + _getOrCreateReferenceForId: function(id) { + var reference = this._getReferenceById(id); - var reference = this._idToReference[id]; if (!reference) { reference = this._createReference(id); } @@ -907,9 +1123,9 @@ Ember.Model.reopenClass({ }, _createReference: function(id) { - if (!this._idToReference) { this._idToReference = {}; } + if (!this._referenceCache) { this._referenceCache = {}; } - Ember.assert('The id ' + id + ' has alread been used with another record of type ' + this.toString() + '.', !id || !this._idToReference[id]); + Ember.assert('The id ' + id + ' has alread been used with another record of type ' + this.toString() + '.', !id || !this._referenceCache[id]); var reference = { id: id, @@ -919,20 +1135,10 @@ Ember.Model.reopenClass({ // if we're creating an item, this process will be done // later, once the object has been persisted. if (id) { - this._idToReference[id] = reference; + this._referenceCache[id] = reference; } return reference; - }, - - resetData: function() { - this._idToReference = null; - this.sideloadedData = null; - this.recordCache = null; - this.recordArrays = null; - this._currentBatchIds = null; - this._hasManyArrays = null; - this._findAllRecordArray = null; } }); @@ -968,7 +1174,8 @@ Ember.Model.reopen({ modelClass: type, content: this._getHasManyContent(key, type, embedded), embedded: embedded, - key: key + key: key, + relationshipKey: meta.relationshipKey }); this._registerHasManyArray(collection); @@ -982,7 +1189,8 @@ Ember.Model.reopen({ (function() { -var get = Ember.get; +var get = Ember.get, + set = Ember.set; function getType() { if (typeof this.type === "string") { @@ -997,14 +1205,32 @@ Ember.belongsTo = function(type, options) { var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo', getType: getType }, relationshipKey = options.key; - return Ember.computed(function(key, value) { + return Ember.computed(function(key, value, oldValue) { type = meta.getType(); - if (arguments.length === 2) { + var dirtyAttributes = get(this, '_dirtyAttributes'), + createdDirtyAttributes = false; + + if (!dirtyAttributes) { + dirtyAttributes = []; + createdDirtyAttributes = true; + } + + if (arguments.length > 1) { if (value) { Ember.assert(Ember.String.fmt('Attempted to set property of type: %@ with a value of type: %@', [value.constructor, type]), value instanceof type); + + if (oldValue !== value) { + dirtyAttributes.pushObject(key); + } else { + dirtyAttributes.removeObject(key); + } + + if (createdDirtyAttributes) { + set(this, '_dirtyAttributes', dirtyAttributes); + } } return value === undefined ? null : value; } else { @@ -1023,11 +1249,12 @@ Ember.Model.reopen({ } if (meta.options.embedded) { - var primaryKey = get(type, 'primaryKey'); - record = type.create({ isLoaded: false }); - record.load(idOrAttrs[primaryKey], idOrAttrs); + var primaryKey = get(type, 'primaryKey'), + id = idOrAttrs[primaryKey]; + record = type.create({ isLoaded: false, id: id }); + record.load(id, idOrAttrs); } else { - record = type.findById(idOrAttrs); + record = type.find(idOrAttrs); } return record; @@ -1043,41 +1270,15 @@ var get = Ember.get, set = Ember.set, meta = Ember.meta; -function wrapObject(value) { - if (Ember.isArray(value)) { - var clonedArray = value.slice(); - - // TODO: write test for recursive cloning - for (var i = 0, l = clonedArray.length; i < l; i++) { - clonedArray[i] = wrapObject(clonedArray[i]); - } - - return Ember.A(clonedArray); - } else if (value && value.constructor === Date) { - return new Date(value.toISOString()); - } else if (value && typeof value === "object") { - var clone = Ember.create(value), property; - - for (property in value) { - if (value.hasOwnProperty(property) && typeof value[property] === "object") { - clone[property] = wrapObject(value[property]); - } - } - return clone; - } else { - return value; - } -} - Ember.Model.dataTypes = {}; Ember.Model.dataTypes[Date] = { deserialize: function(string) { - if(!string) { return null; } + if (!string) { return null; } return new Date(string); }, serialize: function (date) { - if(!date) { return null; } + if (!date) { return null; } return date.toISOString(); }, isEqual: function(obj1, obj2) { @@ -1104,7 +1305,7 @@ function deserialize(value, type) { } else if (type && Ember.Model.dataTypes[type]) { return Ember.Model.dataTypes[type].deserialize(value); } else { - return wrapObject(value); + return value; } } @@ -1114,15 +1315,35 @@ Ember.attr = function(type, options) { var data = get(this, '_data'), dataKey = this.dataKey(key), dataValue = data && get(data, dataKey), - beingCreated = meta(this).proto === this; + beingCreated = meta(this).proto === this, + dirtyAttributes = get(this, '_dirtyAttributes'), + createdDirtyAttributes = false; + + if (!dirtyAttributes) { + dirtyAttributes = []; + createdDirtyAttributes = true; + } if (arguments.length === 2) { - if (beingCreated && !data) { - data = {}; - set(this, '_data', data); - data[dataKey] = value; + if (beingCreated) { + if (!data) { + data = {}; + set(this, '_data', data); + } + dataValue = data[dataKey] = value; } - return wrapObject(value); + + if (dataValue !== value) { + dirtyAttributes.pushObject(key); + } else { + dirtyAttributes.removeObject(key); + } + + if (createdDirtyAttributes) { + set(this, '_dirtyAttributes', dirtyAttributes); + } + + return value; } return this.getAttr(key, deserialize(dataValue, type)); @@ -1143,6 +1364,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url).then(function(data) { self.didFind(record, id, data); + return record; }); }, @@ -1159,6 +1381,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url).then(function(data) { self.didFindAll(klass, records, data); + return records; }); }, @@ -1175,6 +1398,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url, params).then(function(data) { self.didFindQuery(klass, records, params, data); + return records; }); }, @@ -1191,6 +1415,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url, record.toJSON(), "POST").then(function(data) { self.didCreateRecord(record, data); + return record; }); }, @@ -1198,7 +1423,6 @@ Ember.RESTAdapter = Ember.Adapter.extend({ var rootKey = get(record.constructor, 'rootKey'), primaryKey = get(record.constructor, 'primaryKey'), dataToLoad = rootKey ? data[rootKey] : data; - record.load(dataToLoad[primaryKey], dataToLoad); record.didCreateRecord(); }, @@ -1210,6 +1434,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url, record.toJSON(), "PUT").then(function(data) { // TODO: Some APIs may or may not return data self.didSaveRecord(record, data); + return record; }); }, @@ -1239,7 +1464,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ var urlRoot = get(klass, 'url'); if (!urlRoot) { throw new Error('Ember.RESTAdapter requires a `url` property to be specified'); } - if (id) { + if (!Ember.isEmpty(id)) { return urlRoot + "/" + id + ".json"; } else { return urlRoot + ".json"; @@ -1272,6 +1497,11 @@ Ember.RESTAdapter = Ember.Adapter.extend({ }; settings.error = function(jqXHR, textStatus, errorThrown) { + // https://github.com/ebryn/ember-model/issues/202 + if (jqXHR) { + jqXHR.then = null; + } + Ember.run(null, reject, jqXHR); }; @@ -1315,4 +1545,123 @@ Ember.loadPromise = function(target) { }; +})(); + +(function() { + +// This is a debug adapter for the Ember Extension, don't let the fact this is called an "adapter" confuse you. +// Most copied from: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/debug/debug_adapter.js + +if (!Ember.DataAdapter) { return; } + +var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.String.underscore; + +var DebugAdapter = Ember.DataAdapter.extend({ + getFilters: function() { + return [ + { name: 'isNew', desc: 'New' }, + { name: 'isModified', desc: 'Modified' }, + { name: 'isClean', desc: 'Clean' } + ]; + }, + + detect: function(klass) { + return klass !== Ember.Model && Ember.Model.detect(klass); + }, + + columnsForType: function(type) { + var columns = [], count = 0, self = this; + Ember.A(get(type.proto(), 'attributes')).forEach(function(name, meta) { + if (count++ > self.attributeLimit) { return false; } + var desc = capitalize(underscore(name).replace('_', ' ')); + columns.push({ name: name, desc: desc }); + }); + return columns; + }, + + getRecords: function(type) { + var records = []; + type.forEachCachedRecord(function(record) { records.push(record); }); + return records; + }, + + getRecordColumnValues: function(record) { + var self = this, count = 0, + columnValues = { id: get(record, 'id') }; + + record.get('attributes').forEach(function(key) { + if (count++ > self.attributeLimit) { + return false; + } + var value = get(record, key); + columnValues[key] = value; + }); + return columnValues; + }, + + getRecordKeywords: function(record) { + var keywords = [], keys = Ember.A(['id']); + record.get('attributes').forEach(function(key) { + keys.push(key); + }); + keys.forEach(function(key) { + keywords.push(get(record, key)); + }); + return keywords; + }, + + getRecordFilterValues: function(record) { + return { + isNew: record.get('isNew'), + isModified: record.get('isDirty') && !record.get('isNew'), + isClean: !record.get('isDirty') + }; + }, + + getRecordColor: function(record) { + var color = 'black'; + if (record.get('isNew')) { + color = 'green'; + } else if (record.get('isDirty')) { + color = 'blue'; + } + return color; + }, + + observeRecord: function(record, recordUpdated) { + var releaseMethods = Ember.A(), self = this, + keysToObserve = Ember.A(['id', 'isNew', 'isDirty']); + + record.get('attributes').forEach(function(key) { + keysToObserve.push(key); + }); + + keysToObserve.forEach(function(key) { + var handler = function() { + recordUpdated(self.wrapRecord(record)); + }; + Ember.addObserver(record, key, handler); + releaseMethods.push(function() { + Ember.removeObserver(record, key, handler); + }); + }); + + var release = function() { + releaseMethods.forEach(function(fn) { fn(); } ); + }; + + return release; + } +}); + +Ember.onLoad('Ember.Application', function(Application) { + Application.initializer({ + name: "dataAdapter", + + initialize: function(container, application) { + application.register('dataAdapter:main', DebugAdapter); + } + }); +}); + })(); diff --git a/assets/scripts/vendor/ember.js b/assets/scripts/vendor/ember.js index 7635284e..b70d25b7 100644 --- a/assets/scripts/vendor/ember.js +++ b/assets/scripts/vendor/ember.js @@ -1,5 +1,5 @@ -// Version: v1.0.0-rc.7-59-g4048275 -// Last commit: 4048275 (2013-08-25 00:57:38 -0400) +// Version: v1.0.0-53-g0515044 +// Last commit: 0515044 (2013-09-06 06:59:59 -0700) (function() { @@ -156,10 +156,22 @@ Ember.deprecateFunc = function(message, func) { }; }; + +// Inform the developer about the Ember Inspector if not installed. +if (!Ember.testing) { + if (typeof window !== 'undefined' && window.chrome && window.addEventListener) { + window.addEventListener("load", function() { + if (document.body && document.body.dataset && !document.body.dataset.emberExtension) { + Ember.debug('For more advanced debugging, install the Ember Inspector from https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi'); + } + }, false); + } +} + })(); -// Version: v1.0.0-rc.7-59-g4048275 -// Last commit: 4048275 (2013-08-25 00:57:38 -0400) +// Version: v1.0.0-53-g0515044 +// Last commit: 0515044 (2013-09-06 06:59:59 -0700) (function() { @@ -225,7 +237,7 @@ var define, requireModule; @class Ember @static - @version 1.0.0-rc.7 + @version 1.0.0 */ if ('undefined' === typeof Ember) { @@ -252,10 +264,10 @@ Ember.toString = function() { return "Ember"; }; /** @property VERSION @type String - @default '1.0.0-rc.7' + @default '1.0.0' @final */ -Ember.VERSION = '1.0.0-rc.7'; +Ember.VERSION = '1.0.0'; /** Standard environmental variables. You can define these in a global `ENV` @@ -280,6 +292,27 @@ Ember.ENV = Ember.ENV || ENV; Ember.config = Ember.config || {}; +/** + Hash of enabled Canary features. Add to before creating your application. + + @property FEATURES + @type Hash +*/ + +Ember.FEATURES = {}; + +/** + Test that a feature is enabled. Parsed by Ember's build tools to leave + experimental features out of beta/stable builds. + + @method isEnabled + @param {string} feature +*/ + +Ember.FEATURES.isEnabled = function(feature) { + return Ember.FEATURES[feature]; +}; + // .......................................................... // BOOTSTRAP // @@ -332,7 +365,7 @@ Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPE Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true; /** - Empty function. Useful for some operations. + Empty function. Useful for some operations. Always returns `this`. @method K @private @@ -411,11 +444,87 @@ function assertPolyfill(test, message) { @namespace Ember */ Ember.Logger = { + /** + Logs the arguments to the console. + You can pass as many arguments as you want and they will be joined together with a space. + + ```javascript + var foo = 1; + Ember.Logger.log('log value of foo:', foo); // "log value of foo: 1" will be printed to the console + ``` + + @method log + @for Ember.Logger + @param {*} arguments + */ log: consoleMethod('log') || Ember.K, + /** + Prints the arguments to the console with a warning icon. + You can pass as many arguments as you want and they will be joined together with a space. + + ```javascript + Ember.Logger.warn('Something happened!'); // "Something happened!" will be printed to the console with a warning icon. + ``` + + @method warn + @for Ember.Logger + @param {*} arguments + */ warn: consoleMethod('warn') || Ember.K, + /** + Prints the arguments to the console with an error icon, red text and a stack race. + You can pass as many arguments as you want and they will be joined together with a space. + + ```javascript + Ember.Logger.error('Danger! Danger!'); // "Danger! Danger!" will be printed to the console in red text. + ``` + + @method error + @for Ember.Logger + @param {*} arguments + */ error: consoleMethod('error') || Ember.K, + /** + Logs the arguments to the console. + You can pass as many arguments as you want and they will be joined together with a space. + + ```javascript + var foo = 1; + Ember.Logger.info('log value of foo:', foo); // "log value of foo: 1" will be printed to the console + ``` + + @method info + @for Ember.Logger + @param {*} arguments + */ info: consoleMethod('info') || Ember.K, + /** + Logs the arguments to the console in blue text. + You can pass as many arguments as you want and they will be joined together with a space. + + ```javascript + var foo = 1; + Ember.Logger.debug('log value of foo:', foo); // "log value of foo: 1" will be printed to the console + ``` + + @method debug + @for Ember.Logger + @param {*} arguments + */ debug: consoleMethod('debug') || consoleMethod('info') || Ember.K, + /** + + If the value passed into Ember.Logger.assert is not truthy it will throw an error with a stack trace. + + ```javascript + Ember.Logger.assert(true); // undefined + Ember.Logger.assert(true === false); // Throws an Assertion failed error. + ``` + + @method assert + @for Ember.Logger + @param {Boolean} bool Value to test + */ assert: consoleMethod('assert') || assertPolyfill }; @@ -429,6 +538,15 @@ Ember.Logger = { internals encounter an error. This is useful for specialized error handling and reporting code. + ```javascript + Ember.onerror = function(error) { + Em.$.ajax('/report-error', 'POST', { + stack: error.stack, + otherInformation: 'whatever app state you want to provide' + }); + }; + ``` + @event onerror @for Ember @param {Exception} error the error object @@ -459,6 +577,21 @@ Ember.handleErrors = function(func, context) { } }; +/** + Merge the contents of two objects together into the first object. + + ```javascript + Ember.merge({first: 'Tom'}, {last: 'Dale'}); // {first: 'Tom', last: 'Dale'} + var a = {first: 'Yehuda'}, b = {last: 'Katz'}; + Ember.merge(a, b); // a == {first: 'Yehuda', last: 'Katz'}, b == {last: 'Katz'} + ``` + + @method merge + @for Ember + @param {Object} original The object to merge into + @param {Object} updates The object to copy properties from + @return {Object} +*/ Ember.merge = function(original, updates) { for (var prop in updates) { if (!updates.hasOwnProperty(prop)) { continue; } @@ -761,6 +894,12 @@ var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.index return -1; }; +/** + Array polyfills to support ES5 features in older browsers. + + @namespace Ember + @property ArrayPolyfills +*/ Ember.ArrayPolyfills = { map: arrayMap, forEach: arrayForEach, @@ -1174,10 +1313,18 @@ function canInvoke(obj, methodName) { /** Checks to see if the `methodName` exists on the `obj`. + ```javascript + var foo = {bar: Ember.K, baz: null}; + Ember.canInvoke(foo, 'bar'); // true + Ember.canInvoke(foo, 'baz'); // false + Ember.canInvoke(foo, 'bat'); // false + ``` + @method canInvoke @for Ember @param {Object} obj The object to check for the method @param {String} methodName The method name to check for + @return {Boolean} */ Ember.canInvoke = canInvoke; @@ -1185,6 +1332,13 @@ Ember.canInvoke = canInvoke; Checks to see if the `methodName` exists on the `obj`, and if it does, invokes it with the arguments passed. + ```javascript + var d = new Date('03/15/2013'); + Ember.tryInvoke(d, 'getTime'); // 1363320000000 + Ember.tryInvoke(d, 'setFullYear', [2014]); // 1394856000000 + Ember.tryInvoke(d, 'noSuchMethod', [2014]); // undefined + ``` + @method tryInvoke @for Ember @param {Object} obj The object to check for the method @@ -1216,6 +1370,17 @@ var needsFinallyFix = (function() { Provides try { } finally { } functionality, while working around Safari's double finally bug. + ```javascript + var tryable = function() { + someResource.lock(); + runCallback(); // May throw error. + }; + var finalizer = function() { + someResource.unlock(); + }; + Ember.tryFinally(tryable, finalizer); + ``` + @method tryFinally @for Ember @param {Function} tryable The function to run the try callback @@ -1266,6 +1431,30 @@ if (needsFinallyFix) { Provides try { } catch finally { } functionality, while working around Safari's double finally bug. + ```javascript + var tryable = function() { + for (i=0, l=listeners.length; i size ? size : ends; + if (count <= 0) { count = 0; } + + chunk = args.splice(0, size); + chunk = [start, count].concat(chunk); + + start += size; + ends -= count; + + ret = ret.concat(splice.apply(array, chunk)); + } + return ret; + }, + replace: function(array, idx, amt, objects) { if (array.replace) { return array.replace(idx, amt, objects); } else { - var args = concat.apply([idx, amt], objects); - return array.splice.apply(array, args); + return utils._replace(array, idx, amt, objects); } }, @@ -2296,7 +2536,7 @@ var metaFor = Ember.meta, @param {String} keyName The property key (or path) that will change. @return {void} */ -var propertyWillChange = Ember.propertyWillChange = function(obj, keyName) { +function propertyWillChange(obj, keyName) { var m = metaFor(obj, false), watching = m.watching[keyName] > 0 || keyName === 'length', proto = m.proto, @@ -2308,7 +2548,8 @@ var propertyWillChange = Ember.propertyWillChange = function(obj, keyName) { dependentKeysWillChange(obj, keyName, m); chainsWillChange(obj, keyName, m); notifyBeforeObservers(obj, keyName); -}; +} +Ember.propertyWillChange = propertyWillChange; /** This function is called just after an object property has changed. @@ -2316,7 +2557,7 @@ var propertyWillChange = Ember.propertyWillChange = function(obj, keyName) { Normally you will not need to call this method directly but if for some reason you can't directly watch a property you can invoke this method - manually along with `Ember.propertyWilLChange()` which you should call just + manually along with `Ember.propertyWillChange()` which you should call just before the property value changes. @method propertyDidChange @@ -2325,7 +2566,7 @@ var propertyWillChange = Ember.propertyWillChange = function(obj, keyName) { @param {String} keyName The property key (or path) that will change. @return {void} */ -var propertyDidChange = Ember.propertyDidChange = function(obj, keyName) { +function propertyDidChange(obj, keyName) { var m = metaFor(obj, false), watching = m.watching[keyName] > 0 || keyName === 'length', proto = m.proto, @@ -2338,9 +2579,10 @@ var propertyDidChange = Ember.propertyDidChange = function(obj, keyName) { if (!watching && keyName !== 'length') { return; } dependentKeysDidChange(obj, keyName, m); - chainsDidChange(obj, keyName, m); + chainsDidChange(obj, keyName, m, false); notifyObservers(obj, keyName); -}; +} +Ember.propertyDidChange = propertyDidChange; var WILL_SEEN, DID_SEEN; @@ -2381,35 +2623,47 @@ function iterDeps(method, obj, depKey, seen, meta) { } } -var chainsWillChange = function(obj, keyName, m, arg) { - if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do - - var nodes = m.chainWatchers; - - nodes = nodes[keyName]; - if (!nodes) { return; } - - nodes = nodes.slice(); - - for(var i = 0, l = nodes.length; i < l; i++) { - nodes[i].willChange(arg); +function chainsWillChange(obj, keyName, m) { + if (!(m.hasOwnProperty('chainWatchers') && + m.chainWatchers[keyName])) { + return; } -}; -var chainsDidChange = function(obj, keyName, m, arg) { - if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do + var nodes = m.chainWatchers[keyName], + events = [], + i, l; - var nodes = m.chainWatchers; - - nodes = nodes[keyName]; - if (!nodes) { return; } - - nodes = nodes.slice(); - - for(var i = 0, l = nodes.length; i < l; i++) { - nodes[i].didChange(arg); + for(i = 0, l = nodes.length; i < l; i++) { + nodes[i].willChange(events); } -}; + + for (i = 0, l = events.length; i < l; i += 2) { + propertyWillChange(events[i], events[i+1]); + } +} + +function chainsDidChange(obj, keyName, m, suppressEvents) { + if (!(m.hasOwnProperty('chainWatchers') && + m.chainWatchers[keyName])) { + return; + } + + var nodes = m.chainWatchers[keyName], + events = suppressEvents ? null : [], + i, l; + + for(i = 0, l = nodes.length; i < l; i++) { + nodes[i].didChange(events); + } + + if (suppressEvents) { + return; + } + + for (i = 0, l = events.length; i < l; i += 2) { + propertyDidChange(events[i], events[i+1]); + } +} Ember.overrideChains = function(obj, keyName, m) { chainsDidChange(obj, keyName, m, true); @@ -2419,20 +2673,24 @@ Ember.overrideChains = function(obj, keyName, m) { @method beginPropertyChanges @chainable */ -var beginPropertyChanges = Ember.beginPropertyChanges = function() { +function beginPropertyChanges() { deferred++; -}; +} + +Ember.beginPropertyChanges = beginPropertyChanges; /** @method endPropertyChanges */ -var endPropertyChanges = Ember.endPropertyChanges = function() { +function endPropertyChanges() { deferred--; if (deferred<=0) { beforeObserverSet.clear(); observerSet.flush(); } -}; +} + +Ember.endPropertyChanges = endPropertyChanges; /** Make a series of property changes together in an @@ -2454,7 +2712,7 @@ Ember.changeProperties = function(cb, binding) { tryFinally(cb, endPropertyChanges, binding); }; -var notifyBeforeObservers = function(obj, keyName) { +function notifyBeforeObservers(obj, keyName) { if (obj.isDestroying) { return; } var eventName = keyName + ':before', listeners, diff; @@ -2465,9 +2723,9 @@ var notifyBeforeObservers = function(obj, keyName) { } else { sendEvent(obj, eventName, [obj, keyName]); } -}; +} -var notifyObservers = function(obj, keyName) { +function notifyObservers(obj, keyName) { if (obj.isDestroying) { return; } var eventName = keyName + ':change', listeners; @@ -2477,7 +2735,7 @@ var notifyObservers = function(obj, keyName) { } else { sendEvent(obj, eventName, [obj, keyName]); } -}; +} })(); @@ -2496,7 +2754,7 @@ var META_KEY = Ember.META_KEY, /** Sets the value of a property on an object, respecting computed properties and notifying observers and other listeners of the change. If the - property is not defined but the object implements the `unknownProperty` + property is not defined but the object implements the `setUnknownProperty` method then that will be invoked as well. If you plan to run on IE8 and older browsers then you should use this @@ -2506,7 +2764,7 @@ var META_KEY = Ember.META_KEY, On all newer browsers, you only need to use this method to set properties if the property might not be defined on the object and you want - to respect the `unknownProperty` handler. Otherwise you can ignore this + to respect the `setUnknownProperty` handler. Otherwise you can ignore this method. @method set @@ -3165,6 +3423,47 @@ Ember.defineProperty = function(obj, keyName, desc, data, meta) { +(function() { +var get = Ember.get; + +/** + To get multiple properties at once, call `Ember.getProperties` + with an object followed by a list of strings or an array: + + ```javascript + Ember.getProperties(record, 'firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } + ``` + + is equivalent to: + + ```javascript + Ember.getProperties(record, ['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } + ``` + + @method getProperties + @param obj + @param {String...|Array} list of keys to get + @return {Hash} +*/ +Ember.getProperties = function(obj) { + var ret = {}, + propertyNames = arguments, + i = 1; + + if (arguments.length === 2 && Ember.typeOf(arguments[1]) === 'array') { + i = 0; + propertyNames = arguments[1]; + } + for(var len = propertyNames.length; i < len; i++) { + ret[propertyNames[i]] = get(obj, propertyNames[i]); + } + return ret; +}; + +})(); + + + (function() { var changeProperties = Ember.changeProperties, set = Ember.set; @@ -3174,6 +3473,14 @@ var changeProperties = Ember.changeProperties, a single `beginPropertyChanges` and `endPropertyChanges` batch, so observers will be buffered. + ```javascript + anObject.setProperties({ + firstName: "Stanley", + lastName: "Stuart", + age: "21" + }) + ``` + @method setProperties @param self @param {Object} hash @@ -3263,8 +3570,6 @@ var metaFor = Ember.meta, // utils.js warn = Ember.warn, watchKey = Ember.watchKey, unwatchKey = Ember.unwatchKey, - propertyWillChange = Ember.propertyWillChange, - propertyDidChange = Ember.propertyDidChange, FIRST_KEY = /^([^\.\*]+)/; function firstKey(path) { @@ -3498,42 +3803,50 @@ ChainNodePrototype.unchain = function(key, path) { }; -ChainNodePrototype.willChange = function() { +ChainNodePrototype.willChange = function(events) { var chains = this._chains; if (chains) { for(var key in chains) { if (!chains.hasOwnProperty(key)) { continue; } - chains[key].willChange(); + chains[key].willChange(events); } } - if (this._parent) { this._parent.chainWillChange(this, this._key, 1); } + if (this._parent) { this._parent.chainWillChange(this, this._key, 1, events); } }; -ChainNodePrototype.chainWillChange = function(chain, path, depth) { +ChainNodePrototype.chainWillChange = function(chain, path, depth, events) { if (this._key) { path = this._key + '.' + path; } if (this._parent) { - this._parent.chainWillChange(this, path, depth+1); + this._parent.chainWillChange(this, path, depth+1, events); } else { - if (depth > 1) { propertyWillChange(this.value(), path); } + if (depth > 1) { + events.push(this.value(), path); + } path = 'this.' + path; - if (this._paths[path] > 0) { propertyWillChange(this.value(), path); } + if (this._paths[path] > 0) { + events.push(this.value(), path); + } } }; -ChainNodePrototype.chainDidChange = function(chain, path, depth) { +ChainNodePrototype.chainDidChange = function(chain, path, depth, events) { if (this._key) { path = this._key + '.' + path; } if (this._parent) { - this._parent.chainDidChange(this, path, depth+1); + this._parent.chainDidChange(this, path, depth+1, events); } else { - if (depth > 1) { propertyDidChange(this.value(), path); } + if (depth > 1) { + events.push(this.value(), path); + } path = 'this.' + path; - if (this._paths[path] > 0) { propertyDidChange(this.value(), path); } + if (this._paths[path] > 0) { + events.push(this.value(), path); + } } }; -ChainNodePrototype.didChange = function(suppressEvent) { +ChainNodePrototype.didChange = function(events) { // invalidate my own value first. if (this._watching) { var obj = this._parent.value(); @@ -3555,14 +3868,15 @@ ChainNodePrototype.didChange = function(suppressEvent) { if (chains) { for(var key in chains) { if (!chains.hasOwnProperty(key)) { continue; } - chains[key].didChange(suppressEvent); + chains[key].didChange(events); } } - if (suppressEvent) { return; } + // if no events are passed in then we only care about the above wiring update + if (events === null) { return; } // and finally tell parent about my path changing... - if (this._parent) { this._parent.chainDidChange(this, this._key, 1); } + if (this._parent) { this._parent.chainDidChange(this, this._key, 1, events); } }; Ember.finishChains = function(obj) { @@ -3571,9 +3885,10 @@ Ember.finishChains = function(obj) { if (chains.value() !== obj) { m.chains = chains = chains.copy(obj); } - chains.didChange(true); + chains.didChange(null); } }; + })(); @@ -3854,6 +4169,81 @@ function removeDependentKeys(desc, obj, keyName, meta) { // /** + A computed property transforms an objects function into a property. + + By default the function backing the computed property will only be called + once and the result will be cached. You can specify various properties + that your computed property is dependent on. This will force the cached + result to be recomputed if the dependencies are modified. + + In the following example we declare a computed property (by calling + `.property()` on the fullName function) and setup the properties + dependencies (depending on firstName and lastName). The fullName function + will be called once (regardless of how many times it is accessed) as long + as it's dependencies have not been changed. Once firstName or lastName are updated + any future calls (or anything bound) to fullName will incorporate the new + values. + + ```javascript + Person = Ember.Object.extend({ + // these will be supplied by `create` + firstName: null, + lastName: null, + + fullName: function() { + var firstName = this.get('firstName'); + var lastName = this.get('lastName'); + + return firstName + ' ' + lastName; + }.property('firstName', 'lastName') + }); + + var tom = Person.create({ + firstName: "Tom", + lastName: "Dale" + }); + + tom.get('fullName') // "Tom Dale" + ``` + + You can also define what Ember should do when setting a computed property. + If you try to set a computed property, it will be invoked with the key and + value you want to set it to. You can also accept the previous value as the + third parameter. + + ```javascript + + Person = Ember.Object.extend({ + // these will be supplied by `create` + firstName: null, + lastName: null, + + fullName: function(key, value, oldValue) { + // getter + if (arguments.length === 1) { + var firstName = this.get('firstName'); + var lastName = this.get('lastName'); + + return firstName + ' ' + lastName; + + // setter + } else { + var name = value.split(" "); + + this.set('firstName', name[0]); + this.set('lastName', name[1]); + + return value; + } + }.property('firstName', 'lastName') + }); + + var person = Person.create(); + person.set('fullName', "Peter Wagenet"); + person.get('firstName') // Peter + person.get('lastName') // Wagenet + ``` + @class ComputedProperty @namespace Ember @extends Ember.Descriptor @@ -3872,7 +4262,7 @@ ComputedProperty.prototype = new Ember.Descriptor(); var ComputedPropertyPrototype = ComputedProperty.prototype; -/* +/** Properties are cacheable by default. Computed property will automatically cache the return value of your function until one of the dependent keys changes. @@ -4014,11 +4404,37 @@ ComputedPropertyPrototype.didChange = function(obj, keyName) { function finishChains(chainNodes) { for (var i=0, l=chainNodes.length; i= 0) || + if ((concats && a_indexOf.call(concats, key) >= 0) || key === 'concatenatedProperties' || key === 'mergedProperties') { value = applyConcatenatedProperties(base, key, value, values); } else if ((mergings && a_indexOf.call(mergings, key) >= 0)) { value = applyMergedProperties(base, key, value, values); + } else if (isMethod(value)) { + value = giveMethodSuper(base, key, value, values, descs); } descs[key] = undefined; @@ -6370,6 +7072,7 @@ function mergeMixins(mixins, m, descs, values, base, keys) { if (props) { meta = Ember.meta(base); + if (base.willMergeMixin) { base.willMergeMixin(props); } concats = concatenatedMixinProperties('concatenatedProperties', props, values, base); mergings = concatenatedMixinProperties('mergedProperties', props, values, base); @@ -6543,7 +7246,7 @@ Ember.mixin = function(obj) { // Mix mixins into classes by passing them as the first arguments to // .extend. App.CommentView = Ember.View.extend(App.Editable, { - template: Ember.Handlebars.compile('{{#if isEditing}}...{{else}}...{{/if}}') + template: Ember.Handlebars.compile('{{#if view.isEditing}}...{{else}}...{{/if}}') }); commentView = App.CommentView.create(); @@ -6553,6 +7256,31 @@ Ember.mixin = function(obj) { Note that Mixins are created with `Ember.Mixin.create`, not `Ember.Mixin.extend`. + Note that mixins extend a constructor's prototype so arrays and object literals + defined as properties will be shared amongst objects that implement the mixin. + If you want to define an property in a mixin that is not shared, you can define + it either as a computed property or have it be created on initialization of the object. + + ```javascript + //filters array will be shared amongst any object implementing mixin + App.Filterable = Ember.Mixin.create({ + filters: Ember.A() + }); + + //filters will be a separate array for every object implementing the mixin + App.Filterable = Ember.Mixin.create({ + filters: Ember.computed(function(){return Ember.A();}) + }); + + //filters will be created as a separate array during the object's initialization + App.Filterable = Ember.Mixin.create({ + init: function() { + this._super(); + this.set("filters", Ember.A()); + } + }); + ``` + @class Mixin @namespace Ember */ @@ -6788,6 +7516,22 @@ Ember.aliasMethod = function(methodName) { // /** + Specify a method that observes property changes. + + ```javascript + Ember.Object.extend({ + valueObserver: Ember.observer(function() { + // Executes whenever the "value" property changes + }, 'value') + }); + ``` + + In the future this method may become asynchronous. If you want to ensure + synchronous behavior, use `immediateObserver`. + + Also available as `Function.prototype.observes` if prototype extensions are + enabled. + @method observer @for Ember @param {Function} func @@ -6800,9 +7544,23 @@ Ember.observer = function(func) { return func; }; -// If observers ever become asynchronous, Ember.immediateObserver -// must remain synchronous. /** + Specify a method that observes property changes. + + ```javascript + Ember.Object.extend({ + valueObserver: Ember.immediateObserver(function() { + // Executes whenever the "value" property changes + }, 'value') + }); + ``` + + In the future, `Ember.observer` may become asynchronous. In this event, + `Ember.immediateObserver` will maintain the synchronous behavior. + + Also available as `Function.prototype.observesImmediately` if prototype extensions are + enabled. + @method immediateObserver @for Ember @param {Function} func @@ -6830,24 +7588,31 @@ Ember.immediateObserver = function() { ```javascript App.PersonView = Ember.View.extend({ + friends: [{ name: 'Tom' }, { name: 'Stefan' }, { name: 'Kris' }], - valueWillChange: function (obj, keyName) { + + valueWillChange: Ember.beforeObserver(function(obj, keyName) { this.changingFrom = obj.get(keyName); - }.observesBefore('content.value'), - valueDidChange: function(obj, keyName) { + }, 'content.value'), + + valueDidChange: Ember.observer(function(obj, keyName) { // only run if updating a value already in the DOM if (this.get('state') === 'inDOM') { - var color = obj.get(keyName) > this.changingFrom ? 'green' : 'red'; - // logic + var color = obj.get(keyName) > this.changingFrom ? 'green' : 'red'; + // logic } - }.observes('content.value'), - friendsDidChange: function(obj, keyName) { + }, 'content.value'), + + friendsDidChange: Ember.observer(function(obj, keyName) { // some logic // obj.get(keyName) returns friends array - }.observes('friends.@each.name') + }, 'friends.@each.name') }); ``` + Also available as `Function.prototype.observesBefore` if prototype extensions are + enabled. + @method beforeObserver @for Ember @param {Function} func @@ -6864,6 +7629,52 @@ Ember.beforeObserver = function(func) { +(function() { +// Provides a way to register library versions with ember. + +Ember.libraries = function() { + var libraries = []; + var coreLibIndex = 0; + + var getLibrary = function(name) { + for (var i = 0; i < libraries.length; i++) { + if (libraries[i].name === name) { + return libraries[i]; + } + } + }; + + libraries.register = function(name, version) { + if (!getLibrary(name)) { + libraries.push({name: name, version: version}); + } + }; + + libraries.registerCoreLibrary = function(name, version) { + if (!getLibrary(name)) { + libraries.splice(coreLibIndex++, 0, {name: name, version: version}); + } + }; + + libraries.deRegister = function(name) { + var lib = getLibrary(name); + if (lib) libraries.splice(libraries.indexOf(lib), 1); + }; + + libraries.each = function (callback) { + libraries.forEach(function(lib) { + callback(lib.name, lib.version); + }); + }; + return libraries; +}(); + +Ember.libraries.registerCoreLibrary('Ember', Ember.VERSION); + +})(); + + + (function() { /** Ember Metal @@ -6931,6 +7742,7 @@ define("rsvp/async", var browserGlobal = (typeof window !== 'undefined') ? window : {}; var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; var async; + var local = (typeof global !== 'undefined') ? global : this; // old node function useNextTick() { @@ -6981,7 +7793,7 @@ define("rsvp/async", function useSetTimeout() { return function(callback, arg) { - setTimeout(function() { + local.setTimeout(function() { callback(arg); }, 1); }; @@ -7364,6 +8176,10 @@ define("rsvp/promise", }); return thenPromise; + }, + + fail: function(fail) { + return this.then(null, fail); } }; @@ -7466,19 +8282,36 @@ define("rsvp/resolve", __exports__.resolve = resolve; }); +define("rsvp/rethrow", + ["exports"], + function(__exports__) { + "use strict"; + var local = (typeof global === "undefined") ? this : global; + + function rethrow(reason) { + local.setTimeout(function() { + throw reason; + }); + throw reason; + } + + + __exports__.rethrow = rethrow; + }); define("rsvp", - ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { + ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/rethrow","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) { "use strict"; var EventTarget = __dependency1__.EventTarget; var Promise = __dependency2__.Promise; var denodeify = __dependency3__.denodeify; var all = __dependency4__.all; var hash = __dependency5__.hash; - var defer = __dependency6__.defer; - var config = __dependency7__.config; - var resolve = __dependency8__.resolve; - var reject = __dependency9__.reject; + var rethrow = __dependency6__.rethrow; + var defer = __dependency7__.defer; + var config = __dependency8__.config; + var resolve = __dependency9__.resolve; + var reject = __dependency10__.reject; function configure(name, value) { config[name] = value; @@ -7489,16 +8322,30 @@ define("rsvp", __exports__.EventTarget = EventTarget; __exports__.all = all; __exports__.hash = hash; + __exports__.rethrow = rethrow; __exports__.defer = defer; __exports__.denodeify = denodeify; __exports__.configure = configure; __exports__.resolve = resolve; __exports__.reject = reject; }); - })(); (function() { +/** +@private +Public api for the container is still in flux. +The public api, specified on the application namespace should be considered the stable api. +// @module container +*/ + +/* + Flag to enable/disable model factory injections (disabled by default) + If model factory injections are enabled, models should not be + accessed globally (only through `container.lookupFactory('model:modelName'))`); +*/ +Ember.MODEL_FACTORY_INJECTIONS = false || !!Ember.ENV.MODEL_FACTORY_INJECTIONS; + define("container", [], function() { @@ -7506,7 +8353,7 @@ define("container", /** A safe and simple inheriting object. - @class InheritingDict + // @class InheritingDict */ function InheritingDict(parent) { this.parent = parent; @@ -7580,7 +8427,7 @@ define("container", @method has @param {String} key - @returns {Boolean} + @return {Boolean} */ has: function(key) { var dict = this.dict; @@ -7617,7 +8464,10 @@ define("container", /** A lightweight container that helps to assemble and decouple components. - @class Container + @private + Public api for the container is still in flux. + The public api, specified on the application namespace should be considered the stable api. + // @class Container */ function Container(parent) { this.parent = parent; @@ -7707,7 +8557,7 @@ define("container", to correctly inherit from the current container. @method child - @returns {Container} + @return {Container} */ child: function() { var container = new Container(this); @@ -7777,6 +8627,7 @@ define("container", container.unregister('model:user') container.lookup('model:user') === undefined //=> true + ``` @method unregister @param {String} fullName @@ -7786,6 +8637,7 @@ define("container", this.registry.remove(normalizedName); this.cache.remove(normalizedName); + this.factoryCache.remove(normalizedName); this._options.remove(normalizedName); }, @@ -7819,7 +8671,7 @@ define("container", @method resolve @param {String} fullName - @returns {Function} fullName's factory + @return {Function} fullName's factory */ resolve: function(fullName) { return this.resolver(fullName) || this.registry.get(fullName); @@ -7850,6 +8702,17 @@ define("container", return fullName; }, + /** + @method makeToString + + @param {any} factory + @param {string} fullNae + @return {function} toString function + */ + makeToString: function(factory, fullName) { + return factory.toString(); + }, + /** Given a fullName return a corresponding instance. @@ -7978,7 +8841,7 @@ define("container", this.optionsForType(type, options); }, - /* + /** @private Used only via `injection`. @@ -8020,7 +8883,7 @@ define("container", addTypeInjection(this.typeInjections, type, property, fullName); }, - /* + /** Defines injection rules. These rules are used to inject dependencies onto objects when they @@ -8028,8 +8891,8 @@ define("container", Two forms of injections are possible: - * Injecting one fullName on another fullName - * Injecting one fullName on a type + * Injecting one fullName on another fullName + * Injecting one fullName on a type Example: @@ -8075,7 +8938,7 @@ define("container", }, - /* + /** @private Used only via `factoryInjection`. @@ -8112,7 +8975,7 @@ define("container", addTypeInjection(this.factoryTypeInjections, type, property, fullName); }, - /* + /** Defines factory injection rules. Similar to regular injection rules, but are run against factories, via @@ -8123,8 +8986,8 @@ define("container", Two forms of injections are possible: - * Injecting one fullName on another fullName - * Injecting one fullName on a type + * Injecting one fullName on another fullName + * Injecting one fullName on a type Example: @@ -8256,6 +9119,7 @@ define("container", var factory = container.resolve(name); var injectedFactory; var cache = container.factoryCache; + var type = fullName.split(":")[0]; if (!factory) { return; } @@ -8263,13 +9127,19 @@ define("container", return cache.get(fullName); } - if (typeof factory.extend !== 'function') { + if (typeof factory.extend !== 'function' || (!Ember.MODEL_FACTORY_INJECTIONS && type === 'model')) { // TODO: think about a 'safe' merge style extension // for now just fallback to create time injection return factory; } else { - injectedFactory = factory.extend(injectionsFor(container, fullName)); - injectedFactory.reopenClass(factoryInjectionsFor(container, fullName)); + + var injections = injectionsFor(container, fullName); + var factoryInjections = factoryInjectionsFor(container, fullName); + + factoryInjections._toString = container.makeToString(factory, fullName); + + injectedFactory = factory.extend(injections); + injectedFactory.reopenClass(factoryInjections); cache.set(fullName, injectedFactory); @@ -8676,557 +9546,6 @@ Ember.Error.prototype = Ember.create(Error.prototype); -(function() { -/** - Expose RSVP implementation - - Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md - - @class RSVP - @namespace Ember - @constructor -*/ -Ember.RSVP = requireModule('rsvp'); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -var STRING_DASHERIZE_REGEXP = (/[ _]/g); -var STRING_DASHERIZE_CACHE = {}; -var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g); -var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g); -var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); -var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g); - -/** - Defines the hash of localized strings for the current language. Used by - the `Ember.String.loc()` helper. To localize, add string values to this - hash. - - @property STRINGS - @for Ember - @type Hash -*/ -Ember.STRINGS = {}; - -/** - Defines string helper methods including string formatting and localization. - Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be - added to the `String.prototype` as well. - - @class String - @namespace Ember - @static -*/ -Ember.String = { - - /** - Apply formatting options to the string. This will look for occurrences - of "%@" in your string and substitute them with the arguments you pass into - this method. If you want to control the specific order of replacement, - you can add a number after the key as well to indicate which argument - you want to insert. - - Ordered insertions are most useful when building loc strings where values - you need to insert may appear in different orders. - - ```javascript - "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe" - "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John" - ``` - - @method fmt - @param {String} str The string to format - @param {Array} formats An array of parameters to interpolate into string. - @return {String} formatted string - */ - fmt: function(str, formats) { - // first, replace any ORDERED replacements. - var idx = 0; // the current index for non-numerical replacements - return str.replace(/%@([0-9]+)?/g, function(s, argIndex) { - argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++; - s = formats[argIndex]; - return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s); - }) ; - }, - - /** - Formats the passed string, but first looks up the string in the localized - strings hash. This is a convenient way to localize text. See - `Ember.String.fmt()` for more information on formatting. - - Note that it is traditional but not required to prefix localized string - keys with an underscore or other character so you can easily identify - localized strings. - - ```javascript - Ember.STRINGS = { - '_Hello World': 'Bonjour le monde', - '_Hello %@ %@': 'Bonjour %@ %@' - }; - - Ember.String.loc("_Hello World"); // 'Bonjour le monde'; - Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith"; - ``` - - @method loc - @param {String} str The string to format - @param {Array} formats Optional array of parameters to interpolate into string. - @return {String} formatted string - */ - loc: function(str, formats) { - str = Ember.STRINGS[str] || str; - return Ember.String.fmt(str, formats) ; - }, - - /** - Splits a string into separate units separated by spaces, eliminating any - empty strings in the process. This is a convenience method for split that - is mostly useful when applied to the `String.prototype`. - - ```javascript - Ember.String.w("alpha beta gamma").forEach(function(key) { - console.log(key); - }); - - // > alpha - // > beta - // > gamma - ``` - - @method w - @param {String} str The string to split - @return {String} split string - */ - w: function(str) { return str.split(/\s+/); }, - - /** - Converts a camelized string into all lower case separated by underscores. - - ```javascript - 'innerHTML'.decamelize(); // 'inner_html' - 'action_name'.decamelize(); // 'action_name' - 'css-class-name'.decamelize(); // 'css-class-name' - 'my favorite items'.decamelize(); // 'my favorite items' - ``` - - @method decamelize - @param {String} str The string to decamelize. - @return {String} the decamelized string. - */ - decamelize: function(str) { - return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase(); - }, - - /** - Replaces underscores or spaces with dashes. - - ```javascript - 'innerHTML'.dasherize(); // 'inner-html' - 'action_name'.dasherize(); // 'action-name' - 'css-class-name'.dasherize(); // 'css-class-name' - 'my favorite items'.dasherize(); // 'my-favorite-items' - ``` - - @method dasherize - @param {String} str The string to dasherize. - @return {String} the dasherized string. - */ - dasherize: function(str) { - var cache = STRING_DASHERIZE_CACHE, - hit = cache.hasOwnProperty(str), - ret; - - if (hit) { - return cache[str]; - } else { - ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-'); - cache[str] = ret; - } - - return ret; - }, - - /** - Returns the lowerCamelCase form of a string. - - ```javascript - 'innerHTML'.camelize(); // 'innerHTML' - 'action_name'.camelize(); // 'actionName' - 'css-class-name'.camelize(); // 'cssClassName' - 'my favorite items'.camelize(); // 'myFavoriteItems' - 'My Favorite Items'.camelize(); // 'myFavoriteItems' - ``` - - @method camelize - @param {String} str The string to camelize. - @return {String} the camelized string. - */ - camelize: function(str) { - return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) { - return chr ? chr.toUpperCase() : ''; - }).replace(/^([A-Z])/, function(match, separator, chr) { - return match.toLowerCase(); - }); - }, - - /** - Returns the UpperCamelCase form of a string. - - ```javascript - 'innerHTML'.classify(); // 'InnerHTML' - 'action_name'.classify(); // 'ActionName' - 'css-class-name'.classify(); // 'CssClassName' - 'my favorite items'.classify(); // 'MyFavoriteItems' - ``` - - @method classify - @param {String} str the string to classify - @return {String} the classified string - */ - classify: function(str) { - var parts = str.split("."), - out = []; - - for (var i=0, l=parts.length; i Ember.TrackedArray instances. We use + // this to lazily recompute indexes for item property observers. + this.trackedArraysByGuid = {}; + + // This is used to coalesce item changes from property observers. + this.changedItems = {}; +} + +function ItemPropertyObserverContext (dependentArray, index, trackedArray) { + Ember.assert("Internal error: trackedArray is null or undefined", trackedArray); + + this.dependentArray = dependentArray; + this.index = index; + this.item = dependentArray.objectAt(index); + this.trackedArray = trackedArray; + this.beforeObserver = null; + this.observer = null; +} + +DependentArraysObserver.prototype = { + setValue: function (newValue) { + this.instanceMeta.setValue(newValue); + }, + getValue: function () { + return this.instanceMeta.getValue(); + }, + + setupObservers: function (dependentArray, dependentKey) { + Ember.assert("dependent array must be an `Ember.Array`", Ember.Array.detect(dependentArray)); + + this.dependentKeysByGuid[guidFor(dependentArray)] = dependentKey; + + dependentArray.addArrayObserver(this, { + willChange: 'dependentArrayWillChange', + didChange: 'dependentArrayDidChange' + }); + + if (this.cp._itemPropertyKeys[dependentKey]) { + this.setupPropertyObservers(dependentKey, this.cp._itemPropertyKeys[dependentKey]); + } + }, + + teardownObservers: function (dependentArray, dependentKey) { + var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || []; + + delete this.dependentKeysByGuid[guidFor(dependentArray)]; + + this.teardownPropertyObservers(dependentKey, itemPropertyKeys); + + dependentArray.removeArrayObserver(this, { + willChange: 'dependentArrayWillChange', + didChange: 'dependentArrayDidChange' + }); + }, + + setupPropertyObservers: function (dependentKey, itemPropertyKeys) { + var dependentArray = get(this.instanceMeta.context, dependentKey), + length = get(dependentArray, 'length'), + observerContexts = new Array(length); + + this.resetTransformations(dependentKey, observerContexts); + + forEach(dependentArray, function (item, index) { + var observerContext = this.createPropertyObserverContext(dependentArray, index, this.trackedArraysByGuid[dependentKey]); + observerContexts[index] = observerContext; + + forEach(itemPropertyKeys, function (propertyKey) { + addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver); + addObserver(item, propertyKey, this, observerContext.observer); + }, this); + }, this); + }, + + teardownPropertyObservers: function (dependentKey, itemPropertyKeys) { + var dependentArrayObserver = this, + trackedArray = this.trackedArraysByGuid[dependentKey], + beforeObserver, + observer, + item; + + if (!trackedArray) { return; } + + trackedArray.apply(function (observerContexts, offset, operation) { + if (operation === Ember.TrackedArray.DELETE) { return; } + + forEach(observerContexts, function (observerContext) { + beforeObserver = observerContext.beforeObserver; + observer = observerContext.observer; + item = observerContext.item; + + forEach(itemPropertyKeys, function (propertyKey) { + removeBeforeObserver(item, propertyKey, dependentArrayObserver, beforeObserver); + removeObserver(item, propertyKey, dependentArrayObserver, observer); + }); + }); + }); + }, + + createPropertyObserverContext: function (dependentArray, index, trackedArray) { + var observerContext = new ItemPropertyObserverContext(dependentArray, index, trackedArray); + + this.createPropertyObserver(observerContext); + + return observerContext; + }, + + createPropertyObserver: function (observerContext) { + var dependentArrayObserver = this; + + observerContext.beforeObserver = function (obj, keyName) { + dependentArrayObserver.updateIndexes(observerContext.trackedArray, observerContext.dependentArray); + return dependentArrayObserver.itemPropertyWillChange(obj, keyName, observerContext.dependentArray, observerContext.index); + }; + observerContext.observer = function (obj, keyName) { + return dependentArrayObserver.itemPropertyDidChange(obj, keyName, observerContext.dependentArray, observerContext.index); + }; + }, + + resetTransformations: function (dependentKey, observerContexts) { + this.trackedArraysByGuid[dependentKey] = new Ember.TrackedArray(observerContexts); + }, + + addTransformation: function (dependentKey, index, newItems) { + var trackedArray = this.trackedArraysByGuid[dependentKey]; + if (trackedArray) { + trackedArray.addItems(index, newItems); + } + }, + + removeTransformation: function (dependentKey, index, removedCount) { + var trackedArray = this.trackedArraysByGuid[dependentKey]; + + if (trackedArray) { + return trackedArray.removeItems(index, removedCount); + } + + return []; + }, + + updateIndexes: function (trackedArray, array) { + var length = get(array, 'length'); + // OPTIMIZE: we could stop updating once we hit the object whose observer + // fired; ie partially apply the transformations + trackedArray.apply(function (observerContexts, offset, operation) { + // we don't even have observer contexts for removed items, even if we did, + // they no longer have any index in the array + if (operation === Ember.TrackedArray.DELETE) { return; } + if (operation === Ember.TrackedArray.RETAIN && observerContexts.length === length && offset === 0) { + // If we update many items we don't want to walk the array each time: we + // only need to update the indexes at most once per run loop. + return; + } + + forEach(observerContexts, function (context, index) { + context.index = index + offset; + }); + }); + }, + + dependentArrayWillChange: function (dependentArray, index, removedCount, addedCount) { + var removedItem = this.callbacks.removedItem, + changeMeta, + guid = guidFor(dependentArray), + dependentKey = this.dependentKeysByGuid[guid], + itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [], + item, + itemIndex, + sliceIndex, + observerContexts; + + observerContexts = this.removeTransformation(dependentKey, index, removedCount); + + + function removeObservers(propertyKey) { + removeBeforeObserver(item, propertyKey, this, observerContexts[sliceIndex].beforeObserver); + removeObserver(item, propertyKey, this, observerContexts[sliceIndex].observer); + } + + for (sliceIndex = removedCount - 1; sliceIndex >= 0; --sliceIndex) { + itemIndex = index + sliceIndex; + item = dependentArray.objectAt(itemIndex); + + forEach(itemPropertyKeys, removeObservers, this); + + changeMeta = createChangeMeta(dependentArray, item, itemIndex, this.instanceMeta.propertyName, this.cp); + this.setValue( removedItem.call( + this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); + } + }, + + dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) { + var addedItem = this.callbacks.addedItem, + guid = guidFor(dependentArray), + dependentKey = this.dependentKeysByGuid[guid], + observerContexts = new Array(addedCount), + itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey], + changeMeta, + observerContext; + + forEach(dependentArray.slice(index, index + addedCount), function (item, sliceIndex) { + if (itemPropertyKeys) { + observerContext = + observerContexts[sliceIndex] = + this.createPropertyObserverContext(dependentArray, index + sliceIndex, this.trackedArraysByGuid[dependentKey]); + forEach(itemPropertyKeys, function (propertyKey) { + addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver); + addObserver(item, propertyKey, this, observerContext.observer); + }, this); + } + + changeMeta = createChangeMeta(dependentArray, item, index + sliceIndex, this.instanceMeta.propertyName, this.cp); + this.setValue( addedItem.call( + this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); + }, this); + + this.addTransformation(dependentKey, index, observerContexts); + }, + + itemPropertyWillChange: function (obj, keyName, array, index) { + var guid = guidFor(obj); + + if (!this.changedItems[guid]) { + this.changedItems[guid] = { + array: array, + index: index, + obj: obj, + previousValues: {} + }; + } + + this.changedItems[guid].previousValues[keyName] = get(obj, keyName); + }, + + itemPropertyDidChange: function(obj, keyName, array, index) { + Ember.run.once(this, 'flushChanges'); + }, + + flushChanges: function() { + var changedItems = this.changedItems, key, c, changeMeta; + for (key in changedItems) { + c = changedItems[key]; + changeMeta = createChangeMeta(c.array, c.obj, c.index, this.instanceMeta.propertyName, this.cp, c.previousValues); + this.setValue( + this.callbacks.removedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta)); + this.setValue( + this.callbacks.addedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta)); + } + this.changedItems = {}; + } +}; + +function createChangeMeta(dependentArray, item, index, propertyName, property, previousValues) { + var meta = { + arrayChanged: dependentArray, + index: index, + item: item, + propertyName: propertyName, + property: property + }; + + if (previousValues) { + // previous values only available for item property changes + meta.previousValues = previousValues; + } + + return meta; +} + +function addItems (dependentArray, callbacks, cp, propertyName, meta) { + forEach(dependentArray, function (item, index) { + meta.setValue( callbacks.addedItem.call( + this, meta.getValue(), item, createChangeMeta(dependentArray, item, index, propertyName, cp), meta.sugarMeta)); + }, this); +} + +function reset(cp, propertyName) { + var callbacks = cp._callbacks(), + meta; + + if (cp._hasInstanceMeta(this, propertyName)) { + meta = cp._instanceMeta(this, propertyName); + meta.setValue(cp.resetValue(meta.getValue())); + } else { + meta = cp._instanceMeta(this, propertyName); + } + + if (cp.options.initialize) { + cp.options.initialize.call(this, meta.getValue(), { property: cp, propertyName: propertyName }, meta.sugarMeta); + } +} + +function ReduceComputedPropertyInstanceMeta(context, propertyName, initialValue) { + this.context = context; + this.propertyName = propertyName; + this.cache = metaFor(context).cache; + + this.dependentArrays = {}; + this.sugarMeta = {}; + + this.initialValue = initialValue; +} + +ReduceComputedPropertyInstanceMeta.prototype = { + getValue: function () { + if (this.propertyName in this.cache) { + return this.cache[this.propertyName]; + } else { + return this.initialValue; + } + }, + + setValue: function(newValue) { + // This lets sugars force a recomputation, handy for very simple + // implementations of eg max. + if (newValue !== undefined) { + this.cache[this.propertyName] = newValue; + } else { + delete this.cache[this.propertyName]; + } + } +}; + +/** + A computed property whose dependent keys are arrays and which is updated with + "one at a time" semantics. + + @class ReduceComputedProperty + @namespace Ember + @extends Ember.ComputedProperty + @constructor +*/ +function ReduceComputedProperty(options) { + var cp = this; + + this.options = options; + this._instanceMetas = {}; + + this._dependentKeys = null; + // A map of dependentKey -> [itemProperty, ...] that tracks what properties of + // items in the array we must track to update this property. + this._itemPropertyKeys = {}; + this._previousItemPropertyKeys = {}; + + this.readOnly(); + this.cacheable(); + + this.recomputeOnce = function(propertyName) { + // What we really want to do is coalesce by . + // We need a form of `scheduleOnce` that accepts an arbitrary token to + // coalesce by, in addition to the target and method. + Ember.run.once(this, recompute, propertyName); + }; + var recompute = function(propertyName) { + var dependentKeys = cp._dependentKeys, + meta = cp._instanceMeta(this, propertyName), + callbacks = cp._callbacks(); + + reset.call(this, cp, propertyName); + + forEach(cp._dependentKeys, function (dependentKey) { + var dependentArray = get(this, dependentKey), + previousDependentArray = meta.dependentArrays[dependentKey]; + + if (dependentArray === previousDependentArray) { + // The array may be the same, but our item property keys may have + // changed, so we set them up again. We can't easily tell if they've + // changed: the array may be the same object, but with different + // contents. + if (cp._previousItemPropertyKeys[dependentKey]) { + delete cp._previousItemPropertyKeys[dependentKey]; + meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]); + } + } else { + meta.dependentArrays[dependentKey] = dependentArray; + + if (previousDependentArray) { + meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey); + } + + if (dependentArray) { + meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey); + } + } + }, this); + + forEach(cp._dependentKeys, function(dependentKey) { + var dependentArray = get(this, dependentKey); + if (dependentArray) { + addItems.call(this, dependentArray, callbacks, cp, propertyName, meta); + } + }, this); + }; + + this.func = function (propertyName) { + Ember.assert("Computed reduce values require at least one dependent key", cp._dependentKeys); + + recompute.call(this, propertyName); + + return cp._instanceMeta(this, propertyName).getValue(); + }; +} + +Ember.ReduceComputedProperty = ReduceComputedProperty; +ReduceComputedProperty.prototype = o_create(ComputedProperty.prototype); + +function defaultCallback(computedValue) { + return computedValue; +} + +ReduceComputedProperty.prototype._callbacks = function () { + if (!this.callbacks) { + var options = this.options; + this.callbacks = { + removedItem: options.removedItem || defaultCallback, + addedItem: options.addedItem || defaultCallback + }; + } + return this.callbacks; +}; + +ReduceComputedProperty.prototype._hasInstanceMeta = function (context, propertyName) { + var guid = guidFor(context), + key = guid + ':' + propertyName; + + return !!this._instanceMetas[key]; +}; + +ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName) { + var guid = guidFor(context), + key = guid + ':' + propertyName, + meta = this._instanceMetas[key]; + + if (!meta) { + meta = this._instanceMetas[key] = new ReduceComputedPropertyInstanceMeta(context, propertyName, this.initialValue()); + meta.dependentArraysObserver = new DependentArraysObserver(this._callbacks(), this, meta, context, propertyName, meta.sugarMeta); + } + + return meta; +}; + +ReduceComputedProperty.prototype.initialValue = function () { + switch (typeof this.options.initialValue) { + case 'undefined': + throw new Error("reduce computed properties require an initial value: did you forget to pass one to Ember.reduceComputed?"); + case 'function': + return this.options.initialValue(); + default: + return this.options.initialValue; + } +}; + +ReduceComputedProperty.prototype.resetValue = function (value) { + return this.initialValue(); +}; + +ReduceComputedProperty.prototype.itemPropertyKey = function (dependentArrayKey, itemPropertyKey) { + this._itemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey] || []; + this._itemPropertyKeys[dependentArrayKey].push(itemPropertyKey); +}; + +ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArrayKey) { + if (this._itemPropertyKeys[dependentArrayKey]) { + this._previousItemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey]; + this._itemPropertyKeys[dependentArrayKey] = []; + } +}; + +ReduceComputedProperty.prototype.property = function () { + var cp = this, + args = a_slice.call(arguments), + propertyArgs = [], + match, + dependentArrayKey, + itemPropertyKey; + + forEach(a_slice.call(arguments), function (dependentKey) { + if (doubleEachPropertyPattern.test(dependentKey)) { + throw new Error("Nested @each properties not supported: " + dependentKey); + } else if (match = eachPropertyPattern.exec(dependentKey)) { + dependentArrayKey = match[1]; + itemPropertyKey = match[2]; + cp.itemPropertyKey(dependentArrayKey, itemPropertyKey); + propertyArgs.push(dependentArrayKey); + } else { + propertyArgs.push(dependentKey); + } + }); + + return ComputedProperty.prototype.property.apply(this, propertyArgs); +}; + +/** + Creates a computed property which operates on dependent arrays and + is updated with "one at a time" semantics. When items are added or + removed from the dependent array(s) a reduce computed only operates + on the change instead of re-evaluating the entire array. + + If there are more than one arguments the first arguments are + considered to be dependent property keys. The last argument is + required to be an options object. The options object can have the + following four properties. + + `initialValue` - A value or function that will be used as the initial + value for the computed. If this property is a function the result of calling + the function will be used as the initial value. This property is required. + + `initialize` - An optional initialize function. Typically this will be used + to set up state on the instanceMeta object. + + `removedItem` - A function that is called each time an element is removed + from the array. + + `addedItem` - A function that is called each time an element is added to + the array. + + + The `initialize` function has the following signature: + + ```javascript + function (initialValue, changeMeta, instanceMeta) + ``` + + `initialValue` - The value of the `initialValue` property from the + options object. + + `changeMeta` - An object which contains meta information about the + computed. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + + The `removedItem` and `addedItem` functions both have the following signature: + + ```javascript + function (accumulatedValue, item, changeMeta, instanceMeta) + ``` + + `accumulatedValue` - The value returned from the last time + `removedItem` or `addedItem` was called or `initialValue`. + + `item` - the element added or removed from the array + + `changeMeta` - An object which contains meta information about the + change. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + - `index` the index of the added or removed item + - `item` the added or removed item: this is exactly the same as + the second arg + - `arrayChanged` the array that triggered the change. Can be + useful when depending on multiple arrays. + + For property changes triggered on an item property change (when + depKey is something like `someArray.@each.someProperty`), + `changeMeta` will also contain the following property: + + - `previousValues` an object whose keys are the properties that changed on + the item, and whose values are the item's previous values. + + `previousValues` is important Ember coalesces item property changes via + Ember.run.once. This means that by the time removedItem gets called, item has + the new values, but you may need the previous value (eg for sorting & + filtering). + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + The `removedItem` and `addedItem` functions should return the accumulated + value. It is acceptable to not return anything (ie return undefined) + to invalidate the computation. This is generally not a good idea for + arrayComputed but it's used in eg max and min. + + Example + + ```javascript + Ember.computed.max = function (dependentKey) { + return Ember.reduceComputed.call(null, dependentKey, { + initialValue: -Infinity, + + addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + return Math.max(accumulatedValue, item); + }, + + removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + if (item < accumulatedValue) { + return accumulatedValue; + } + } + }); + }; + ``` + + @method reduceComputed + @for Ember + @param {String} [dependentKeys*] + @param {Object} options + @return {Ember.ComputedProperty} +*/ +Ember.reduceComputed = function (options) { + var args; + + if (arguments.length > 1) { + args = a_slice.call(arguments, 0, -1); + options = a_slice.call(arguments, -1)[0]; + } + + if (typeof options !== "object") { + throw new Error("Reduce Computed Property declared without an options hash"); + } + + if (Ember.isNone(options.initialValue)) { + throw new Error("Reduce Computed Property declared without an initial value"); + } + + var cp = new ReduceComputedProperty(options); + + if (args) { + cp.property.apply(cp, args); + } + + return cp; +}; + +})(); + + + +(function() { +var ReduceComputedProperty = Ember.ReduceComputedProperty, + a_slice = [].slice, + o_create = Ember.create, + forEach = Ember.EnumerableUtils.forEach; + +function ArrayComputedProperty() { + var cp = this; + + ReduceComputedProperty.apply(this, arguments); + + this.func = (function(reduceFunc) { + return function (propertyName) { + if (!cp._hasInstanceMeta(this, propertyName)) { + // When we recompute an array computed property, we need already + // retrieved arrays to be updated; we can't simply empty the cache and + // hope the array is re-retrieved. + forEach(cp._dependentKeys, function(dependentKey) { + Ember.addObserver(this, dependentKey, function() { + cp.recomputeOnce.call(this, propertyName); + }); + }, this); + } + + return reduceFunc.apply(this, arguments); + }; + })(this.func); + + return this; +} +Ember.ArrayComputedProperty = ArrayComputedProperty; +ArrayComputedProperty.prototype = o_create(ReduceComputedProperty.prototype); +ArrayComputedProperty.prototype.initialValue = function () { + return Ember.A(); +}; +ArrayComputedProperty.prototype.resetValue = function (array) { + array.clear(); + return array; +}; + +/** + Creates a computed property which operates on dependent arrays and + is updated with "one at a time" semantics. When items are added or + removed from the dependent array(s) an array computed only operates + on the change instead of re-evaluating the entire array. This should + return an array, if you'd like to use "one at a time" semantics and + compute some value other then an array look at + `Ember.reduceComputed`. + + If there are more than one arguments the first arguments are + considered to be dependent property keys. The last argument is + required to be an options object. The options object can have the + following three properties. + + `initialize` - An optional initialize function. Typically this will be used + to set up state on the instanceMeta object. + + `removedItem` - A function that is called each time an element is + removed from the array. + + `addedItem` - A function that is called each time an element is + added to the array. + + + The `initialize` function has the following signature: + + ```javascript + function (array, changeMeta, instanceMeta) + ``` + + `array` - The initial value of the arrayComputed, an empty array. + + `changeMeta` - An object which contains meta information about the + computed. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + + The `removedItem` and `addedItem` functions both have the following signature: + + ```javascript + function (accumulatedValue, item, changeMeta, instanceMeta) + ``` + + `accumulatedValue` - The value returned from the last time + `removedItem` or `addedItem` was called or an empty array. + + `item` - the element added or removed from the array + + `changeMeta` - An object which contains meta information about the + change. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + - `index` the index of the added or removed item + - `item` the added or removed item: this is exactly the same as + the second arg + - `arrayChanged` the array that triggered the change. Can be + useful when depending on multiple arrays. + + For property changes triggered on an item property change (when + depKey is something like `someArray.@each.someProperty`), + `changeMeta` will also contain the following property: + + - `previousValues` an object whose keys are the properties that changed on + the item, and whose values are the item's previous values. + + `previousValues` is important Ember coalesces item property changes via + Ember.run.once. This means that by the time removedItem gets called, item has + the new values, but you may need the previous value (eg for sorting & + filtering). + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + The `removedItem` and `addedItem` functions should return the accumulated + value. It is acceptable to not return anything (ie return undefined) + to invalidate the computation. This is generally not a good idea for + arrayComputed but it's used in eg max and min. + + Example + + ```javascript + Ember.computed.map = function(dependentKey, callback) { + var options = { + addedItem: function(array, item, changeMeta, instanceMeta) { + var mapped = callback(item); + array.insertAt(changeMeta.index, mapped); + return array; + }, + removedItem: function(array, item, changeMeta, instanceMeta) { + array.removeAt(changeMeta.index, 1); + return array; + } + }; + + return Ember.arrayComputed(dependentKey, options); + }; + ``` + + @method arrayComputed + @for Ember + @param {String} [dependentKeys*] + @param {Object} options + @return {Ember.ComputedProperty} +*/ +Ember.arrayComputed = function (options) { + var args; + + if (arguments.length > 1) { + args = a_slice.call(arguments, 0, -1); + options = a_slice.call(arguments, -1)[0]; + } + + if (typeof options !== "object") { + throw new Error("Array Computed Property declared without an options hash"); + } + + var cp = new ArrayComputedProperty(options); + + if (args) { + cp.property.apply(cp, args); + } + + return cp; +}; + +})(); + + + +(function() { +/** +@module ember +@submodule ember-runtime +*/ + +var get = Ember.get, + set = Ember.set, + guidFor = Ember.guidFor, + merge = Ember.merge, + a_slice = [].slice, + forEach = Ember.EnumerableUtils.forEach, + map = Ember.EnumerableUtils.map; + +/** + A computed property that calculates the maximum value in the + dependent array. This will return `-Infinity` when the dependent + array is empty. + + Example + + ```javascript + App.Person = Ember.Object.extend({ + childAges: Ember.computed.mapBy('children', 'age'), + maxChildAge: Ember.computed.max('childAges') + }); + + var lordByron = App.Person.create({children: []}); + lordByron.get('maxChildAge'); // -Infinity + lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); + lordByron.get('maxChildAge'); // 7 + lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); + lordByron.get('maxChildAge'); // 8 + ``` + + @method computed.max + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computes the largest value in the dependentKey's array +*/ +Ember.computed.max = function (dependentKey) { + return Ember.reduceComputed.call(null, dependentKey, { + initialValue: -Infinity, + + addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + return Math.max(accumulatedValue, item); + }, + + removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + if (item < accumulatedValue) { + return accumulatedValue; + } + } + }); +}; + +/** + A computed property that calculates the minimum value in the + dependent array. This will return `Infinity` when the dependent + array is empty. + + Example + + ```javascript + App.Person = Ember.Object.extend({ + childAges: Ember.computed.mapBy('children', 'age'), + minChildAge: Ember.computed.min('childAges') + }); + + var lordByron = App.Person.create({children: []}); + lordByron.get('minChildAge'); // Infinity + lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); + lordByron.get('minChildAge'); // 7 + lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); + lordByron.get('minChildAge'); // 5 + ``` + + @method computed.min + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array +*/ +Ember.computed.min = function (dependentKey) { + return Ember.reduceComputed.call(null, dependentKey, { + initialValue: Infinity, + + addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + return Math.min(accumulatedValue, item); + }, + + removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + if (item > accumulatedValue) { + return accumulatedValue; + } + } + }); +}; + +/** + Returns an array mapped via the callback + + The callback method you provide should have the following signature: + + ```javascript + function(item); + ``` + + - `item` is the current item in the iteration. + + Example + + ```javascript + App.Hampster = Ember.Object.extend({ + excitingChores: Ember.computed.map('chores', function(chore) { + return chore.toUpperCase() + '!'; + }) + }); + + var hampster = App.Hampster.create({chores: ['cook', 'clean', 'write more unit tests']}); + hampster.get('excitingChores'); // ['COOK!', 'CLEAN!', 'WRITE MORE UNIT TESTS!'] + ``` + + @method computed.map + @for Ember + @param {String} dependentKey + @param {Function} callback + @return {Ember.ComputedProperty} an array mapped via the callback +*/ +Ember.computed.map = function(dependentKey, callback) { + var options = { + addedItem: function(array, item, changeMeta, instanceMeta) { + var mapped = callback(item); + array.insertAt(changeMeta.index, mapped); + return array; + }, + removedItem: function(array, item, changeMeta, instanceMeta) { + array.removeAt(changeMeta.index, 1); + return array; + } + }; + + return Ember.arrayComputed(dependentKey, options); +}; + +/** + Returns an array mapped to the specified key. + + Example + + ```javascript + App.Person = Ember.Object.extend({ + childAges: Ember.computed.mapBy('children', 'age'), + minChildAge: Ember.computed.min('childAges') + }); + + var lordByron = App.Person.create({children: []}); + lordByron.get('childAge'); // [] + lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); + lordByron.get('childAge'); // [7] + lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); + lordByron.get('childAge'); // [7, 5, 8] + ``` + + @method computed.mapBy + @for Ember + @param {String} dependentKey + @param {String} propertyKey + @return {Ember.ComputedProperty} an array mapped to the specified key +*/ +Ember.computed.mapBy = function(dependentKey, propertyKey) { + var callback = function(item) { return get(item, propertyKey); }; + return Ember.computed.map(dependentKey + '.@each.' + propertyKey, callback); +}; + +/** + @method computed.mapProperty + @for Ember + @deprecated Use `Ember.computed.mapBy` instead + @param dependentKey + @param propertyKey +*/ +Ember.computed.mapProperty = Ember.computed.mapBy; + +/** + Filters the array by the callback. + + The callback method you provide should have the following signature: + + ```javascript + function(item); + ``` + + - `item` is the current item in the iteration. + + Example + + ```javascript + App.Hampster = Ember.Object.extend({ + remainingChores: Ember.computed.filter('chores', function(chore) { + return !chore.done; + }) + }); + + var hampster = App.Hampster.create({chores: [ + {name: 'cook', done: true}, + {name: 'clean', done: true}, + {name: 'write more unit tests', done: false} + ]}); + hampster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] + ``` + + @method computed.filter + @for Ember + @param {String} dependentKey + @param {Function} callback + @return {Ember.ComputedProperty} the filtered array +*/ +Ember.computed.filter = function(dependentKey, callback) { + var options = { + initialize: function (array, changeMeta, instanceMeta) { + instanceMeta.filteredArrayIndexes = new Ember.SubArray(); + }, + + addedItem: function(array, item, changeMeta, instanceMeta) { + var match = !!callback(item), + filterIndex = instanceMeta.filteredArrayIndexes.addItem(changeMeta.index, match); + + if (match) { + array.insertAt(filterIndex, item); + } + + return array; + }, + + removedItem: function(array, item, changeMeta, instanceMeta) { + var filterIndex = instanceMeta.filteredArrayIndexes.removeItem(changeMeta.index); + + if (filterIndex > -1) { + array.removeAt(filterIndex); + } + + return array; + } + }; + + return Ember.arrayComputed(dependentKey, options); +}; + +/** + Filters the array by the property and value + + Example + + ```javascript + App.Hampster = Ember.Object.extend({ + remainingChores: Ember.computed.filterBy('chores', 'done', false) + }); + + var hampster = App.Hampster.create({chores: [ + {name: 'cook', done: true}, + {name: 'clean', done: true}, + {name: 'write more unit tests', done: false} + ]}); + hampster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] + ``` + + @method computed.filterBy + @for Ember + @param {String} dependentKey + @param {String} propertyKey + @param {String} value + @return {Ember.ComputedProperty} the filtered array +*/ +Ember.computed.filterBy = function(dependentKey, propertyKey, value) { + var callback; + + if (arguments.length === 2) { + callback = function(item) { + return get(item, propertyKey); + }; + } else { + callback = function(item) { + return get(item, propertyKey) === value; + }; + } + + return Ember.computed.filter(dependentKey + '.@each.' + propertyKey, callback); +}; + +/** + @method computed.filterProperty + @for Ember + @param dependentKey + @param propertyKey + @param value + @deprecated Use `Ember.computed.filterBy` instead +*/ +Ember.computed.filterProperty = Ember.computed.filterBy; + +/** + A computed property which returns a new array with all the unique + elements from one or more dependent arrays. + + Example + + ```javascript + App.Hampster = Ember.Object.extend({ + uniqueFruits: Ember.computed.uniq('fruits') + }); + + var hampster = App.Hampster.create({fruits: [ + 'banana', + 'grape', + 'kale', + 'banana' + ]}); + hampster.get('uniqueFruits'); // ['banana', 'grape', 'kale'] + ``` + + @method computed.uniq + @for Ember + @param {String} propertyKey* + @return {Ember.ComputedProperty} computes a new array with all the + unique elements from the dependent array +*/ +Ember.computed.uniq = function() { + var args = a_slice.call(arguments); + args.push({ + initialize: function(array, changeMeta, instanceMeta) { + instanceMeta.itemCounts = {}; + }, + + addedItem: function(array, item, changeMeta, instanceMeta) { + var guid = guidFor(item); + + if (!instanceMeta.itemCounts[guid]) { + instanceMeta.itemCounts[guid] = 1; + } else { + ++instanceMeta.itemCounts[guid]; + } + array.addObject(item); + return array; + }, + removedItem: function(array, item, _, instanceMeta) { + var guid = guidFor(item), + itemCounts = instanceMeta.itemCounts; + + if (--itemCounts[guid] === 0) { + array.removeObject(item); + } + return array; + } + }); + return Ember.arrayComputed.apply(null, args); +}; + +/** + Alias for [Ember.computed.uniq](/api/#method_computed_uniq). + + @method computed.union + @for Ember + @param {String} propertyKey* + @return {Ember.ComputedProperty} computes a new array with all the + unique elements from the dependent array +*/ +Ember.computed.union = Ember.computed.uniq; + +/** + A computed property which returns a new array with all the duplicated + elements from two or more dependeny arrays. + + Example + + ```javascript + var obj = Ember.Object.createWithMixins({ + adaFriends: ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'], + charlesFriends: ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'], + friendsInCommon: Ember.computed.intersect('adaFriends', 'charlesFriends') + }); + + obj.get('friendsInCommon'); // ['William King', 'Mary Somerville'] + ``` + + @method computed.intersect + @for Ember + @param {String} propertyKey* + @return {Ember.ComputedProperty} computes a new array with all the + duplicated elements from the dependent arrays +*/ +Ember.computed.intersect = function () { + var getDependentKeyGuids = function (changeMeta) { + return map(changeMeta.property._dependentKeys, function (dependentKey) { + return guidFor(dependentKey); + }); + }; + + var args = a_slice.call(arguments); + args.push({ + initialize: function (array, changeMeta, instanceMeta) { + instanceMeta.itemCounts = {}; + }, + + addedItem: function(array, item, changeMeta, instanceMeta) { + var itemGuid = guidFor(item), + dependentGuids = getDependentKeyGuids(changeMeta), + dependentGuid = guidFor(changeMeta.arrayChanged), + numberOfDependentArrays = changeMeta.property._dependentKeys.length, + itemCounts = instanceMeta.itemCounts; + + if (!itemCounts[itemGuid]) { itemCounts[itemGuid] = {}; } + if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } + + if (++itemCounts[itemGuid][dependentGuid] === 1 && + numberOfDependentArrays === Ember.keys(itemCounts[itemGuid]).length) { + + array.addObject(item); + } + return array; + }, + removedItem: function(array, item, changeMeta, instanceMeta) { + var itemGuid = guidFor(item), + dependentGuids = getDependentKeyGuids(changeMeta), + dependentGuid = guidFor(changeMeta.arrayChanged), + numberOfDependentArrays = changeMeta.property._dependentKeys.length, + numberOfArraysItemAppearsIn, + itemCounts = instanceMeta.itemCounts; + + if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } + if (--itemCounts[itemGuid][dependentGuid] === 0) { + delete itemCounts[itemGuid][dependentGuid]; + numberOfArraysItemAppearsIn = Ember.keys(itemCounts[itemGuid]).length; + + if (numberOfArraysItemAppearsIn === 0) { + delete itemCounts[itemGuid]; + } + array.removeObject(item); + } + return array; + } + }); + return Ember.arrayComputed.apply(null, args); +}; + +/** + A computed property which returns a new array with all the + properties from the first dependent array that are not in the second + dependent array. + + Example + + ```javascript + App.Hampster = Ember.Object.extend({ + likes: ['banana', 'grape', 'kale'], + wants: Ember.computed.setDiff('likes', 'fruits') + }); + + var hampster = App.Hampster.create({fruits: [ + 'grape', + 'kale', + ]}); + hampster.get('wants'); // ['banana'] + ``` + + @method computed.setDiff + @for Ember + @param {String} setAProperty + @param {String} setBProperty + @return {Ember.ComputedProperty} computes a new array with all the + items from the first dependent array that are not in the second + dependent array +*/ +Ember.computed.setDiff = function (setAProperty, setBProperty) { + if (arguments.length !== 2) { + throw new Error("setDiff requires exactly two dependent arrays."); + } + return Ember.arrayComputed.call(null, setAProperty, setBProperty, { + addedItem: function (array, item, changeMeta, instanceMeta) { + var setA = get(this, setAProperty), + setB = get(this, setBProperty); + + if (changeMeta.arrayChanged === setA) { + if (!setB.contains(item)) { + array.addObject(item); + } + } else { + array.removeObject(item); + } + return array; + }, + + removedItem: function (array, item, changeMeta, instanceMeta) { + var setA = get(this, setAProperty), + setB = get(this, setBProperty); + + if (changeMeta.arrayChanged === setB) { + if (setA.contains(item)) { + array.addObject(item); + } + } else { + array.removeObject(item); + } + return array; + } + }); +}; + +function binarySearch(array, item, low, high) { + var mid, midItem, res, guidMid, guidItem; + + if (arguments.length < 4) { high = get(array, 'length'); } + if (arguments.length < 3) { low = 0; } + + if (low === high) { + return low; + } + + mid = low + Math.floor((high - low) / 2); + midItem = array.objectAt(mid); + + guidMid = _guidFor(midItem); + guidItem = _guidFor(item); + + if (guidMid === guidItem) { + return mid; + } + + res = this.order(midItem, item); + if (res === 0) { + res = guidMid < guidItem ? -1 : 1; + } + + + if (res < 0) { + return this.binarySearch(array, item, mid+1, high); + } else if (res > 0) { + return this.binarySearch(array, item, low, mid); + } + + return mid; + + function _guidFor(item) { + if (Ember.ObjectProxy.detectInstance(item)) { + return guidFor(get(item, 'content')); + } + return guidFor(item); + } +} + +/** + A computed property which returns a new array with all the + properties from the first dependent array sorted based on a property + or sort function. + + The callback method you provide should have the following signature: + + ```javascript + function(itemA, itemB); + ``` + + - `itemA` the first item to compare. + - `itemB` the second item to compare. + + This function should return `-1` when `itemA` should come before + `itemB`. It should return `1` when `itemA` should come after + `itemB`. If the `itemA` and `itemB` are equal this function should return `0`. + + Example + + ```javascript + var ToDoList = Ember.Object.extend({ + todosSorting: ['name'], + sortedTodos: Ember.computed.sort('todos', 'todosSorting'), + priorityTodos: Ember.computed.sort('todos', function(a, b){ + if (a.priority > b.priority) { + return 1; + } else if (a.priority < b.priority) { + return -1; + } + return 0; + }), + }); + var todoList = ToDoList.create({todos: [ + {name: 'Unit Test', priority: 2}, + {name: 'Documentation', priority: 3}, + {name: 'Release', priority: 1} + ]}); + + todoList.get('sortedTodos'); // [{name:'Documentation', priority:3}, {name:'Release', priority:1}, {name:'Unit Test', priority:2}] + todoList.get('priroityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}] + ``` + + @method computed.sort + @for Ember + @param {String} dependentKey + @param {String or Function} sortDefinition a dependent key to an + array of sort properties or a function to use when sorting + @return {Ember.ComputedProperty} computes a new sorted array based + on the sort property array or callback function +*/ +Ember.computed.sort = function (itemsKey, sortDefinition) { + Ember.assert("Ember.computed.sort requires two arguments: an array key to sort and either a sort properties key or sort function", arguments.length === 2); + + var initFn, sortPropertiesKey; + + if (typeof sortDefinition === 'function') { + initFn = function (array, changeMeta, instanceMeta) { + instanceMeta.order = sortDefinition; + instanceMeta.binarySearch = binarySearch; + }; + } else { + sortPropertiesKey = sortDefinition; + initFn = function (array, changeMeta, instanceMeta) { + function setupSortProperties() { + var sortPropertyDefinitions = get(this, sortPropertiesKey), + sortProperty, + sortProperties = instanceMeta.sortProperties = [], + sortPropertyAscending = instanceMeta.sortPropertyAscending = {}, + idx, + asc; + + Ember.assert("Cannot sort: '" + sortPropertiesKey + "' is not an array.", Ember.isArray(sortPropertyDefinitions)); + + changeMeta.property.clearItemPropertyKeys(itemsKey); + + forEach(sortPropertyDefinitions, function (sortPropertyDefinition) { + if ((idx = sortPropertyDefinition.indexOf(':')) !== -1) { + sortProperty = sortPropertyDefinition.substring(0, idx); + asc = sortPropertyDefinition.substring(idx+1).toLowerCase() !== 'desc'; + } else { + sortProperty = sortPropertyDefinition; + asc = true; + } + + sortProperties.push(sortProperty); + sortPropertyAscending[sortProperty] = asc; + changeMeta.property.itemPropertyKey(itemsKey, sortProperty); + }); + + sortPropertyDefinitions.addObserver('@each', this, updateSortPropertiesOnce); + } + + function updateSortPropertiesOnce() { + Ember.run.once(this, updateSortProperties, changeMeta.propertyName); + } + + function updateSortProperties(propertyName) { + setupSortProperties.call(this); + changeMeta.property.recomputeOnce.call(this, propertyName); + } + + Ember.addObserver(this, sortPropertiesKey, updateSortPropertiesOnce); + + setupSortProperties.call(this); + + + instanceMeta.order = function (itemA, itemB) { + var sortProperty, result, asc; + for (var i = 0; i < this.sortProperties.length; ++i) { + sortProperty = this.sortProperties[i]; + result = Ember.compare(get(itemA, sortProperty), get(itemB, sortProperty)); + + if (result !== 0) { + asc = this.sortPropertyAscending[sortProperty]; + return asc ? result : (-1 * result); + } + } + + return 0; + }; + + instanceMeta.binarySearch = binarySearch; + }; + } + + return Ember.arrayComputed.call(null, itemsKey, { + initialize: initFn, + + addedItem: function (array, item, changeMeta, instanceMeta) { + var index = instanceMeta.binarySearch(array, item); + array.insertAt(index, item); + return array; + }, + + removedItem: function (array, item, changeMeta, instanceMeta) { + var proxyProperties, index, searchItem; + + if (changeMeta.previousValues) { + proxyProperties = merge({ content: item }, changeMeta.previousValues); + + searchItem = Ember.ObjectProxy.create(proxyProperties); + } else { + searchItem = item; + } + + index = instanceMeta.binarySearch(array, searchItem); + array.removeAt(index); + return array; + } + }); +}; + +})(); + + + +(function() { +/** + Expose RSVP implementation + + Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md + + @class RSVP + @namespace Ember + @constructor +*/ +Ember.RSVP = requireModule('rsvp'); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-runtime +*/ + +var STRING_DASHERIZE_REGEXP = (/[ _]/g); +var STRING_DASHERIZE_CACHE = {}; +var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g); +var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g); +var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); +var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g); + +/** + Defines the hash of localized strings for the current language. Used by + the `Ember.String.loc()` helper. To localize, add string values to this + hash. + + @property STRINGS + @for Ember + @type Hash +*/ +Ember.STRINGS = {}; + +/** + Defines string helper methods including string formatting and localization. + Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be + added to the `String.prototype` as well. + + @class String + @namespace Ember + @static +*/ +Ember.String = { + + /** + Apply formatting options to the string. This will look for occurrences + of "%@" in your string and substitute them with the arguments you pass into + this method. If you want to control the specific order of replacement, + you can add a number after the key as well to indicate which argument + you want to insert. + + Ordered insertions are most useful when building loc strings where values + you need to insert may appear in different orders. + + ```javascript + "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe" + "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John" + ``` + + @method fmt + @param {String} str The string to format + @param {Array} formats An array of parameters to interpolate into string. + @return {String} formatted string + */ + fmt: function(str, formats) { + // first, replace any ORDERED replacements. + var idx = 0; // the current index for non-numerical replacements + return str.replace(/%@([0-9]+)?/g, function(s, argIndex) { + argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++; + s = formats[argIndex]; + return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s); + }) ; + }, + + /** + Formats the passed string, but first looks up the string in the localized + strings hash. This is a convenient way to localize text. See + `Ember.String.fmt()` for more information on formatting. + + Note that it is traditional but not required to prefix localized string + keys with an underscore or other character so you can easily identify + localized strings. + + ```javascript + Ember.STRINGS = { + '_Hello World': 'Bonjour le monde', + '_Hello %@ %@': 'Bonjour %@ %@' + }; + + Ember.String.loc("_Hello World"); // 'Bonjour le monde'; + Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith"; + ``` + + @method loc + @param {String} str The string to format + @param {Array} formats Optional array of parameters to interpolate into string. + @return {String} formatted string + */ + loc: function(str, formats) { + str = Ember.STRINGS[str] || str; + return Ember.String.fmt(str, formats) ; + }, + + /** + Splits a string into separate units separated by spaces, eliminating any + empty strings in the process. This is a convenience method for split that + is mostly useful when applied to the `String.prototype`. + + ```javascript + Ember.String.w("alpha beta gamma").forEach(function(key) { + console.log(key); + }); + + // > alpha + // > beta + // > gamma + ``` + + @method w + @param {String} str The string to split + @return {String} split string + */ + w: function(str) { return str.split(/\s+/); }, + + /** + Converts a camelized string into all lower case separated by underscores. + + ```javascript + 'innerHTML'.decamelize(); // 'inner_html' + 'action_name'.decamelize(); // 'action_name' + 'css-class-name'.decamelize(); // 'css-class-name' + 'my favorite items'.decamelize(); // 'my favorite items' + ``` + + @method decamelize + @param {String} str The string to decamelize. + @return {String} the decamelized string. + */ + decamelize: function(str) { + return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase(); + }, + + /** + Replaces underscores, spaces, or camelCase with dashes. + + ```javascript + 'innerHTML'.dasherize(); // 'inner-html' + 'action_name'.dasherize(); // 'action-name' + 'css-class-name'.dasherize(); // 'css-class-name' + 'my favorite items'.dasherize(); // 'my-favorite-items' + ``` + + @method dasherize + @param {String} str The string to dasherize. + @return {String} the dasherized string. + */ + dasherize: function(str) { + var cache = STRING_DASHERIZE_CACHE, + hit = cache.hasOwnProperty(str), + ret; + + if (hit) { + return cache[str]; + } else { + ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-'); + cache[str] = ret; + } + + return ret; + }, + + /** + Returns the lowerCamelCase form of a string. + + ```javascript + 'innerHTML'.camelize(); // 'innerHTML' + 'action_name'.camelize(); // 'actionName' + 'css-class-name'.camelize(); // 'cssClassName' + 'my favorite items'.camelize(); // 'myFavoriteItems' + 'My Favorite Items'.camelize(); // 'myFavoriteItems' + ``` + + @method camelize + @param {String} str The string to camelize. + @return {String} the camelized string. + */ + camelize: function(str) { + return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) { + return chr ? chr.toUpperCase() : ''; + }).replace(/^([A-Z])/, function(match, separator, chr) { + return match.toLowerCase(); + }); + }, + + /** + Returns the UpperCamelCase form of a string. + + ```javascript + 'innerHTML'.classify(); // 'InnerHTML' + 'action_name'.classify(); // 'ActionName' + 'css-class-name'.classify(); // 'CssClassName' + 'my favorite items'.classify(); // 'MyFavoriteItems' + ``` + + @method classify + @param {String} str the string to classify + @return {String} the classified string + */ + classify: function(str) { + var parts = str.split("."), + out = []; + + for (var i=0, l=parts.length; i true + controller.get('isSettled') //=> false + controller.get('isRejected') //=> false + controller.get('isFulfilled') //=> false + ``` + + When the the $.getJSON completes, and the promise is fulfilled + with json, the life cycle attributes will update accordingly. + + ```javascript + controller.get('isPending') //=> false + controller.get('isSettled') //=> true + controller.get('isRejected') //=> false + controller.get('isFulfilled') //=> true + ``` + + As the controller is an ObjectController, and the json now its content, + all the json properties will be available directly from the controller. + + ```javascript + // Assuming the following json: + { + firstName: 'Stefan', + lastName: 'Penner' + } + + // both properties will accessible on the controller + controller.get('firstName') //=> 'Stefan' + controller.get('lastName') //=> 'Penner' + ``` + + If the controller is backing a template, the attributes are + bindable from within that template + + ```handlebars + {{#if isPending}} + loading... + {{else}} + firstName: {{firstName}} + lastName: {{lastName}} + {{/if}} + ``` + @class Ember.PromiseProxyMixin +*/ +Ember.PromiseProxyMixin = Ember.Mixin.create({ + reason: null, + isPending: not('isSettled').readOnly(), + isSettled: or('isRejected', 'isFulfilled').readOnly(), + isRejected: false, + isFulfilled: false, + + promise: Ember.computed(function(key, promise) { + if (arguments.length === 2) { + promise = resolve(promise); + installPromise(this, promise); + return promise; + } else { + throw new Error("PromiseProxy's promise must be set"); + } + }), + + then: function(fulfill, reject) { + return get(this, 'promise').then(fulfill, reject); + } +}); + + +})(); + + + +(function() { + +})(); + + + +(function() { +var get = Ember.get, + forEach = Ember.EnumerableUtils.forEach, + RETAIN = 'r', + INSERT = 'i', + DELETE = 'd'; + +/** + An `Ember.TrackedArray` tracks array operations. It's useful when you want to + lazily compute the indexes of items in an array after they've been shifted by + subsequent operations. + + @class TrackedArray + @namespace Ember + @param {array} [items=[]] The array to be tracked. This is used just to get + the initial items for the starting state of retain:n. +*/ +Ember.TrackedArray = function (items) { + if (arguments.length < 1) { items = []; } + + var length = get(items, 'length'); + + if (length) { + this._content = [new ArrayOperation(RETAIN, length, items)]; + } else { + this._content = []; + } +}; + +Ember.TrackedArray.RETAIN = RETAIN; +Ember.TrackedArray.INSERT = INSERT; +Ember.TrackedArray.DELETE = DELETE; + +Ember.TrackedArray.prototype = { + + /** + Track that `newItems` were added to the tracked array at `index`. + + @method addItems + @param index + @param newItems + */ + addItems: function (index, newItems) { + var count = get(newItems, 'length'), + match = this._findArrayOperation(index), + arrayOperation = match.operation, + arrayOperationIndex = match.index, + arrayOperationRangeStart = match.rangeStart, + composeIndex, + splitIndex, + splitItems, + splitArrayOperation, + newArrayOperation; + + newArrayOperation = new ArrayOperation(INSERT, count, newItems); + + if (arrayOperation) { + if (!match.split) { + // insert left of arrayOperation + this._content.splice(arrayOperationIndex, 0, newArrayOperation); + composeIndex = arrayOperationIndex; + } else { + this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); + composeIndex = arrayOperationIndex + 1; + } + } else { + // insert at end + this._content.push(newArrayOperation); + composeIndex = arrayOperationIndex; + } + + this._composeInsert(composeIndex); + }, + + /** + Track that `count` items were removed at `index`. + + @method removeItems + @param index + @param count + */ + removeItems: function (index, count) { + var match = this._findArrayOperation(index), + arrayOperation = match.operation, + arrayOperationIndex = match.index, + arrayOperationRangeStart = match.rangeStart, + newArrayOperation, + composeIndex; + + newArrayOperation = new ArrayOperation(DELETE, count); + if (!match.split) { + // insert left of arrayOperation + this._content.splice(arrayOperationIndex, 0, newArrayOperation); + composeIndex = arrayOperationIndex; + } else { + this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); + composeIndex = arrayOperationIndex + 1; + } + + return this._composeDelete(composeIndex); + }, + + /** + Apply all operations, reducing them to retain:n, for `n`, the number of + items in the array. + + `callback` will be called for each operation and will be passed the following arguments: + +* {array} items The items for the given operation +* {number} offset The computed offset of the items, ie the index in the +array of the first item for this operation. +* {string} operation The type of the operation. One of `Ember.TrackedArray.{RETAIN, DELETE, INSERT}` +* + + @method apply + @param {function} callback + */ + apply: function (callback) { + var items = [], + offset = 0; + + forEach(this._content, function (arrayOperation) { + callback(arrayOperation.items, offset, arrayOperation.operation); + + if (arrayOperation.operation !== DELETE) { + offset += arrayOperation.count; + items = items.concat(arrayOperation.items); + } + }); + + this._content = [new ArrayOperation(RETAIN, items.length, items)]; + }, + + /** + Return an ArrayOperationMatch for the operation that contains the item at `index`. + + @method _findArrayOperation + + @param {number} index the index of the item whose operation information + should be returned. + @private + */ + _findArrayOperation: function (index) { + var arrayOperationIndex, + len, + split = false, + arrayOperation, + arrayOperationRangeStart, + arrayOperationRangeEnd; + + // OPTIMIZE: we could search these faster if we kept a balanced tree. + // find leftmost arrayOperation to the right of `index` + for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._content.length; arrayOperationIndex < len; ++arrayOperationIndex) { + arrayOperation = this._content[arrayOperationIndex]; + + if (arrayOperation.operation === DELETE) { continue; } + + arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1; + + if (index === arrayOperationRangeStart) { + break; + } else if (index > arrayOperationRangeStart && index <= arrayOperationRangeEnd) { + split = true; + break; + } else { + arrayOperationRangeStart = arrayOperationRangeEnd + 1; + } + } + + return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart); + }, + + _split: function (arrayOperationIndex, splitIndex, newArrayOperation) { + var arrayOperation = this._content[arrayOperationIndex], + splitItems = arrayOperation.items.slice(splitIndex), + splitArrayOperation = new ArrayOperation(arrayOperation.operation, splitItems.length, splitItems); + + // truncate LHS + arrayOperation.count = splitIndex; + arrayOperation.items = arrayOperation.items.slice(0, splitIndex); + + this._content.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation); + }, + + // TODO: unify _composeInsert, _composeDelete + // see SubArray for a better implementation. + _composeInsert: function (index) { + var newArrayOperation = this._content[index], + leftArrayOperation = this._content[index-1], // may be undefined + rightArrayOperation = this._content[index+1], // may be undefined + leftOp = leftArrayOperation && leftArrayOperation.operation, + rightOp = rightArrayOperation && rightArrayOperation.operation; + + if (leftOp === INSERT) { + // merge left + leftArrayOperation.count += newArrayOperation.count; + leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items); + + if (rightOp === INSERT) { + // also merge right + leftArrayOperation.count += rightArrayOperation.count; + leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items); + this._content.splice(index, 2); + } else { + // only merge left + this._content.splice(index, 1); + } + } else if (rightOp === INSERT) { + // merge right + newArrayOperation.count += rightArrayOperation.count; + newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items); + this._content.splice(index + 1, 1); + } + }, + + _composeDelete: function (index) { + var arrayOperation = this._content[index], + deletesToGo = arrayOperation.count, + leftArrayOperation = this._content[index-1], // may be undefined + leftOp = leftArrayOperation && leftArrayOperation.operation, + nextArrayOperation, + nextOp, + nextCount, + removedItems = []; + + if (leftOp === DELETE) { + arrayOperation = leftArrayOperation; + index -= 1; + } + + for (var i = index + 1; deletesToGo > 0; ++i) { + nextArrayOperation = this._content[i]; + nextOp = nextArrayOperation.operation; + nextCount = nextArrayOperation.count; + + if (nextOp === DELETE) { + arrayOperation.count += nextCount; + continue; + } + + if (nextCount > deletesToGo) { + removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo)); + nextArrayOperation.count -= deletesToGo; + + // In the case where we truncate the last arrayOperation, we don't need to + // remove it; also the deletesToGo reduction is not the entirety of + // nextCount + i -= 1; + nextCount = deletesToGo; + + deletesToGo = 0; + } else { + removedItems = removedItems.concat(nextArrayOperation.items); + deletesToGo -= nextCount; + } + + if (nextOp === INSERT) { + arrayOperation.count -= nextCount; + } + } + + if (arrayOperation.count > 0) { + this._content.splice(index+1, i-1-index); + } else { + // The delete operation can go away; it has merely reduced some other + // operation, as in D:3 I:4 + this._content.splice(index, 1); + } + + return removedItems; + } +}; + +function ArrayOperation (operation, count, items) { + this.operation = operation; // RETAIN | INSERT | DELETE + this.count = count; + this.items = items; +} + +/** + Internal data structure used to include information when looking up operations + by item index. + + @method ArrayOperationMatch + @private + @property {ArrayOperation} operation + @property {number} index The index of `operation` in the array of operations. + @property {boolean} split Whether or not the item index searched for would + require a split for a new operation type. + @property {number} rangeStart The index of the first item in the operation, + with respect to the tracked array. The index of the last item can be computed + from `rangeStart` and `operation.count`. +*/ +function ArrayOperationMatch(operation, index, split, rangeStart) { + this.operation = operation; + this.index = index; + this.split = split; + this.rangeStart = rangeStart; +} + +})(); + + + +(function() { +var get = Ember.get, + forEach = Ember.EnumerableUtils.forEach, + RETAIN = 'r', + FILTER = 'f'; + +function Operation (type, count) { + this.type = type; + this.count = count; +} + +/** + An `Ember.SubArray` tracks an array in a way similar to, but more specialized + than, `Ember.TrackedArray`. It is useful for keeping track of the indexes of + items within a filtered array. + + @class SubArray + @namespace Ember +*/ +Ember.SubArray = function (length) { + if (arguments.length < 1) { length = 0; } + + if (length > 0) { + this._operations = [new Operation(RETAIN, length)]; + } else { + this._operations = []; + } +}; + +Ember.SubArray.prototype = { + /** + Track that an item was added to the tracked array. + + @method addItem + + @param {number} index The index of the item in the tracked array. + @param {boolean} match `true` iff the item is included in the subarray. + + @return {number} The index of the item in the subarray. + */ + addItem: function(index, match) { + var returnValue = -1, + itemType = match ? RETAIN : FILTER, + self = this; + + this._findOperation(index, function(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { + var newOperation, splitOperation; + + if (itemType === operation.type) { + ++operation.count; + } else if (index === rangeStart) { + // insert to the left of `operation` + self._operations.splice(operationIndex, 0, new Operation(itemType, 1)); + } else { + newOperation = new Operation(itemType, 1); + splitOperation = new Operation(operation.type, rangeEnd - index + 1); + operation.count = index - rangeStart; + + self._operations.splice(operationIndex + 1, 0, newOperation, splitOperation); + } + + if (match) { + if (operation.type === RETAIN) { + returnValue = seenInSubArray + (index - rangeStart); + } else { + returnValue = seenInSubArray; + } + } + + self._composeAt(operationIndex); + }, function(seenInSubArray) { + self._operations.push(new Operation(itemType, 1)); + + if (match) { + returnValue = seenInSubArray; + } + + self._composeAt(self._operations.length-1); + }); + + return returnValue; + }, + + /** + Track that an item was removed from the tracked array. + + @method removeItem + + @param {number} index The index of the item in the tracked array. + + @return {number} The index of the item in the subarray, or `-1` if the item + was not in the subarray. + */ + removeItem: function(index) { + var returnValue = -1, + self = this; + + this._findOperation(index, function (operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { + if (operation.type === RETAIN) { + returnValue = seenInSubArray + (index - rangeStart); + } + + if (operation.count > 1) { + --operation.count; + } else { + self._operations.splice(operationIndex, 1); + self._composeAt(operationIndex); + } + }); + + return returnValue; + }, + + + _findOperation: function (index, foundCallback, notFoundCallback) { + var operationIndex, + len, + operation, + rangeStart, + rangeEnd, + seenInSubArray = 0; + + // OPTIMIZE: change to balanced tree + // find leftmost operation to the right of `index` + for (operationIndex = rangeStart = 0, len = this._operations.length; operationIndex < len; rangeStart = rangeEnd + 1, ++operationIndex) { + operation = this._operations[operationIndex]; + rangeEnd = rangeStart + operation.count - 1; + + if (index >= rangeStart && index <= rangeEnd) { + foundCallback(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray); + return; + } else if (operation.type === RETAIN) { + seenInSubArray += operation.count; + } + } + + notFoundCallback(seenInSubArray); + }, + + _composeAt: function(index) { + var op = this._operations[index], + otherOp; + + if (!op) { + // Composing out of bounds is a no-op, as when removing the last operation + // in the list. + return; + } + + if (index > 0) { + otherOp = this._operations[index-1]; + if (otherOp.type === op.type) { + op.count += otherOp.count; + this._operations.splice(index-1, 1); + } + } + + if (index < this._operations.length-1) { + otherOp = this._operations[index+1]; + if (otherOp.type === op.type) { + op.count += otherOp.count; + this._operations.splice(index+1, 1); + } + } + } +}; })(); @@ -12245,6 +15370,7 @@ function makeCtor() { Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty)); Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1)); + Ember.assert("`actions` must be provided at extend time, not at create time, when Ember.ActionHandler is used (i.e. views, controllers & routes).", !((keyName === 'actions') && Ember.ActionHandler.detect(this))); if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) { var baseValue = this[keyName]; @@ -12275,9 +15401,9 @@ function makeCtor() { } } finishPartial(this, m); + this.init.apply(this, arguments); m.proto = proto; finishChains(this); - this.init.apply(this, arguments); sendEvent(this, "init"); }; @@ -12410,7 +15536,10 @@ CoreObject.PrototypeMixin = Mixin.create({ are also concatenated, in addition to `classNames`. This feature is available for you to use throughout the Ember object model, - although typical app developers are likely to use it infrequently. + although typical app developers are likely to use it infrequently. Since + it changes expectations about behavior of properties, you should properly + document its usage in each individual concatenated property (to not + mislead your users to think they can override the property in a subclass). @property concatenatedProperties @type Array @@ -12629,12 +15758,65 @@ var ClassMixin = Mixin.create({ return new C(); }, + /** + + Augments a constructor's prototype with additional + properties and functions: + + ```javascript + MyObject = Ember.Object.extend({ + name: 'an object' + }); + + o = MyObject.create(); + o.get('name'); // 'an object' + + MyObject.reopen({ + say: function(msg){ + console.log(msg); + } + }) + + o2 = MyObject.create(); + o2.say("hello"); // logs "hello" + + o.say("goodbye"); // logs "goodbye" + ``` + + To add functions and properties to the constructor itself, + see `reopenClass` + + @method reopen + */ reopen: function() { this.willReopen(); reopen.apply(this.PrototypeMixin, arguments); return this; }, + /** + Augments a constructor's own properties and functions: + + ```javascript + MyObject = Ember.Object.extend({ + name: 'an object' + }); + + + MyObject.reopenClass({ + canBuild: false + }); + + MyObject.canBuild; // false + o = MyObject.create(); + ``` + + To add functions and properties to instances of + a constructor by extending the constructor's prototype + see `reopen` + + @method reopenClass + */ reopenClass: function() { reopen.apply(this.ClassMixin, arguments); applyMixin(this, arguments, false); @@ -12913,6 +16095,8 @@ function classToString() { if (this[NAME_KEY]) { ret = this[NAME_KEY]; + } else if (this._toString) { + ret = this._toString; } else { var str = superClassString(this); if (str) { @@ -13309,7 +16493,9 @@ var get = Ember.get, removeBeforeObserver = Ember.removeBeforeObserver, removeObserver = Ember.removeObserver, propertyWillChange = Ember.propertyWillChange, - propertyDidChange = Ember.propertyDidChange; + propertyDidChange = Ember.propertyDidChange, + meta = Ember.meta, + defineProperty = Ember.defineProperty; function contentPropertyWillChange(content, contentKey) { var key = contentKey.slice(8); // remove "content." @@ -13427,6 +16613,14 @@ Ember.ObjectProxy = Ember.Object.extend(/** @scope Ember.ObjectProxy.prototype * }, setUnknownProperty: function (key, value) { + var m = meta(this); + if (m.proto === this) { + // if marked as prototype then just defineProperty + // rather than delegate + defineProperty(this, key, null, value); + return value; + } + var content = get(this, 'content'); Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content); return set(content, key, value); @@ -13434,25 +16628,6 @@ Ember.ObjectProxy = Ember.Object.extend(/** @scope Ember.ObjectProxy.prototype * }); -Ember.ObjectProxy.reopenClass({ - create: function () { - var mixin, prototype, i, l, properties, keyName; - if (arguments.length) { - prototype = this.proto(); - for (i = 0, l = arguments.length; i < l; i++) { - properties = arguments[i]; - for (keyName in properties) { - if (!properties.hasOwnProperty(keyName) || keyName in prototype) { continue; } - if (!mixin) mixin = {}; - mixin[keyName] = null; - } - } - if (mixin) this._initMixins([mixin]); - } - return this._super.apply(this, arguments); - } -}); - })(); @@ -13465,7 +16640,8 @@ Ember.ObjectProxy.reopenClass({ var set = Ember.set, get = Ember.get, guidFor = Ember.guidFor; -var forEach = Ember.EnumerableUtils.forEach; +var forEach = Ember.EnumerableUtils.forEach, + indexOf = Ember.ArrayPolyfills.indexOf; var EachArray = Ember.Object.extend(Ember.Array, { @@ -13523,7 +16699,7 @@ function removeObserverForContentKey(content, keyName, proxy, idx, loc) { guid = guidFor(item); indicies = objects[guid]; - indicies[indicies.indexOf(loc)] = null; + indicies[indexOf.call(indicies, loc)] = null; } } } @@ -13672,7 +16848,7 @@ Ember.EachProxy = Ember.Object.extend({ */ -var get = Ember.get, set = Ember.set; +var get = Ember.get, set = Ember.set, replace = Ember.EnumerableUtils._replace; // Add Ember.Array to Array.prototype. Remove methods with native // implementations and supply some more optimized versions of generic methods @@ -13694,7 +16870,7 @@ var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember // primitive for array support. replace: function(idx, amt, objects) { - if (this.isFrozen) throw Ember.FROZEN_ERROR ; + if (this.isFrozen) throw Ember.FROZEN_ERROR; // if we replaced exactly the same number of items, then pass only the // replaced range. Otherwise, pass the full remaining array length @@ -13703,14 +16879,13 @@ var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember this.arrayContentWillChange(idx, amt, len); if (!objects || objects.length === 0) { - this.splice(idx, amt) ; + this.splice(idx, amt); } else { - var args = [idx, amt].concat(objects) ; - this.splice.apply(this,args) ; + replace(this, idx, amt, objects); } this.arrayContentDidChange(idx, amt, len); - return this ; + return this; }, // If you ask for an unknown property, then try to collect the value @@ -13787,7 +16962,26 @@ Ember.NativeArray = NativeArray; /** Creates an `Ember.NativeArray` from an Array like object. - Does not modify the original object. + Does not modify the original object. Ember.A is not needed if + `Ember.EXTEND_PROTOTYPES` is `true` (the default value). However, + it is recommended that you use Ember.A when creating addons for + ember or when you can not garentee that `Ember.EXTEND_PROTOTYPES` + will be `true`. + + Example + + ```js + var Pagination = Ember.CollectionView.extend({ + tagName: 'ul', + classNames: ['pagination'], + init: function() { + this._super(); + if (!this.get('content')) { + this.set('content', Ember.A([])); + } + } + }); + ``` @method A @for Ember @@ -13800,7 +16994,17 @@ Ember.A = function(arr) { /** Activates the mixin on the Array.prototype if not already applied. Calling - this method more than once is safe. + this method more than once is safe. This will be called when ember is loaded + unless you have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` + set to `false`. + + Example + + ```js + if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) { + Ember.NativeArray.activate(); + } + ``` @method activate @for Ember.NativeArray @@ -13895,8 +17099,8 @@ var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, isNone = Ember.is When using `Ember.Set`, you can observe the `"[]"` property to be alerted whenever the content changes. You can also add an enumerable observer to the set to be notified of specific objects that are added and - removed from the set. See `Ember.Enumerable` for more information on - enumerables. + removed from the set. See [Ember.Enumerable](/api/classes/Ember.Enumerable.html) + for more information on enumerables. This is often unhelpful. If you are filtering sets of objects, for instance, it is very inefficient to re-filter all of the items each time the set @@ -14311,6 +17515,20 @@ var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {}; var loaded = {}; /** + +Detects when a specific package of Ember (e.g. 'Ember.Handlebars') +has fully loaded and is available for extension. + +The provided `callback` will be called with the `name` passed +resolved from a string into the object: + +```javascript +Ember.onLoad('Ember.Handlebars' function(hbars){ + hbars.registerHelper(...); +}); +``` + + @method onLoad @for Ember @param name {String} name of hook @@ -14328,6 +17546,10 @@ Ember.onLoad = function(name, callback) { }; /** + +Called when an Ember.js package (e.g Ember.Handlebars) has finished +loading. Triggers any callbacks registered for this event. + @method runLoadHooks @for Ember @param name {String} name of hook @@ -14366,39 +17588,18 @@ var get = Ember.get; compose Ember's controller layer: `Ember.Controller`, `Ember.ArrayController`, and `Ember.ObjectController`. - Within an `Ember.Router`-managed application single shared instaces of every - Controller object in your application's namespace will be added to the - application's `Ember.Router` instance. See `Ember.Application#initialize` - for additional information. - - ## Views - - By default a controller instance will be the rendering context - for its associated `Ember.View.` This connection is made during calls to - `Ember.ControllerMixin#connectOutlet`. - - Within the view's template, the `Ember.View` instance can be accessed - through the controller with `{{view}}`. - - ## Target Forwarding - - By default a controller will target your application's `Ember.Router` - instance. Calls to `{{action}}` within the template of a controller's view - are forwarded to the router. See `Ember.Handlebars.helpers.action` for - additional information. - @class ControllerMixin @namespace Ember */ -Ember.ControllerMixin = Ember.Mixin.create({ +Ember.ControllerMixin = Ember.Mixin.create(Ember.ActionHandler, { /* ducktype as a controller */ isController: true, /** - The object to which events from the view should be sent. + The object to which actions from the view should be sent. For example, when a Handlebars template uses the `{{action}}` helper, - it will attempt to send the event to the view's controller's `target`. + it will attempt to send the action to the view's controller's `target`. By default, a controller's `target` is set to the router after it is instantiated by `Ember.Application#initialize`. @@ -14416,16 +17617,16 @@ Ember.ControllerMixin = Ember.Mixin.create({ model: Ember.computed.alias('content'), - send: function(actionName) { - var args = [].slice.call(arguments, 1), target; + deprecatedSendHandles: function(actionName) { + return !!this[actionName]; + }, - if (this[actionName]) { - Ember.assert("The controller " + this + " does not have the action " + actionName, typeof this[actionName] === 'function'); - this[actionName].apply(this, args); - } else if (target = get(this, 'target')) { - Ember.assert("The target for controller " + this + " (" + target + ") did not define a `send` method", typeof target.send === 'function'); - target.send.apply(target, arguments); - } + deprecatedSend: function(actionName) { + var args = [].slice.call(arguments, 1); + Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function'); + Ember.deprecate('Action handlers implemented directly on controllers are deprecated in favor of action handlers on an `actions` object (' + actionName + ' on ' + this + ')', false); + this[actionName].apply(this, args); + return; } }); @@ -14474,6 +17675,29 @@ var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach; songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'} ``` + If you add or remove the properties to sort by or change the sort direction the content + sort order will be automatically updated. + + ```javascript + songsController.set('sortProperties', ['title']); + songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'} + + songsController.toggleProperty('sortAscending'); + songsController.get('firstObject'); // {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'} + ``` + + SortableMixin works by sorting the arrangedContent array, which is the array that + arrayProxy displays. Due to the fact that the underlying 'content' array is not changed, that + array will not display the sorted list: + + ```javascript + songsController.get('content').get('firstObject'); // Returns the unsorted original content + songsController.get('firstObject'); // Returns the sorted content. + ``` + + Although the sorted content can also be accessed through the arrangedContent property, + it is preferable to use the proxied class and not the arrangedContent array directly. + @class SortableMixin @namespace Ember @uses Ember.MutableEnumerable @@ -14483,6 +17707,9 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { /** Specifies which properties dictate the arrangedContent's sort order. + When specifying multiple properties the sorting will use properties + from the `sortProperties` array prioritized from first to last. + @property {Array} sortProperties */ sortProperties: null, @@ -14496,7 +17723,7 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { /** The function used to compare two values. You can override this if you - want to do custom comparisons.Functions must be of the type expected by + want to do custom comparisons. Functions must be of the type expected by Array#sort, i.e. return 0 if the two parameters are equal, return a negative value if the first parameter is smaller than the second or @@ -14553,6 +17780,13 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { isSorted: Ember.computed.bool('sortProperties'), + /** + Overrides the default arrangedContent from arrayProxy in order to sort by sortFunction. + Also sets up observers for each sortProperty on each item in the content Array. + + @property arrangedContent + */ + arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) { var content = get(this, 'content'), isSorted = get(this, 'isSorted'), @@ -14870,11 +18104,15 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, }, init: function() { - if (!this.get('content')) { Ember.defineProperty(this, 'content', undefined, Ember.A()); } this._super(); + this.set('_subControllers', Ember.A()); }, + content: Ember.computed(function () { + return Ember.A(); + }), + controllerAt: function(idx, object, controllerClass) { var container = get(this, 'container'), subControllers = get(this, '_subControllers'), @@ -14925,9 +18163,11 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, */ /** - `Ember.ObjectController` is part of Ember's Controller layer. + `Ember.ObjectController` is part of Ember's Controller layer. It is intended + to wrap a single object, proxying unhandled attempts to `get` and `set` to the underlying + content object, and to forward unhandled action attempts to its `target`. - `Ember.ObjectController` derives its functionality from its superclass + `Ember.ObjectController` derives this functionality from its superclass `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin. @class ObjectController @@ -15211,9 +18451,14 @@ function escapeAttribute(value) { final representation. `Ember.RenderBuffer` will generate HTML which can be pushed to the DOM. + ```javascript + var buffer = Ember.RenderBuffer('div'); + ``` + @class RenderBuffer @namespace Ember @constructor + @param {String} tagName tag name (such as 'div' or 'p') used for the buffer */ Ember.RenderBuffer = function(tagName) { return new Ember._RenderBuffer(tagName); @@ -16002,7 +19247,13 @@ Ember.warn("The VIEW_PRESERVES_CONTEXT flag has been removed and the functionali Ember.TEMPLATES = {}; /** - `Ember.CoreView` is + `Ember.CoreView` is an abstract class that exists to give view-like behavior + to both Ember's main view class `Ember.View` and other classes like + `Ember._SimpleMetamorphView` that don't need the fully functionaltiy of + `Ember.View`. + + Unless you have specific needs for `CoreView`, you will use `Ember.View` + in your applications. @class CoreView @namespace Ember @@ -16010,7 +19261,7 @@ Ember.TEMPLATES = {}; @uses Ember.Evented */ -Ember.CoreView = Ember.Object.extend(Ember.Evented, { +Ember.CoreView = Ember.Object.extend(Ember.Evented, Ember.ActionHandler, { isView: true, states: states, @@ -16125,6 +19376,18 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, { } }, + deprecatedSendHandles: function(actionName) { + return !!this[actionName]; + }, + + deprecatedSend: function(actionName) { + var args = [].slice.call(arguments, 1); + Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function'); + Ember.deprecate('Action handlers implemented directly on views are deprecated in favor of action handlers on an `actions` object (' + actionName + ' on ' + this + ')', false); + this[actionName].apply(this, args); + return; + }, + has: function(name) { return Ember.typeOf(this[name]) === 'function' || this._super(name); }, @@ -16226,7 +19489,7 @@ var EMPTY_ARRAY = []; The default HTML tag name used for a view's DOM representation is `div`. This can be customized by setting the `tagName` property. The following view -class: + class: ```javascript ParagraphView = Ember.View.extend({ @@ -16400,8 +19663,8 @@ class: will be removed. Both `classNames` and `classNameBindings` are concatenated properties. See - `Ember.Object` documentation for more information about concatenated - properties. + [Ember.Object](/api/classes/Ember.Object.html) documentation for more + information about concatenated properties. ## HTML Attributes @@ -16461,7 +19724,7 @@ class: Updates to the the property of an attribute binding will result in automatic update of the HTML attribute in the view's rendered HTML representation. - `attributeBindings` is a concatenated property. See `Ember.Object` + `attributeBindings` is a concatenated property. See [Ember.Object](/api/classes/Ember.Object.html) documentation for more information about concatenated properties. ## Templates @@ -16504,9 +19767,6 @@ class: Using a value for `templateName` that does not have a Handlebars template with a matching `data-template-name` attribute will throw an error. - Assigning a value to both `template` and `templateName` properties will throw - an error. - For views classes that may have a template later defined (e.g. as the block portion of a `{{view}}` Handlebars helper call in another template or in a subclass), you can provide a `defaultTemplate` property set to compiled @@ -16612,7 +19872,8 @@ class: ``` - See `Handlebars.helpers.yield` for more information. + See [Ember.Handlebars.helpers.yield](/api/classes/Ember.Handlebars.helpers.html#method_yield) + for more information. ## Responding to Browser Events @@ -16709,7 +19970,7 @@ class: ### Handlebars `{{action}}` Helper - See `Handlebars.helpers.action`. + See [Handlebars.helpers.action](/api/classes/Ember.Handlebars.helpers.html#method_action). ### Event Names @@ -16764,8 +20025,8 @@ class: ## Handlebars `{{view}}` Helper Other `Ember.View` instances can be included as part of a view's template by - using the `{{view}}` Handlebars helper. See `Handlebars.helpers.view` for - additional information. + using the `{{view}}` Handlebars helper. See [Ember.Handlebars.helpers.view](/api/classes/Ember.Handlebars.helpers.html#method_view) + for additional information. @class View @namespace Ember @@ -17806,7 +21067,7 @@ Ember.View = Ember.CoreView.extend( visually challenged users navigate rich web applications. The full list of valid WAI-ARIA roles is available at: - http://www.w3.org/TR/wai-aria/roles#roles_categorization + [http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org/TR/wai-aria/roles#roles_categorization) @property ariaRole @type String @@ -18038,6 +21299,10 @@ Ember.View = Ember.CoreView.extend( @return {Ember.View} new instance */ createChildView: function(view, attrs) { + if (!view) { + throw new TypeError("createChildViews first argument must exist"); + } + if (view.isView && view._parentView === this && view.container === this.container) { return view; } @@ -18281,7 +21546,7 @@ Ember.View.reopenClass({ Parse a path and return an object which holds the parsed properties. - For example a path like "content.isEnabled:enabled:disabled" wil return the + For example a path like "content.isEnabled:enabled:disabled" will return the following object: ```javascript @@ -19044,7 +22309,7 @@ Ember.ContainerView = Ember.View.extend(Ember.MutableArray, { length: Ember.computed(function () { return this._childViews.length; - }), + }).volatile(), /** @private @@ -19374,11 +22639,6 @@ var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt; manipulated. Instead, add, remove, replace items from its `content` property. This will trigger appropriate changes to its rendered HTML. - ## Use in templates via the `{{collection}}` `Ember.Handlebars` helper - - `Ember.Handlebars` provides a helper specifically for adding - `CollectionView`s to templates. See `Ember.Handlebars.collection` for more - details @class CollectionView @namespace Ember @@ -19423,12 +22683,25 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie */ itemViewClass: Ember.View, + /** + Setup a CollectionView + + @method init + */ init: function() { var ret = this._super(); this._contentDidChange(); return ret; }, + /** + @private + + Invoked when the content property is about to change. Notifies observers that the + entire array content will change. + + @method _contentWillChange + */ _contentWillChange: Ember.beforeObserver(function() { var content = this.get('content'); @@ -19459,10 +22732,22 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie this.arrayDidChange(content, 0, null, len); }, 'content'), + /** + @private + + Ensure that the content implements Ember.Array + + @method _assertArrayLike + */ _assertArrayLike: function(content) { Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content)); }, + /** + Removes the content and content observers. + + @method destroy + */ destroy: function() { if (!this._super()) { return; } @@ -19476,6 +22761,19 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie return this; }, + /** + Called when a mutation to the underlying content array will occur. + + This method will remove any views that are no longer in the underlying + content array. + + Invokes whenever the content array itself will change. + + @method arrayWillChange + @param {Array} content the managed collection of objects + @param {Number} start the index at which the changes will occurr + @param {Number} removed number of object to be removed from content + */ arrayWillChange: function(content, start, removedCount) { // If the contents were empty before and this template collection has an // empty view remove it now. @@ -19566,6 +22864,21 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie this.replace(start, 0, addedViews); }, + /** + Instantiates a view to be added to the childViews array during view + initialization. You generally will not call this method directly unless + you are overriding `createChildViews()`. Note that this method will + automatically configure the correct settings on the new view instance to + act as a child of the parent. + + The tag name for the view will be set to the tagName of the viewClass + passed in. + + @method createChildView + @param {Class} viewClass + @param {Hash} [attrs] Attributes to add + @return {Ember.View} new instance + */ createChildView: function(view, attrs) { view = this._super(view, attrs); @@ -19634,7 +22947,7 @@ var get = Ember.get, set = Ember.set, isNone = Ember.isNone; ```html

{{person.title}}

- +

{{person.signature}}

``` @@ -19656,15 +22969,18 @@ var get = Ember.get, set = Ember.set, isNone = Ember.isNone; If you want to customize the component, in order to handle events or actions, you implement a subclass of `Ember.Component` named after the name of the - component. + component. Note that `Component` needs to be appended to the name of + your subclass like `AppProfileComponent`. For example, you could implement the action `hello` for the `app-profile` component: - ```js + ```javascript App.AppProfileComponent = Ember.Component.extend({ - hello: function(name) { - console.log("Hello", name) + actions: { + hello: function(name) { + console.log("Hello", name); + } } }); ``` @@ -19717,7 +23033,8 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { view.appendChild(Ember.View, { isVirtual: true, tagName: '', - template: get(this, 'template'), + _contextView: parentView, + template: template, context: get(parentView, 'context'), controller: get(parentView, 'controller'), templateData: { keywords: parentView.cloneKeywords() } @@ -19725,6 +23042,14 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { } }, + /** + If the component is currently inserted into the DOM of a parent view, this + property will point to the controller of the parent view. + + @property targetObject + @type Ember.Controller + @default null + */ targetObject: Ember.computed(function(key) { var parentView = get(this, '_parentView'); return parentView ? get(parentView, 'controller') : null; @@ -19761,15 +23086,17 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { target's action method. Example: ```javascript - App.MyTree = Ember.Component.extend({ + App.MyTreeComponent = Ember.Component.extend({ click: function() { this.sendAction('didClickTreeNode', this.get('node')); } }); App.CategoriesController = Ember.Controller.extend({ - didClickCategory: function(category) { - //Do something with the node/category that was clicked + actions: { + didClickCategory: function(category) { + //Do something with the node/category that was clicked + } } }); ``` @@ -20013,6 +23340,14 @@ define("metamorph", range.insertNode(fragment); }; + /** + * @public + * + * Remove this object (including starting and ending + * placeholders). + * + * @method remove + */ removeFunc = function() { // get a range for the current metamorph object including // the starting and ending placeholders. @@ -20053,7 +23388,7 @@ define("metamorph", }; } else { - /** + /* * This code is mostly taken from jQuery, with one exception. In jQuery's case, we * have some HTML and we need to figure out how to convert it into some nodes. * @@ -20107,12 +23442,12 @@ define("metamorph", } }; - /** + /* * Given a parent node and some HTML, generate a set of nodes. Return the first * node, which will allow us to traverse the rest using nextSibling. * * We need to do this because innerHTML in IE does not really parse the nodes. - **/ + */ var firstNodeFor = function(parentNode, html) { var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default; var depth = arr[0], start = arr[1], end = arr[2]; @@ -20145,7 +23480,7 @@ define("metamorph", return element; }; - /** + /* * In some cases, Internet Explorer can create an anonymous node in * the hierarchy with no tagName. You can create this scenario via: * @@ -20155,7 +23490,7 @@ define("metamorph", * * If our script markers are inside such a node, we need to find that * node and use *it* as the marker. - **/ + */ var realNode = function(start) { while (start.parentNode.tagName === "") { start = start.parentNode; @@ -20164,7 +23499,7 @@ define("metamorph", return start; }; - /** + /* * When automatically adding a tbody, Internet Explorer inserts the * tbody immediately before the first . Other browsers create it * before the first node, no matter what. @@ -20191,7 +23526,8 @@ define("metamorph", * * This code reparents the first script tag by making it the tbody's * first child. - **/ + * + */ var fixParentage = function(start, end) { if (start.parentNode !== end.parentNode) { end.parentNode.insertBefore(start, end.parentNode.firstChild); @@ -20458,16 +23794,7 @@ function makeBindings(options) { @param {String} dependentKeys* */ Ember.Handlebars.helper = function(name, value) { - if (Ember.Component.detect(value)) { - Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", name.match(/-/)); - - var proto = value.proto(); - if (!proto.layoutName && !proto.templateName) { - value.reopen({ - layoutName: 'components/' + name - }); - } - } + Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", !Ember.Component.detect(value) || name.match(/-/)); if (Ember.View.detect(value)) { Ember.Handlebars.registerHelper(name, function(options) { @@ -20520,7 +23847,6 @@ if (Handlebars.JavaScriptCompiler) { Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars"; - Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() { return "''"; }; @@ -20624,7 +23950,7 @@ if (Handlebars.compile) { var template = Ember.Handlebars.template(templateSpec); template.isMethod = false; //Make sure we don't wrap templates with ._super - + return template; }; } @@ -20888,61 +24214,101 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) { numProperties = properties.length, options = arguments[arguments.length - 1], normalizedProperties = [], + types = options.types, data = options.data, hash = options.hash, view = data.view, currentContext = (options.contexts && options.contexts[0]) || this, - normalized, - pathRoot, path, prefixPathForDependentKeys = '', - loc, hashOption; + prefixPathForDependentKeys = '', + loc, len, hashOption, + boundOption, property, + normalizedValue = Ember._SimpleHandlebarsView.prototype.normalizedValue; Ember.assert("registerBoundHelper-generated helpers do not support use with Handlebars blocks.", !options.fn); // Detect bound options (e.g. countBinding="otherCount") - hash.boundOptions = {}; + var boundOptions = hash.boundOptions = {}; for (hashOption in hash) { - if (!hash.hasOwnProperty(hashOption)) { continue; } - - if (Ember.IS_BINDING.test(hashOption) && typeof hash[hashOption] === 'string') { + if (Ember.IS_BINDING.test(hashOption)) { // Lop off 'Binding' suffix. - hash.boundOptions[hashOption.slice(0, -7)] = hash[hashOption]; + boundOptions[hashOption.slice(0, -7)] = hash[hashOption]; } } // Expose property names on data.properties object. + var watchedProperties = []; data.properties = []; for (loc = 0; loc < numProperties; ++loc) { data.properties.push(properties[loc]); - normalizedProperties.push(normalizePath(currentContext, properties[loc], data)); + if (types[loc] === 'ID') { + var normalizedProp = normalizePath(currentContext, properties[loc], data); + normalizedProperties.push(normalizedProp); + watchedProperties.push(normalizedProp); + } else { + normalizedProperties.push(null); + } } + // Handle case when helper invocation is preceded by `unbound`, e.g. + // {{unbound myHelper foo}} if (data.isUnbound) { return evaluateUnboundHelper(this, fn, normalizedProperties, options); } - if (dependentKeys.length === 0) { - return evaluateMultiPropertyBoundHelper(currentContext, fn, normalizedProperties, options); - } - - Ember.assert("Dependent keys can only be used with single-property helpers.", properties.length === 1); - - normalized = normalizedProperties[0]; - - pathRoot = normalized.root; - path = normalized.path; - - var bindView = new Ember._SimpleHandlebarsView( - path, pathRoot, !options.hash.unescaped, options.data - ); + var bindView = new Ember._SimpleHandlebarsView(null, null, !options.hash.unescaped, options.data); + // Override SimpleHandlebarsView's method for generating the view's content. bindView.normalizedValue = function() { - var value = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView); - return fn.call(view, value, options); + var args = [], boundOption; + + // Copy over bound hash options. + for (boundOption in boundOptions) { + if (!boundOptions.hasOwnProperty(boundOption)) { continue; } + property = normalizePath(currentContext, boundOptions[boundOption], data); + bindView.path = property.path; + bindView.pathRoot = property.root; + hash[boundOption] = normalizedValue.call(bindView); + } + + for (loc = 0; loc < numProperties; ++loc) { + property = normalizedProperties[loc]; + if (property) { + bindView.path = property.path; + bindView.pathRoot = property.root; + args.push(normalizedValue.call(bindView)); + } else { + args.push(properties[loc]); + } + } + args.push(options); + + // Run the supplied helper function. + return fn.apply(currentContext, args); }; view.appendChild(bindView); - view.registerObserver(pathRoot, path, bindView, bindView.rerender); + // Assemble list of watched properties that'll re-render this helper. + for (boundOption in boundOptions) { + if (boundOptions.hasOwnProperty(boundOption)) { + watchedProperties.push(normalizePath(currentContext, boundOptions[boundOption], data)); + } + } + + // Observe each property. + for (loc = 0, len = watchedProperties.length; loc < len; ++loc) { + property = watchedProperties[loc]; + view.registerObserver(property.root, property.path, bindView, bindView.rerender); + } + + if (types[0] !== 'ID' || normalizedProperties.length === 0) { + return; + } + + // Add dependent key observers to the first param + var normalized = normalizedProperties[0], + pathRoot = normalized.root, + path = normalized.path; if(!Ember.isEmpty(path)) { prefixPathForDependentKeys = path + '.'; @@ -20956,68 +24322,6 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) { Ember.Handlebars.registerHelper(name, helper); }; -/** - @private - - Renders the unbound form of an otherwise bound helper function. - - @method evaluateMultiPropertyBoundHelper - @param {Function} fn - @param {Object} context - @param {Array} normalizedProperties - @param {String} options -*/ -function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, options) { - var numProperties = normalizedProperties.length, - data = options.data, - view = data.view, - hash = options.hash, - boundOptions = hash.boundOptions, - watchedProperties, - boundOption, bindView, loc, property, len; - - bindView = new Ember._SimpleHandlebarsView(null, null, !hash.unescaped, data); - bindView.normalizedValue = function() { - var args = [], boundOption; - - // Copy over bound options. - for (boundOption in boundOptions) { - if (!boundOptions.hasOwnProperty(boundOption)) { continue; } - property = normalizePath(context, boundOptions[boundOption], data); - bindView.path = property.path; - bindView.pathRoot = property.root; - hash[boundOption] = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView); - } - - for (loc = 0; loc < numProperties; ++loc) { - property = normalizedProperties[loc]; - bindView.path = property.path; - bindView.pathRoot = property.root; - args.push(Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView)); - } - args.push(options); - return fn.apply(context, args); - }; - - view.appendChild(bindView); - - // Assemble list of watched properties that'll re-render this helper. - watchedProperties = []; - for (boundOption in boundOptions) { - if (boundOptions.hasOwnProperty(boundOption)) { - watchedProperties.push(normalizePath(context, boundOptions[boundOption], data)); - } - } - watchedProperties = watchedProperties.concat(normalizedProperties); - - // Observe each property. - for (loc = 0, len = watchedProperties.length; loc < len; ++loc) { - property = watchedProperties[loc]; - view.registerObserver(property.root, property.path, bindView, bindView.rerender); - } - -} - /** @private @@ -21067,19 +24371,19 @@ Ember.Handlebars.template = function(spec) { (function() { /** - * Mark a string as safe for unescaped output with Handlebars. If you - * return HTML from a Handlebars helper, use this function to - * ensure Handlebars does not escape the HTML. - * - * ```javascript - * Ember.String.htmlSafe('
someString
') - * ``` - * - * @method htmlSafe - * @for Ember.String - * @static - * @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars - */ + Mark a string as safe for unescaped output with Handlebars. If you + return HTML from a Handlebars helper, use this function to + ensure Handlebars does not escape the HTML. + + ```javascript + Ember.String.htmlSafe('
someString
') + ``` + + @method htmlSafe + @for Ember.String + @static + @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars +*/ Ember.String.htmlSafe = function(str) { return new Handlebars.SafeString(str); }; @@ -21089,18 +24393,18 @@ var htmlSafe = Ember.String.htmlSafe; if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { /** - * Mark a string as being safe for unescaped output with Handlebars. - * - * ```javascript - * '
someString
'.htmlSafe() - * ``` - * - * See `Ember.String.htmlSafe`. - * - * @method htmlSafe - * @for String - * @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars - */ + Mark a string as being safe for unescaped output with Handlebars. + + ```javascript + '
someString
'.htmlSafe() + ``` + + See [Ember.String.htmlSafe](/api/classes/Ember.String.html#method_htmlSafe). + + @method htmlSafe + @for String + @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars + */ String.prototype.htmlSafe = function() { return htmlSafe(this); }; @@ -21280,6 +24584,8 @@ function SimpleHandlebarsView(path, pathRoot, isEscaped, templateData) { this.morph = Metamorph(); this.state = 'preRender'; this.updateId = null; + this._parentView = null; + this.buffer = null; } Ember._SimpleHandlebarsView = SimpleHandlebarsView; @@ -21293,7 +24599,11 @@ SimpleHandlebarsView.prototype = { Ember.run.cancel(this.updateId); this.updateId = null; } + if (this._parentView) { + this._parentView.removeChild(this); + } this.morph = null; + this.state = 'destroyed'; }, propertyWillChange: Ember.K, @@ -21348,7 +24658,7 @@ SimpleHandlebarsView.prototype = { rerender: function() { switch(this.state) { case 'preRender': - case 'destroying': + case 'destroyed': break; case 'inBuffer': throw new Ember.Error("Something you did tried to replace an {{expression}} before it was inserted into the DOM."); @@ -21863,7 +25173,7 @@ EmberHandlebars.registerHelper('with', function(context, options) { /** - See `boundIf` + See [boundIf](/api/classes/Ember.Handlebars.helpers.html#method_boundIf) @method if @for Ember.Handlebars.helpers @@ -21898,11 +25208,11 @@ EmberHandlebars.registerHelper('unless', function(context, options) { }); /** - `bindAttr` allows you to create a binding between DOM element attributes and + `bind-attr` allows you to create a binding between DOM element attributes and Ember objects. For example: ```handlebars - imageTitle + imageTitle ``` The above handlebars template will fill the ``'s `src` attribute will @@ -21924,17 +25234,17 @@ EmberHandlebars.registerHelper('unless', function(context, options) { A humorous image of a cat ``` - `bindAttr` cannot redeclare existing DOM element attributes. The use of `src` - in the following `bindAttr` example will be ignored and the hard coded value + `bind-attr` cannot redeclare existing DOM element attributes. The use of `src` + in the following `bind-attr` example will be ignored and the hard coded value of `src="/failwhale.gif"` will take precedence: ```handlebars - imageTitle + imageTitle ``` - ### `bindAttr` and the `class` attribute + ### `bind-attr` and the `class` attribute - `bindAttr` supports a special syntax for handling a number of cases unique + `bind-attr` supports a special syntax for handling a number of cases unique to the `class` DOM element attribute. The `class` attribute combines multiple discreet values into a single attribute as a space-delimited list of strings. Each string can be: @@ -21943,7 +25253,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { * a boolean return value of an object's property * a hard-coded value - A string return value works identically to other uses of `bindAttr`. The + A string return value works identically to other uses of `bind-attr`. The return value of the property will become the value of the attribute. For example, the following view and template: @@ -21956,7 +25266,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { ``` ```handlebars - ``` Result in the following rendered output: @@ -21978,7 +25288,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { ``` ```handlebars - + ``` Result in the following rendered output: @@ -21992,14 +25302,14 @@ EmberHandlebars.registerHelper('unless', function(context, options) { value changes: ```handlebars - + ``` A hard-coded value can be used by prepending `:` to the desired class name: `:class-name-to-always-apply`. ```handlebars - + ``` Results in the following rendered output: @@ -22012,19 +25322,19 @@ EmberHandlebars.registerHelper('unless', function(context, options) { hard-coded value – can be combined in a single declaration: ```handlebars - + ``` - @method bindAttr + @method bind-attr @for Ember.Handlebars.helpers @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('bindAttr', function(options) { +EmberHandlebars.registerHelper('bind-attr', function(options) { var attrs = options.hash; - Ember.assert("You must specify at least one hash argument to bindAttr", !!Ember.keys(attrs).length); + Ember.assert("You must specify at least one hash argument to bind-attr", !!Ember.keys(attrs).length); var view = options.data.view; var ret = []; @@ -22086,7 +25396,7 @@ EmberHandlebars.registerHelper('bindAttr', function(options) { // When the observer fires, find the element using the // unique data id and update the attribute to the new value. // Note: don't add observer when path is 'this' or path - // is whole keyword e.g. {{#each x in list}} ... {{bindAttr attr="x"}} + // is whole keyword e.g. {{#each x in list}} ... {{bind-attr attr="x"}} if (path !== 'this' && !(normalized.isKeyword && normalized.path === '' )) { view.registerObserver(normalized.root, normalized.path, observer); } @@ -22106,6 +25416,18 @@ EmberHandlebars.registerHelper('bindAttr', function(options) { return new EmberHandlebars.SafeString(ret.join(' ')); }); +/** + See `bind-attr` + + @method bindAttr + @for Ember.Handlebars.helpers + @deprecated + @param {Function} context + @param {Hash} options + @return {String} HTML string +*/ +EmberHandlebars.registerHelper('bindAttr', EmberHandlebars.helpers['bind-attr']); + /** @private @@ -22585,8 +25907,8 @@ var get = Ember.get, handlebarsGet = Ember.Handlebars.get, fmt = Ember.String.fm /** `{{collection}}` is a `Ember.Handlebars` helper for adding instances of - `Ember.CollectionView` to a template. See `Ember.CollectionView` for - additional information on how a `CollectionView` functions. + `Ember.CollectionView` to a template. See [Ember.CollectionView](/api/classes/Ember.CollectionView.html) + for additional information on how a `CollectionView` functions. `{{collection}}`'s primary use is as a block helper with a `contentBinding` option pointing towards an `Ember.Array`-compatible object. An `Ember.View` @@ -23039,6 +26361,8 @@ GroupedEach.prototype = { }, addArrayObservers: function() { + if (!this.content) { return; } + this.content.addArrayObserver(this, { willChange: 'contentArrayWillChange', didChange: 'contentArrayDidChange' @@ -23046,6 +26370,8 @@ GroupedEach.prototype = { }, removeArrayObservers: function() { + if (!this.content) { return; } + this.content.removeArrayObserver(this, { willChange: 'contentArrayWillChange', didChange: 'contentArrayDidChange' @@ -23063,6 +26389,8 @@ GroupedEach.prototype = { }, render: function() { + if (!this.content) { return; } + var content = this.content, contentLength = get(content, 'length'), data = this.options.data, @@ -23075,12 +26403,21 @@ GroupedEach.prototype = { }, rerenderContainingView: function() { - Ember.run.scheduleOnce('render', this.containingView, 'rerender'); + var self = this; + Ember.run.scheduleOnce('render', this, function() { + // It's possible it's been destroyed after we enqueued a re-render call. + if (!self.destroyed) { + self.containingView.rerender(); + } + }); }, destroy: function() { this.removeContentObservers(); - this.removeArrayObservers(); + if (this.content) { + this.removeArrayObservers(); + } + this.destroyed = true; } }; @@ -23204,6 +26541,49 @@ GroupedEach.prototype = { Each itemController will receive a reference to the current controller as a `parentController` property. + ### (Experimental) Grouped Each + + When used in conjunction with the experimental [group helper](https://github.com/emberjs/group-helper), + you can inform Handlebars to re-render an entire group of items instead of + re-rendering them one at a time (in the event that they are changed en masse + or an item is added/removed). + + ```handlebars + {{#group}} + {{#each people}} + {{firstName}} {{lastName}} + {{/each}} + {{/group}} + ``` + + This can be faster than the normal way that Handlebars re-renders items + in some cases. + + If for some reason you have a group with more than one `#each`, you can make + one of the collections be updated in normal (non-grouped) fashion by setting + the option `groupedRows=true` (counter-intuitive, I know). + + For example, + + ```handlebars + {{dealershipName}} + + {{#group}} + {{#each dealers}} + {{firstName}} {{lastName}} + {{/each}} + + {{#each car in cars groupedRows=true}} + {{car.make}} {{car.model}} {{car.color}} + {{/each}} + {{/group}} + ``` + Any change to `dealershipName` or the `dealers` collection will cause the + entire group to be re-rendered. However, changes to the `cars` collection + will be re-rendered individually (as normal). + + Note that `group` behavior is also disabled by specifying an `itemViewClass`. + @method each @for Ember.Handlebars.helpers @param [name] {String} name for item (used with `in`) @@ -23211,6 +26591,7 @@ GroupedEach.prototype = { @param [options] {Object} Handlebars key/value pairs of options @param [options.itemViewClass] {String} a path to a view class used for each item @param [options.itemController] {String} name of a controller to be created for each item + @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper */ Ember.Handlebars.registerHelper('each', function(path, options) { if (arguments.length === 4) { @@ -23367,6 +26748,11 @@ Ember.Handlebars.registerHelper('partial', function(name, options) { var get = Ember.get, set = Ember.set; /** + + `{{yield}}` denotes an area of a template that will be rendered inside + of another template. It has two main uses: + + ### Use with `layout` When used in a Handlebars template that is assigned to an `Ember.View` instance's `layout` property Ember will render the layout template first, inserting the view's own rendered output at the `{{yield}}` location. @@ -23409,7 +26795,34 @@ var get = Ember.get, set = Ember.set; bView.appendTo('body'); // throws - // Uncaught Error: assertion failed: You called yield in a template that was not a layout + // Uncaught Error: assertion failed: + // You called yield in a template that was not a layout + ``` + + ### Use with Ember.Component + When designing components `{{yield}}` is used to denote where, inside the component's + template, an optional block passed to the component should render: + + ```handlebars + + {{#labeled-textfield value=someProperty}} + First name: + {{/my-component}} + ``` + + ```handlebars + + + ``` + + Result: + + ```html +