go back to sc-router
This commit is contained in:
parent
948807eedd
commit
50ff39d5c0
|
@ -16,6 +16,7 @@ input 'assets/javascripts' do
|
|||
vendor/ansiparse.js
|
||||
vendor/i18n.js
|
||||
vendor/jquery.timeago.js
|
||||
vendor/sc-routes.js
|
||||
)
|
||||
concat files, 'vendor.js'
|
||||
end
|
||||
|
|
|
@ -1,16 +1,54 @@
|
|||
$.mockjaxSettings.log = false
|
||||
require 'hax0rs'
|
||||
|
||||
# $.mockjaxSettings.log = false
|
||||
|
||||
@Travis = Em.Namespace.create
|
||||
run: ->
|
||||
@app = Travis.App.create(this)
|
||||
@app.initialize()
|
||||
|
||||
App: Em.Application.extend
|
||||
initialize: (router) ->
|
||||
$.extend(this, Travis.Controllers)
|
||||
$.extend(this, Travis.Views)
|
||||
@store = Travis.Store.create()
|
||||
@_super(router || Travis.Router.create())
|
||||
# ember wants this dependencies setup for connectOutlet
|
||||
$.extend this, Travis.Controllers
|
||||
$.extend this, Travis.Views
|
||||
for name, controller of Travis.Controllers
|
||||
name = name.charAt(0).toLowerCase() + name.substr(1)
|
||||
this[name] = controller.create(namespace: this, controllers: this)
|
||||
|
||||
run: ->
|
||||
@app = Travis.App.create()
|
||||
@app.initialize()
|
||||
@store = Travis.Store.create()
|
||||
@routes = Travis.Router.create(app: this)
|
||||
|
||||
@_super(Em.Object.create())
|
||||
@routes.start()
|
||||
|
||||
connectLayout: ->
|
||||
view = Travis.Views.ApplicationView.create()
|
||||
view.set('controller', @applicationController)
|
||||
view.appendTo(@get('rootElement') || 'body')
|
||||
|
||||
connectLeft: (repositories) ->
|
||||
@set('repositories', repositories)
|
||||
@get('applicationController').connectOutlet(outletName: 'left', name: 'repositories', context: repositories)
|
||||
|
||||
connectRepository: (repository) ->
|
||||
@set('repository', repository)
|
||||
@get('applicationController').connectOutlet(outletName: 'main', name: 'repository', context: repository)
|
||||
|
||||
connectTabs: (build, job) ->
|
||||
@setPath('tabsController.repository', @get('repository'))
|
||||
@setPath('tabsController.build', build)
|
||||
@setPath('tabsController.job', job)
|
||||
@get('repositoryController').connectOutlet(outletName: 'tabs', name: 'tabs')
|
||||
|
||||
connectBuilds: (builds) ->
|
||||
@get('repositoryController').connectOutlet(outletName: 'tab', name: 'history', context: builds)
|
||||
|
||||
connectBuild: (build) ->
|
||||
@get('repositoryController').connectOutlet(outletName: 'tab', name: 'build', context: build)
|
||||
|
||||
connectJob: (job) ->
|
||||
@get('repositoryController').connectOutlet(outletName: 'tab', name: 'job', context: job)
|
||||
|
||||
|
||||
require 'ext/jquery'
|
||||
|
|
|
@ -1,35 +1,50 @@
|
|||
@Travis.Urls =
|
||||
repository: (repository) ->
|
||||
"#!/#{repository.get('slug')}" if repository
|
||||
|
||||
lastBuild: (repository) ->
|
||||
"#!/#{repository.get('slug')}/builds/#{repository.get('lastBuildId')}" if repository
|
||||
|
||||
builds: (repository) ->
|
||||
"#!/#{repository.get('slug')}/builds" if repository
|
||||
|
||||
build: (repository, build) ->
|
||||
"#!/#{repository.get('slug')}/builds/#{build.get('id')}" if repository && build
|
||||
|
||||
job: (repository, job) ->
|
||||
"#!/#{repository.get('slug')}/jobs/#{job.get('id')}" if repository && job
|
||||
|
||||
Repository:
|
||||
urlGithub: (->
|
||||
'http://github.com/%@'.fmt @get('slug')
|
||||
"http://github.com/#{@get('slug')}"
|
||||
).property('slug'),
|
||||
|
||||
urlGithubWatchers: (->
|
||||
'http://github.com/%@/watchers'.fmt @get('slug')
|
||||
"http://github.com/#{@get('slug')}/watchers"
|
||||
).property('slug'),
|
||||
|
||||
urlGithubNetwork: (->
|
||||
'http://github.com/%@/network'.fmt @get('slug')
|
||||
"http://github.com/#{@get('slug')}/network"
|
||||
).property('slug'),
|
||||
|
||||
urlGithubAdmin: (->
|
||||
'http://github.com/%@/admin/hooks#travis_minibucket'.fmt @get('slug')
|
||||
"http://github.com/#{@get('slug')}/admin/hooks#travis_minibucket"
|
||||
).property('slug')
|
||||
|
||||
statusImage: (->
|
||||
'%@.png'.fmt @get('slug')
|
||||
"#{@get('slug')}.png"
|
||||
).property('slug')
|
||||
|
||||
Commit:
|
||||
urlGithubCommit: (->
|
||||
'http://github.com/%@/commit/%@'.fmt @getPath('repository.slug'), @getPath('commit.sha')
|
||||
"http://github.com/#{@getPath('repository.slug')}/commit/#{@getPath('commit.sha')}"
|
||||
).property('repository.slug', 'commit.sha')
|
||||
|
||||
urlAuthor: (->
|
||||
'mailto:%@'.fmt @getPath('commit.authorEmail')
|
||||
).property()
|
||||
"mailto:#{@getPath('commit.authorEmail')}"
|
||||
).property('commit.authorEmail')
|
||||
|
||||
urlCommitter: (->
|
||||
'mailto:%@'.fmt @getPath('commit.committerEmail')
|
||||
).property()
|
||||
"mailto:#{@getPath('commit.committerEmail')}"
|
||||
).property('commit.committerEmail')
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ require 'travis/model'
|
|||
|
||||
@Travis.Repository = Travis.Model.extend
|
||||
slug: DS.attr('string')
|
||||
owner: DS.attr('string')
|
||||
name: DS.attr('string')
|
||||
description: DS.attr('string')
|
||||
lastBuildId: DS.attr('number')
|
||||
lastBuildNumber: DS.attr('string')
|
||||
|
@ -9,8 +11,6 @@ require 'travis/model'
|
|||
lastBuildStarted_at: DS.attr('string')
|
||||
lastBuildFinished_at: DS.attr('string')
|
||||
|
||||
primaryKey: 'slug'
|
||||
|
||||
lastBuild: DS.belongsTo('Travis.Build')
|
||||
|
||||
builds: (->
|
||||
|
@ -21,14 +21,6 @@ require 'travis/model'
|
|||
Travis.Build.byRepositoryId @get('id'), event_type: 'pull_request'
|
||||
).property()
|
||||
|
||||
owner: (->
|
||||
(@get('slug') || @_id).split('/')[0]
|
||||
).property('owner', 'name'),
|
||||
|
||||
name: (->
|
||||
(@get('slug') || @_id).split('/')[1]
|
||||
).property('owner', 'name'),
|
||||
|
||||
lastBuildDuration: (->
|
||||
duration = @getPath('data.lastBuildDuration')
|
||||
duration = Travis.Helpers.durationFrom(@get('lastBuildStarted_at'), @get('lastBuildFinished_at')) unless duration
|
||||
|
|
|
@ -1,119 +1,71 @@
|
|||
require 'hax0rs'
|
||||
Travis.Router = Em.Object.extend
|
||||
ROUTES:
|
||||
'!/:owner/:name/jobs/:id/:line': 'job'
|
||||
'!/:owner/:name/jobs/:id': 'job'
|
||||
'!/:owner/:name/builds/:id': 'build'
|
||||
'!/:owner/:name/builds': 'builds'
|
||||
'!/:owner/:name/pull_requests': 'pullRequests'
|
||||
'!/:owner/:name/branch_summary': 'branches'
|
||||
'!/:owner/:name': 'current'
|
||||
'': 'index'
|
||||
|
||||
@Travis.Router = Em.Router.extend
|
||||
# enableLogging: true
|
||||
location: 'hash'
|
||||
init: () ->
|
||||
@app = @get('app')
|
||||
|
||||
root: Em.Route.extend
|
||||
# common "layout" state for all states that show a repo list on the left.
|
||||
# there also will be "profile" and "stats" states next to "default" that do
|
||||
# not have a 3-column layout
|
||||
default: Em.Route.extend
|
||||
route: '/'
|
||||
start: ->
|
||||
@app.connectLayout()
|
||||
@app.connectLeft(Travis.Repository.find())
|
||||
@route(route, action) for route, action of @ROUTES
|
||||
|
||||
viewCurrent: Ember.Route.transitionTo('repository.current')
|
||||
viewBuilds: Ember.Route.transitionTo('repository.builds')
|
||||
viewBuild: Ember.Route.transitionTo('repository.build')
|
||||
viewJob: Ember.Route.transitionTo('repository.job')
|
||||
route: (route, tab) ->
|
||||
Em.routes.add(route, (params) => this[tab](params))
|
||||
|
||||
connectOutlets: (router) ->
|
||||
repositories = Travis.Repository.find()
|
||||
router.set('repositories', repositories)
|
||||
router.set('job', undefined)
|
||||
router.connectLeft(repositories)
|
||||
index: (params) ->
|
||||
repositories = @app.get('repositories')
|
||||
onceLoaded repositories, =>
|
||||
repository = repositories.get('firstObject')
|
||||
@app.connectRepository(repository)
|
||||
@app.connectTabs()
|
||||
@app.connectBuild(repository.get('lastBuild'))
|
||||
|
||||
index: Em.Route.extend
|
||||
route: '/'
|
||||
current: (params) ->
|
||||
@repository params, (repository) =>
|
||||
@app.connectTabs()
|
||||
@app.connectBuild(repository.get('lastBuild'))
|
||||
|
||||
# on / we show the most recent repository from the repos list, so we
|
||||
# have to wait until it's loaded
|
||||
connectOutlets: (router) ->
|
||||
repositories = router.get('repositories')
|
||||
onceLoaded repositories, =>
|
||||
repository = repositories.get('firstObject')
|
||||
build = Travis.Build.find(repository.get('lastBuildId'))
|
||||
router.connectRepository(repository)
|
||||
router.connectTabs()
|
||||
router.connectBuild(build)
|
||||
builds: (params) ->
|
||||
@repository params, (repository) =>
|
||||
@app.connectTabs()
|
||||
@app.connectBuilds(repository.get('builds'))
|
||||
|
||||
repository: Em.Route.extend
|
||||
route: '/:owner/:name'
|
||||
build: (params) ->
|
||||
@repository params
|
||||
@buildBy params.id, (build) =>
|
||||
@app.connectTabs(build)
|
||||
@app.connectBuild(build)
|
||||
|
||||
serialize: (router, repository) ->
|
||||
router.serializeRepository(repository)
|
||||
job: (params) ->
|
||||
@repository params
|
||||
@jobBy params.id, (job) =>
|
||||
@app.connectTabs(job.get('build'), job)
|
||||
@app.connectJob(job)
|
||||
|
||||
deserialize: (router, params) ->
|
||||
router.deserializeRepository(params)
|
||||
repository: (params, callback) ->
|
||||
@repositoryBy params, (repository) =>
|
||||
@app.connectRepository(repository)
|
||||
callback(repository) if callback
|
||||
|
||||
connectOutlets: (router, repository) ->
|
||||
router.connectRepository(repository)
|
||||
repositoryBy: (params, callback) ->
|
||||
repositories = Travis.Repository.bySlug("#{params.owner}/#{params.name}")
|
||||
onceLoaded repositories, =>
|
||||
callback(repositories.get('firstObject'))
|
||||
|
||||
current: Em.Route.extend
|
||||
route: '/'
|
||||
|
||||
connectOutlets: (router) ->
|
||||
repository = router.get('repository')
|
||||
onceLoaded repository, -> # TODO should not need to wait here, right?
|
||||
build = repository.get('lastBuild')
|
||||
router.connectTabs()
|
||||
router.connectBuild(build)
|
||||
|
||||
builds: Em.Route.extend
|
||||
route: '/builds'
|
||||
|
||||
connectOutlets: (router) ->
|
||||
repository = router.get('repository')
|
||||
onceLoaded repository, => # TODO hrm, otherwise it gets builds?repository_id=null
|
||||
router.connectTabs()
|
||||
router.connectBuilds(repository.get('builds'))
|
||||
|
||||
build: Em.Route.extend
|
||||
route: '/builds/:build_id'
|
||||
|
||||
connectOutlets: (router, build) ->
|
||||
build = Travis.Build.find(build.id) unless build instanceof Travis.Build # what?
|
||||
router.connectTabs(build)
|
||||
router.connectBuild(build)
|
||||
|
||||
job: Em.Route.extend
|
||||
route: '/jobs/:job_id'
|
||||
|
||||
connectOutlets: (router, job) ->
|
||||
job = Travis.Job.find(job.id) unless job instanceof Travis.Job # what?
|
||||
router.connectTabs(job.get('build'), job)
|
||||
router.connectJob(job)
|
||||
|
||||
|
||||
connectLeft: (repositories) ->
|
||||
@get('applicationController').connectOutlet(outletName: 'left', name: 'repositories', context: repositories)
|
||||
|
||||
connectRepository: (repository) ->
|
||||
@set('repository', repository)
|
||||
@get('applicationController').connectOutlet(outletName: 'main', name: 'repository', context: repository)
|
||||
|
||||
connectTabs: (build, job) ->
|
||||
@setPath('tabsController.repository', @get('repository'))
|
||||
@setPath('tabsController.build', build)
|
||||
@setPath('tabsController.job', job)
|
||||
@get('repositoryController').connectOutlet(outletName: 'tabs', name: 'tabs')
|
||||
|
||||
connectBuilds: (builds) ->
|
||||
@get('repositoryController').connectOutlet(outletName: 'tab', name: 'history', context: builds)
|
||||
|
||||
connectBuild: (build) ->
|
||||
@get('repositoryController').connectOutlet(outletName: 'tab', name: 'build', context: build)
|
||||
|
||||
connectJob: (job) ->
|
||||
@get('repositoryController').connectOutlet(outletName: 'tab', name: 'job', context: job)
|
||||
|
||||
|
||||
serializeRepository: (object) ->
|
||||
if object instanceof Travis.Repository
|
||||
slug = object.get('slug') || object._id # wat.
|
||||
{ owner: slug.split('/')[0], name: slug.split('/')[1] }
|
||||
else
|
||||
object
|
||||
|
||||
deserializeRepository: (params) ->
|
||||
Travis.Repository.find("#{params.owner}/#{params.name}")
|
||||
buildBy: (id, callback) =>
|
||||
build = Travis.Build.find(id)
|
||||
onceLoaded build, =>
|
||||
callback(build)
|
||||
|
||||
jobBy: (id, callback) ->
|
||||
job = Travis.Job.find(id)
|
||||
onceLoaded job, =>
|
||||
callback(job)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
{{#each build in content}}
|
||||
{{#view Travis.Views.BuildsItemView contextBinding="build"}}
|
||||
<tr>
|
||||
<td class="number"><a {{action viewBuild href=true}}>{{number}}</a></td>
|
||||
<td class="number"><a {{bindAttr href="view.urlBuild"}}>#{{number}}</a></td>
|
||||
<td class="commit"><a {{bindAttr href="view.urlGithubCommit"}}>{{formatCommit commit}}</a></td>
|
||||
<td class="message">{{{formatMessage commit.message short="true"}}}</td>
|
||||
<td class="duration" {{bindAttr title="started_at"}}>{{formatDuration duration}}</td>
|
||||
|
|
|
@ -1,46 +1,48 @@
|
|||
<div id="build" {{bindAttr class="classes"}}>
|
||||
<dl class="summary clearfix">
|
||||
<div class="left">
|
||||
<dt>{{t builds.name}}</dt>
|
||||
<dd class="number"><a {{action viewBuild href=true}}>{{number}}</a></dd>
|
||||
<dt class="finished_at_label">{{t builds.finished_at}}</dt>
|
||||
<dd class="finished_at timeago" {{bindAttr title="finished_at"}}>{{formatTime finished_at}}</dd>
|
||||
<dt>{{t builds.duration}}</dt>
|
||||
<dd class="duration" {{bindAttr title="started_at"}}>{{formatDuration duration}}</dd>
|
||||
</div>
|
||||
{{#unless isLoaded}}
|
||||
Loading ...
|
||||
{{else}}
|
||||
<div id="build" {{bindAttr class="classes"}}>
|
||||
<dl class="summary clearfix">
|
||||
<div class="left">
|
||||
<dt>{{t builds.name}}</dt>
|
||||
<dd class="number"><a {{bindAttr href="view.urlBuild"}}>{{number}}</a></dd>
|
||||
<dt class="finished_at_label">{{t builds.finished_at}}</dt>
|
||||
<dd class="finished_at timeago" {{bindAttr title="finished_at"}}>{{formatTime finished_at}}</dd>
|
||||
<dt>{{t builds.duration}}</dt>
|
||||
<dd class="duration" {{bindAttr title="started_at"}}>{{formatDuration duration}}</dd>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<dt>{{t builds.commit}}</dt>
|
||||
<dd class="commit"><a {{bindAttr href="urlGithubCommit"}}>{{formatCommit commit}}</a></dd>
|
||||
{{#if commit.compareUrl}}
|
||||
<dt>{{t builds.compare}}</dt>
|
||||
<dd class="compare"><a {{bindAttr href="commit.compareUrl"}}>{{pathFrom commit.compareUrl}}</a></dd>
|
||||
{{/if}}
|
||||
{{#if commit.authorName}}
|
||||
<dt>{{t builds.author}}</dt>
|
||||
<dd class="author"><a {{bindAttr href="urlAuthor"}}>{{commit.authorName}}</a></dd>
|
||||
{{/if}}
|
||||
{{#if commit.committerName}}
|
||||
<dt>{{t builds.committer}}</dt>
|
||||
<dd class="committer"><a {{bindAttr href="urlCommitter"}}>{{commit.committerName}}</a></dd>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="right">
|
||||
<dt>{{t builds.commit}}</dt>
|
||||
<dd class="commit"><a {{bindAttr href="urlGithubCommit"}}>{{formatCommit commit}}</a></dd>
|
||||
{{#if commit.compareUrl}}
|
||||
<dt>{{t builds.compare}}</dt>
|
||||
<dd class="compare"><a {{bindAttr href="commit.compareUrl"}}>{{pathFrom commit.compareUrl}}</a></dd>
|
||||
{{/if}}
|
||||
{{#if commit.authorName}}
|
||||
<dt>{{t builds.author}}</dt>
|
||||
<dd class="author"><a {{bindAttr href="urlAuthor"}}>{{commit.authorName}}</a></dd>
|
||||
{{/if}}
|
||||
{{#if commit.committerName}}
|
||||
<dt>{{t builds.committer}}</dt>
|
||||
<dd class="committer"><a {{bindAttr href="urlCommitter"}}>{{commit.committerName}}</a></dd>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<dt>{{t builds.message}}</dt>
|
||||
<dd class="message">{{{formatMessage commit.message}}}</dd>
|
||||
<dt>{{t builds.message}}</dt>
|
||||
<dd class="message">{{{formatMessage commit.message}}}</dd>
|
||||
|
||||
{{#unless isMatrix}}
|
||||
<dt>{{t builds.config}}</dt>
|
||||
<dd class="config">{{formatConfig config}}</dd>
|
||||
{{/unless}}
|
||||
</dl>
|
||||
{{#unless isMatrix}}
|
||||
<dt>{{t builds.config}}</dt>
|
||||
<dd class="config">{{formatConfig config}}</dd>
|
||||
{{/unless}}
|
||||
</dl>
|
||||
|
||||
{{#if isLoaded}}
|
||||
{{#if isMatrix}}
|
||||
{{view Travis.Views.JobsView jobsBinding="view.requiredJobs" required="true"}}
|
||||
{{view Travis.Views.JobsView jobsBinding="view.allowedFailureJobs"}}
|
||||
{{else}}
|
||||
{{view Travis.Views.LogView contextBinding="jobs.firstObject"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
|
|
@ -15,15 +15,17 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each view.jobs}}
|
||||
<tr {{bindAttr class="color"}}>
|
||||
<td class="number"><a {{action viewJob href=true}}>#{{number}}</a></td>
|
||||
<td class="duration" {{bindAttr title="started_at"}}>{{formatDuration duration}}</td>
|
||||
<td class="finished_at timeago" {{bindAttr title="finished_at"}}>{{formatTime finished_at}}</td>
|
||||
{{#each configValues}}
|
||||
<td>{{this}}</td>
|
||||
{{/each}}
|
||||
</tr>
|
||||
{{#each job in view.jobs}}
|
||||
{{#view Travis.Views.JobsItemView contextBinding="job"}}
|
||||
<tr {{bindAttr class="color"}}>
|
||||
<td class="number"><a {{bindAttr href="view.urlJob"}}>#{{number}}</a></td>
|
||||
<td class="duration" {{bindAttr title="started_at"}}>{{formatDuration duration}}</td>
|
||||
<td class="finished_at timeago" {{bindAttr title="finished_at"}}>{{formatTime finished_at}}</td>
|
||||
{{#each configValues}}
|
||||
<td>{{this}}</td>
|
||||
{{/each}}
|
||||
</tr>
|
||||
{{/view}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<dl class="summary clearfix">
|
||||
<div class="left">
|
||||
<dt>Job</dt>
|
||||
<dd class="number"><a {{action viewJob href=true}}>{{number}}</a></dd>
|
||||
<dd class="number"><a {{bindAttr href="view.urlJob"}}>{{number}}</a></dd>
|
||||
<dt class="finished_at_label">{{t jobs.finished_at}}</dt>
|
||||
<dd class="finished_at timeago" {{bindAttr title="finished_at"}}>{{formatTime finished_at}}</dd>
|
||||
<dt>{{t jobs.duration}}</dt>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
{{#if content.lastObject.isLoaded}}
|
||||
{{#unless content.lastObject.isLoaded}}
|
||||
Loading ...
|
||||
{{else}}
|
||||
<ul id="repositories">
|
||||
{{#each repository in content}}
|
||||
{{#view Travis.Views.RepositoriesItemView tagName="li" classBinding="classes" contextBinding="repository"}}
|
||||
<div class="wrapper">
|
||||
<a {{action viewCurrent href=true}} class="current">{{slug}}</a>
|
||||
<a {{action viewBuild href=true context="lastBuild"}} class="last_build">#{{lastBuildNumber}}</a>
|
||||
<a {{bindAttr href="view.urlRepository"}} class="current">{{slug}}</a>
|
||||
<a {{bindAttr href="view.urlLastBuild"}} class="last_build">#{{lastBuildNumber}}</a>
|
||||
</div>
|
||||
|
||||
<p class="summary">
|
||||
|
@ -20,4 +22,4 @@
|
|||
{{/view}}
|
||||
{{/each}}
|
||||
<ul>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
<div id="repository">
|
||||
<h3>
|
||||
<a {{bindAttr href="urlGithub"}}>{{slug}}</a>
|
||||
</h3>
|
||||
{{#unless isLoaded}}
|
||||
Loading ...
|
||||
{{else}}
|
||||
<div id="repository">
|
||||
<h3>
|
||||
<a {{bindAttr href="urlGithub"}}>{{slug}}</a>
|
||||
</h3>
|
||||
|
||||
<p class="description">{{description}}</p>
|
||||
<p class="description">{{description}}</p>
|
||||
|
||||
<ul class="github-stats">
|
||||
<li class="language">{{last_build_language}}</li>
|
||||
<li><a class="watchers" title="Watches" {{bindAttr href="urlGithubWatchers"}}>{{stats.watchers}}</a></li>
|
||||
<li><a class="forks" title="Forks" {{bindAttr href="urlGithubNetwork"}}>{{stats.forks}}</a></li>
|
||||
</ul>
|
||||
<ul class="github-stats">
|
||||
<li class="language">{{last_build_language}}</li>
|
||||
<li><a class="watchers" title="Watches" {{bindAttr href="urlGithubWatchers"}}>{{stats.watchers}}</a></li>
|
||||
<li><a class="forks" title="Forks" {{bindAttr href="urlGithubNetwork"}}>{{stats.forks}}</a></li>
|
||||
</ul>
|
||||
|
||||
{{outlet tabs}}
|
||||
{{outlet tabs}}
|
||||
|
||||
<div class="tab">
|
||||
{{outlet tab}}
|
||||
<div class="tab">
|
||||
{{outlet tab}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<ul class="tabs">
|
||||
<li><a {{action viewCurrent href=true context="repository"}} class="current">Current</a></li>
|
||||
<li><a {{action viewBuilds href=true context="repository"}} class="history">History</a></li>
|
||||
<li><a {{bindAttr href="view.urlRepository"}} class="current">Current</a></li>
|
||||
<li><a {{bindAttr href="view.urlBuilds"}} class="history">History</a></li>
|
||||
{{#if build}}
|
||||
<li><a {{action viewBuild href=true context="build"}} class="build">Build #{{build.number}}</a></li>
|
||||
<li><a {{bindAttr href="view.urlBuild"}} class="build">Build #{{build.number}}</a></li>
|
||||
{{/if}}
|
||||
{{#if job}}
|
||||
<li><a {{action viewJob href=true context="job"}} class="job">Job #{{job.number}}</a></li>
|
||||
<li><a {{bindAttr href="view.urlJob"}} class="job">Job #{{job.number}}</a></li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
|
|
|
@ -13,11 +13,13 @@ Travis.Views =
|
|||
classes.join(' ')
|
||||
).property('repository.lastBuildResult', 'repository.selected')
|
||||
|
||||
lastBuild: (->
|
||||
owner: @getPath('repository.owner')
|
||||
name: @getPath('repository.name')
|
||||
id: @getPath('repository.lastBuildId')
|
||||
).property('repository.owner', 'repository.name', 'repository.lastBuildId')
|
||||
urlRepository: (->
|
||||
Travis.Urls.repository(@get('context'))
|
||||
).property('context')
|
||||
|
||||
urlLastBuild: (->
|
||||
Travis.Urls.lastBuild(@get('context'))
|
||||
).property('context')
|
||||
|
||||
RepositoryView: Em.View.extend
|
||||
templateName: 'repositories/show'
|
||||
|
@ -25,19 +27,39 @@ Travis.Views =
|
|||
TabsView: Em.View.extend
|
||||
templateName: 'repositories/tabs'
|
||||
|
||||
urlRepository: (->
|
||||
Travis.Urls.repository(@getPath('controller.repository'))
|
||||
).property('controller.repository.id')
|
||||
|
||||
urlBuilds: (->
|
||||
Travis.Urls.builds(@getPath('controller.repository'))
|
||||
).property('controller.repository.id')
|
||||
|
||||
urlBuild: (->
|
||||
Travis.Urls.build(@getPath('controller.repository'), @getPath('controller.build'))
|
||||
).property('controller.repository.slug', 'controller.build.id')
|
||||
|
||||
urlJob: (->
|
||||
Travis.Urls.job(@getPath('controller.repository'), @getPath('controller.job'))
|
||||
).property('controller.repository.slug', 'controller.job.id')
|
||||
|
||||
HistoryView: Em.View.extend
|
||||
templateName: 'builds/list'
|
||||
|
||||
BuildsItemView: Em.View.extend
|
||||
classes: (->
|
||||
Travis.Helpers.colorForResult(@getPath('content.result'))
|
||||
).property('content.result')
|
||||
Travis.Helpers.colorForResult(@getPath('context.result'))
|
||||
).property('context.result')
|
||||
|
||||
urlBuild: (->
|
||||
Travis.Urls.build(@getPath('context.repository'), @get('context'))
|
||||
).property('context.repository.slug', 'context')
|
||||
|
||||
BuildView: Em.View.extend
|
||||
templateName: 'builds/show'
|
||||
|
||||
classes: (->
|
||||
Helpers.colorForResult(@get('result'))
|
||||
Travis.Helpers.colorForResult(@get('result'))
|
||||
).property('result')
|
||||
|
||||
requiredJobs: (->
|
||||
|
@ -48,9 +70,18 @@ Travis.Views =
|
|||
@getPath('context.jobs').filter((job) -> job.get('allow_failure'))
|
||||
).property()
|
||||
|
||||
urlBuild: (->
|
||||
Travis.Urls.build(@getPath('context.repository'), @get('context'))
|
||||
).property('controller.content.repository.id', 'controller.content.id')
|
||||
|
||||
JobsView: Em.View.extend
|
||||
templateName: 'jobs/list'
|
||||
|
||||
JobsItemView: Em.View.extend
|
||||
urlJob: (->
|
||||
Travis.Urls.job(@getPath('context.repository'), @get('context'))
|
||||
).property('context.repository', 'context')
|
||||
|
||||
JobView: Em.View.extend
|
||||
templateName: 'jobs/show'
|
||||
|
||||
|
@ -58,6 +89,10 @@ Travis.Views =
|
|||
Travis.Helpers.colorForResult(@get('result'))
|
||||
).property('result')
|
||||
|
||||
urlJob: (->
|
||||
Travis.Urls.job(@getPath('context.repository'), @get('context'))
|
||||
).property('controller.content.repository.id', 'controller.content.id')
|
||||
|
||||
LogView: Em.View.extend
|
||||
templateName: 'jobs/log'
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
responseTime = 0
|
||||
|
||||
repositories = [
|
||||
{ id: 1, owner: 'travis-ci', name: 'travis-core', slug: 'travis-ci/travis-core', build_ids: [1, 2], last_build_id: 1, last_build_number: 1, last_build_result: 0 },
|
||||
{ id: 2, owner: 'travis-ci', name: 'travis-assets', slug: 'travis-ci/travis-assets', build_ids: [3], last_build_id: 3, last_build_number: 3},
|
||||
|
@ -5,10 +7,10 @@ repositories = [
|
|||
]
|
||||
|
||||
builds = [
|
||||
{ id: 1, repository_id: 'travis-ci/travis-core', commit_id: 1, job_ids: [1, 2], number: 1, event_type: 'push', config: { rvm: ['rbx', '1.9.3'] }, finished_at: '2012-06-20T00:21:20Z', duration: 35, result: 0 },
|
||||
{ id: 2, repository_id: 'travis-ci/travis-core', commit_id: 2, job_ids: [3], number: 2, event_type: 'push', config: { rvm: ['rbx'] } },
|
||||
{ id: 3, repository_id: 'travis-ci/travis-assets', commit_id: 3, job_ids: [4], number: 3, event_type: 'push', config: { rvm: ['rbx'] }, finished_at: '2012-06-20T00:21:20Z', duration: 35, result: 0 },
|
||||
{ id: 4, repository_id: 'travis-ci/travis-hub', commit_id: 4, job_ids: [5], number: 4, event_type: 'push', config: { rvm: ['rbx'] } },
|
||||
{ id: 1, repository_id: '1', commit_id: 1, job_ids: [1, 2], number: 1, event_type: 'push', config: { rvm: ['rbx', '1.9.3'] }, finished_at: '2012-06-20T00:21:20Z', duration: 35, result: 0 },
|
||||
{ id: 2, repository_id: '1', commit_id: 2, job_ids: [3], number: 2, event_type: 'push', config: { rvm: ['rbx'] } },
|
||||
{ id: 3, repository_id: '2', commit_id: 3, job_ids: [4], number: 3, event_type: 'push', config: { rvm: ['rbx'] }, finished_at: '2012-06-20T00:21:20Z', duration: 35, result: 0 },
|
||||
{ id: 4, repository_id: '3', commit_id: 4, job_ids: [5], number: 4, event_type: 'push', config: { rvm: ['rbx'] } },
|
||||
]
|
||||
|
||||
commits = [
|
||||
|
@ -36,19 +38,19 @@ artifacts = [
|
|||
|
||||
$.mockjax
|
||||
url: '/repositories'
|
||||
responseTime: 0
|
||||
responseTime: responseTime
|
||||
responseText: { repositories: repositories }
|
||||
|
||||
for repository in repositories
|
||||
$.mockjax
|
||||
url: '/' + repository.slug
|
||||
responseTime: 0
|
||||
responseTime: responseTime
|
||||
responseText: { repository: repository }
|
||||
|
||||
for build in builds
|
||||
$.mockjax
|
||||
url: '/builds/' + build.id
|
||||
responseTime: 0
|
||||
responseTime: responseTime
|
||||
responseText:
|
||||
build: build,
|
||||
commit: commits[build.commit_id - 1]
|
||||
|
@ -57,8 +59,8 @@ for build in builds
|
|||
for repository in repositories
|
||||
$.mockjax
|
||||
url: '/builds'
|
||||
data: { repository_id: 1, event_type: 'push', orderBy: 'number DESC' }
|
||||
responseTime: 0
|
||||
data: { repository_id: repository.id, event_type: 'push', orderBy: 'number DESC' }
|
||||
responseTime: responseTime
|
||||
responseText:
|
||||
builds: (builds[id - 1] for id in repository.build_ids)
|
||||
commits: (commits[builds[id - 1].commit_id - 1] for id in repository.build_ids)
|
||||
|
@ -66,7 +68,7 @@ for repository in repositories
|
|||
for job in jobs
|
||||
$.mockjax
|
||||
url: '/jobs/' + job.id
|
||||
responseTime: 0
|
||||
responseTime: responseTime
|
||||
responseText:
|
||||
job: job,
|
||||
commit: commits[job.commit_id - 1]
|
||||
|
@ -74,7 +76,7 @@ for job in jobs
|
|||
for artifact in artifacts
|
||||
$.mockjax
|
||||
url: '/artifacts/' + artifact.id
|
||||
responseTime: 0
|
||||
responseTime: responseTime
|
||||
responseText:
|
||||
artifact: artifact
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
describe 'The current build tab', ->
|
||||
describe 'on the "index" state', ->
|
||||
beforeEach ->
|
||||
app '/'
|
||||
app ''
|
||||
waitFor buildRendered
|
||||
|
||||
it 'displays the build summary', ->
|
||||
|
@ -19,13 +19,13 @@ describe 'The current build tab', ->
|
|||
displaysBuildMatrix
|
||||
headers: ['Job', 'Duration', 'Finished', 'Rvm']
|
||||
jobs: [
|
||||
{ number: '#1.1', repo: 'travis-ci/travis-core', finishedAt: /\d+ (\w+) ago/, duration: '35 sec', rvm: 'rbx' },
|
||||
{ number: '#1.2', repo: 'travis-ci/travis-core', finishedAt: '-', duration: '-', rvm: '1.9.3' }
|
||||
{ id: 1, number: '#1.1', repo: 'travis-ci/travis-core', finishedAt: /\d+ (\w+) ago/, duration: '35 sec', rvm: 'rbx' },
|
||||
{ id: 2, number: '#1.2', repo: 'travis-ci/travis-core', finishedAt: '-', duration: '-', rvm: '1.9.3' }
|
||||
]
|
||||
|
||||
describe 'on the "current" state', ->
|
||||
beforeEach ->
|
||||
app '/travis-ci/travis-core'
|
||||
app '!/travis-ci/travis-core'
|
||||
waitFor repositoriesRendered
|
||||
waitFor buildRendered
|
||||
|
||||
|
@ -44,6 +44,6 @@ describe 'The current build tab', ->
|
|||
displaysBuildMatrix
|
||||
headers: ['Job', 'Duration', 'Finished', 'Rvm']
|
||||
jobs: [
|
||||
{ number: '#1.1', repo: 'travis-ci/travis-core', finishedAt: /\d+ (\w+) ago/, duration: '35 sec', rvm: 'rbx' },
|
||||
{ number: '#1.2', repo: 'travis-ci/travis-core', finishedAt: '-', duration: '-', rvm: '1.9.3' }
|
||||
{ id: 1, number: '#1.1', repo: 'travis-ci/travis-core', finishedAt: /\d+ (\w+) ago/, duration: '35 sec', rvm: 'rbx' },
|
||||
{ id: 2, number: '#1.2', repo: 'travis-ci/travis-core', finishedAt: '-', duration: '-', rvm: '1.9.3' }
|
||||
]
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
describe 'The repositories list', ->
|
||||
beforeEach ->
|
||||
app '/'
|
||||
app ''
|
||||
waitFor repositoriesRendered
|
||||
|
||||
it 'lists repositories', ->
|
||||
href = $('#repositories a.slug').attr('href')
|
||||
expect(href).toEqual '#/travis-ci/travis-core'
|
||||
href = $('#repositories a.current').attr('href')
|
||||
expect(href).toEqual '#!/travis-ci/travis-core'
|
||||
|
||||
it "links to the repository's last build action", ->
|
||||
href = $('#repositories a.last_build').attr('href')
|
||||
expect(href).toEqual '#/travis-ci/travis-core/builds/1'
|
||||
expect(href).toEqual '#!/travis-ci/travis-core/builds/1'
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
describe 'The repository view', ->
|
||||
beforeEach ->
|
||||
app '/'
|
||||
app ''
|
||||
waitFor repositoriesRendered
|
||||
|
||||
it 'displays the repository header', ->
|
||||
|
|
|
@ -2,17 +2,13 @@ minispade.require 'app'
|
|||
|
||||
@reset = ->
|
||||
Travis.app.destroy() if Travis.app
|
||||
$('body #content').empty()
|
||||
$('body #content').remove()
|
||||
|
||||
@app = (url) ->
|
||||
router = Travis.Router.create
|
||||
location: Em.NoneLocation.create()
|
||||
|
||||
Travis.app = Travis.App.create()
|
||||
Travis.app.set('rootElement', '#content')
|
||||
Travis.app.initialize(router)
|
||||
|
||||
router.route(url)
|
||||
$('body').append('<div id="content"></div>')
|
||||
Travis.app = Travis.App.create(rootElement: '#content')
|
||||
Travis.app.initialize()
|
||||
Em.routes.set('location', url)
|
||||
|
||||
beforeEach ->
|
||||
reset()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@repositoriesRendered = ->
|
||||
$('#repositories li').length > 0
|
||||
$('#repositories li a.current').text() != ''
|
||||
|
||||
@buildRendered = ->
|
||||
$('#build .summary .number').text() != ''
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@displaysBuildSummary = (data) ->
|
||||
element = $('#build .summary .number a')
|
||||
expect(element.attr('href')).toEqual "#/#{data.repo}/builds/#{data.id}"
|
||||
expect(element.attr('href')).toEqual "#!/#{data.repo}/builds/#{data.id}"
|
||||
|
||||
element = $('#build .summary .finished_at')
|
||||
expect(element.text()).toMatch /\d+ (\w+) ago/
|
||||
|
@ -34,7 +34,7 @@
|
|||
expect(element.text()).toEqual job.number
|
||||
|
||||
element = $("#jobs tr:nth-child(#{ix}) td.number a")
|
||||
expect(element.attr('href')).toEqual "#/#{job.repo}/jobs/#{job.id}"
|
||||
expect(element.attr('href')).toEqual "#!/#{job.repo}/jobs/#{job.id}"
|
||||
|
||||
element = $("#jobs tr:nth-child(#{ix}) td.duration")
|
||||
expect(element.text()).toEqual job.duration
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
# describe 'The tabs view', ->
|
||||
# describe 'on the "index" state', ->
|
||||
# beforeEach ->
|
||||
# app '/'
|
||||
# waitFor repositoriesRendered
|
||||
#
|
||||
# it 'has a "current" tab linking to the current build', ->
|
||||
# href = $('#main .tabs a.current').attr('href')
|
||||
# expect(href).toEqual '/travis-ci/travis-core'
|
||||
#
|
||||
# it 'has a "history" tab linking to the builds list', ->
|
||||
# href = $('#main .tabs a.history').attr('href')
|
||||
# expect(href).toEqual '/travis-ci/travis-core/builds'
|
||||
#
|
||||
# describe 'on the "current" state', ->
|
||||
# app '/travis-ci/travis-core'
|
||||
# waitFor repositoriesRendered
|
||||
#
|
||||
# it 'has a "current" tab linking to the current build', ->
|
||||
# href = $('#main .tabs a.current').attr('href')
|
||||
# expect(href).toEqual '/travis-ci/travis-core'
|
||||
#
|
||||
#
|
||||
describe 'The tabs view', ->
|
||||
describe 'on the "index" state', ->
|
||||
beforeEach ->
|
||||
app ''
|
||||
waitFor repositoriesRendered
|
||||
|
||||
it 'has a "current" tab linking to the current build', ->
|
||||
href = $('#main .tabs a.current').attr('href')
|
||||
expect(href).toEqual '#!/travis-ci/travis-core'
|
||||
|
||||
it 'has a "history" tab linking to the builds list', ->
|
||||
href = $('#main .tabs a.history').attr('href')
|
||||
expect(href).toEqual '#!/travis-ci/travis-core/builds'
|
||||
|
||||
describe 'on the "current" state', ->
|
||||
beforeEach ->
|
||||
app '!/travis-ci/travis-core'
|
||||
waitFor repositoriesRendered
|
||||
waitFor buildRendered
|
||||
|
||||
it 'has a "current" tab linking to the current build', ->
|
||||
href = $('#main .tabs a.current').attr('href')
|
||||
expect(href).toEqual '#!/travis-ci/travis-core'
|
||||
|
||||
|
||||
|
|
142
assets/javascripts/vendor/ember.js
vendored
142
assets/javascripts/vendor/ember.js
vendored
|
@ -944,7 +944,7 @@ Ember.isArray = function(obj) {
|
|||
Ember.makeArray(); => []
|
||||
Ember.makeArray(null); => []
|
||||
Ember.makeArray(undefined); => []
|
||||
Ember.makeArray('lindsay'); => ['lindsay']
|
||||
Ember.makeArray('lindsay'); => ['lindsay']
|
||||
Ember.makeArray([1,2,42]); => [1,2,42]
|
||||
|
||||
var controller = Ember.ArrayProxy.create({ content: [] });
|
||||
|
@ -3646,7 +3646,7 @@ Ember.RunLoop = RunLoop;
|
|||
call.
|
||||
|
||||
Ember.run(function(){
|
||||
// code to be execute within a RunLoop
|
||||
// code to be execute within a RunLoop
|
||||
});
|
||||
|
||||
@name run
|
||||
|
@ -3684,7 +3684,7 @@ var run = Ember.run;
|
|||
an lower-level way to use a RunLoop instead of using Ember.run().
|
||||
|
||||
Ember.run.begin();
|
||||
// code to be execute within a RunLoop
|
||||
// code to be execute within a RunLoop
|
||||
Ember.run.end();
|
||||
|
||||
|
||||
|
@ -3700,7 +3700,7 @@ Ember.run.begin = function() {
|
|||
instead of using Ember.run().
|
||||
|
||||
Ember.run.begin();
|
||||
// code to be execute within a RunLoop
|
||||
// code to be execute within a RunLoop
|
||||
Ember.run.end();
|
||||
|
||||
@returns {void}
|
||||
|
@ -5448,7 +5448,7 @@ Ember.inspect = function(obj) {
|
|||
/**
|
||||
Compares two objects, returning true if they are logically equal. This is
|
||||
a deeper comparison than a simple triple equal. For sets it will compare the
|
||||
internal objects. For any other object that implements `isEqual()` it will
|
||||
internal objects. For any other object that implements `isEqual()` it will
|
||||
respect that method.
|
||||
|
||||
Ember.isEqual('hello', 'hello'); => true
|
||||
|
@ -5630,7 +5630,7 @@ Ember.String = {
|
|||
> beta
|
||||
> gamma
|
||||
|
||||
@param {String} str
|
||||
@param {String} str
|
||||
The string to split
|
||||
|
||||
@returns {String} split string
|
||||
|
@ -5639,7 +5639,7 @@ Ember.String = {
|
|||
|
||||
/**
|
||||
Converts a camelized string into all lower case separated by underscores.
|
||||
|
||||
|
||||
'innerHTML'.decamelize() => 'inner_html'
|
||||
'action_name'.decamelize() => 'action_name'
|
||||
'css-class-name'.decamelize() => 'css-class-name'
|
||||
|
@ -5656,7 +5656,7 @@ Ember.String = {
|
|||
|
||||
/**
|
||||
Replaces underscores or spaces with dashes.
|
||||
|
||||
|
||||
'innerHTML'.dasherize() => 'inner-html'
|
||||
'action_name'.dasherize() => 'action-name'
|
||||
'css-class-name'.dasherize() => 'css-class-name'
|
||||
|
@ -5823,7 +5823,7 @@ if (Ember.EXTEND_PROTOTYPES) {
|
|||
|
||||
/**
|
||||
The `property` extension of Javascript's Function prototype is available
|
||||
when Ember.EXTEND_PROTOTYPES is true, which is the default.
|
||||
when Ember.EXTEND_PROTOTYPES is true, which is the default.
|
||||
|
||||
Computed properties allow you to treat a function like a property:
|
||||
|
||||
|
@ -5878,7 +5878,7 @@ if (Ember.EXTEND_PROTOTYPES) {
|
|||
|
||||
/**
|
||||
The `observes` extension of Javascript's Function prototype is available
|
||||
when Ember.EXTEND_PROTOTYPES is true, which is the default.
|
||||
when Ember.EXTEND_PROTOTYPES is true, which is the default.
|
||||
|
||||
You can observe property changes simply by adding the `observes`
|
||||
call to the end of your method declarations in classes that you write.
|
||||
|
@ -5889,7 +5889,7 @@ if (Ember.EXTEND_PROTOTYPES) {
|
|||
// Executes whenever the "value" property changes
|
||||
}.observes('value')
|
||||
});
|
||||
|
||||
|
||||
@see Ember.Observable
|
||||
*/
|
||||
Function.prototype.observes = function() {
|
||||
|
@ -5899,7 +5899,7 @@ if (Ember.EXTEND_PROTOTYPES) {
|
|||
|
||||
/**
|
||||
The `observesBefore` extension of Javascript's Function prototype is
|
||||
available when Ember.EXTEND_PROTOTYPES is true, which is the default.
|
||||
available when Ember.EXTEND_PROTOTYPES is true, which is the default.
|
||||
|
||||
You can get notified when a property changes is about to happen by
|
||||
by adding the `observesBefore` call to the end of your method
|
||||
|
@ -5910,7 +5910,7 @@ if (Ember.EXTEND_PROTOTYPES) {
|
|||
// Executes whenever the "value" property is about to change
|
||||
}.observesBefore('value')
|
||||
});
|
||||
|
||||
|
||||
@see Ember.Observable
|
||||
*/
|
||||
Function.prototype.observesBefore = function() {
|
||||
|
@ -6509,9 +6509,9 @@ Ember.Enumerable = Ember.Mixin.create(
|
|||
|
||||
/**
|
||||
Returns a copy of the array with all null elements removed.
|
||||
|
||||
|
||||
var arr = ["a", null, "c", null];
|
||||
arr.compact(); => ["a", "c"]
|
||||
arr.compact(); => ["a", "c"]
|
||||
|
||||
@returns {Array} the array without null elements.
|
||||
*/
|
||||
|
@ -7514,7 +7514,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
|
|||
colors.clear(); => []
|
||||
colors.length(); => 0
|
||||
|
||||
@returns {Ember.Array} An empty Array.
|
||||
@returns {Ember.Array} An empty Array.
|
||||
*/
|
||||
clear: function () {
|
||||
var len = get(this, 'length');
|
||||
|
@ -7708,15 +7708,15 @@ var get = Ember.get, set = Ember.set;
|
|||
@class
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
This mixin provides properties and property observing functionality, core
|
||||
features of the Ember object model.
|
||||
|
||||
|
||||
Properties and observers allow one object to observe changes to a
|
||||
property on another object. This is one of the fundamental ways that
|
||||
models, controllers and views communicate with each other in an Ember
|
||||
application.
|
||||
|
||||
|
||||
Any object that has this mixin applied can be used in observer
|
||||
operations. That includes Ember.Object and most objects you will
|
||||
interact with as you write your Ember application.
|
||||
|
@ -7724,16 +7724,16 @@ var get = Ember.get, set = Ember.set;
|
|||
Note that you will not generally apply this mixin to classes yourself,
|
||||
but you will use the features provided by this module frequently, so it
|
||||
is important to understand how to use it.
|
||||
|
||||
|
||||
## Using get() and set()
|
||||
|
||||
|
||||
Because of Ember's support for bindings and observers, you will always
|
||||
access properties using the get method, and set properties using the
|
||||
set method. This allows the observing objects to be notified and
|
||||
computed properties to be handled properly.
|
||||
|
||||
|
||||
More documentation about `get` and `set` are below.
|
||||
|
||||
|
||||
## Observing Property Changes
|
||||
|
||||
You typically observe property changes simply by adding the `observes`
|
||||
|
@ -7745,7 +7745,7 @@ var get = Ember.get, set = Ember.set;
|
|||
// Executes whenever the "value" property changes
|
||||
}.observes('value')
|
||||
});
|
||||
|
||||
|
||||
Although this is the most common way to add an observer, this capability
|
||||
is actually built into the Ember.Object class on top of two methods
|
||||
defined in this mixin: `addObserver` and `removeObserver`. You can use
|
||||
|
@ -7758,12 +7758,12 @@ var get = Ember.get, set = Ember.set;
|
|||
|
||||
This will call the `targetAction` method on the `targetObject` to be called
|
||||
whenever the value of the `propertyKey` changes.
|
||||
|
||||
Note that if `propertyKey` is a computed property, the observer will be
|
||||
called when any of the property dependencies are changed, even if the
|
||||
|
||||
Note that if `propertyKey` is a computed property, the observer will be
|
||||
called when any of the property dependencies are changed, even if the
|
||||
resulting value of the computed property is unchanged. This is necessary
|
||||
because computed properties are not computed until `get` is called.
|
||||
|
||||
|
||||
@extends Ember.Mixin
|
||||
*/
|
||||
Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
|
||||
|
@ -7777,7 +7777,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
|
|||
This method is usually similar to using object[keyName] or object.keyName,
|
||||
however it supports both computed properties and the unknownProperty
|
||||
handler.
|
||||
|
||||
|
||||
Because `get` unifies the syntax for accessing all these kinds
|
||||
of properties, it can make many refactorings easier, such as replacing a
|
||||
simple property with a computed property, or vice versa.
|
||||
|
@ -7973,11 +7973,11 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
|
|||
Ember.propertyDidChange(this, keyName);
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
Convenience method to call `propertyWillChange` and `propertyDidChange` in
|
||||
succession.
|
||||
|
||||
|
||||
@param {String} keyName The property key to be notified about.
|
||||
@returns {Ember.Observable}
|
||||
*/
|
||||
|
@ -8069,7 +8069,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
|
|||
This method will be called when a client attempts to get the value of a
|
||||
property that has not been defined in one of the typical ways. Override
|
||||
this method to create "virtual" properties.
|
||||
|
||||
|
||||
@param {String} key The name of the unknown property that was requested.
|
||||
@returns {Object} The property value or undefined. Default is undefined.
|
||||
*/
|
||||
|
@ -8081,7 +8081,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
|
|||
This method will be called when a client attempts to set the value of a
|
||||
property that has not been defined in one of the typical ways. Override
|
||||
this method to create "virtual" properties.
|
||||
|
||||
|
||||
@param {String} key The name of the unknown property to be set.
|
||||
@param {Object} value The value the unknown property is to be set to.
|
||||
*/
|
||||
|
@ -8092,7 +8092,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
|
|||
/**
|
||||
This is like `get`, but allows you to pass in a dot-separated property
|
||||
path.
|
||||
|
||||
|
||||
person.getPath('address.zip'); // return the zip
|
||||
person.getPath('children.firstObject.age'); // return the first kid's age
|
||||
|
||||
|
@ -8108,7 +8108,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
|
|||
/**
|
||||
This is like `set`, but allows you to specify the property you want to
|
||||
set as a dot-separated property path.
|
||||
|
||||
|
||||
person.setPath('address.zip', 10011); // set the zip to 10011
|
||||
person.setPath('children.firstObject.age', 6); // set the first kid's age to 6
|
||||
|
||||
|
@ -8126,9 +8126,9 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
|
|||
/**
|
||||
Retrieves the value of a property, or a default value in the case that the property
|
||||
returns undefined.
|
||||
|
||||
|
||||
person.getWithDefault('lastName', 'Doe');
|
||||
|
||||
|
||||
@param {String} keyName The name of the property to retrieve
|
||||
@param {Object} defaultValue The value to return if the property value is undefined
|
||||
@returns {Object} The property value or the defaultValue.
|
||||
|
@ -8139,10 +8139,10 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
|
|||
|
||||
/**
|
||||
Set the value of a property to the current value plus some amount.
|
||||
|
||||
|
||||
person.incrementProperty('age');
|
||||
team.incrementProperty('score', 2);
|
||||
|
||||
|
||||
@param {String} keyName The name of the property to increment
|
||||
@param {Object} increment The amount to increment by. Defaults to 1
|
||||
@returns {Object} The new property value
|
||||
|
@ -8152,13 +8152,13 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
|
|||
set(this, keyName, (get(this, keyName) || 0)+increment);
|
||||
return get(this, keyName);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
Set the value of a property to the current value minus some amount.
|
||||
|
||||
|
||||
player.decrementProperty('lives');
|
||||
orc.decrementProperty('health', 5);
|
||||
|
||||
|
||||
@param {String} keyName The name of the property to decrement
|
||||
@param {Object} increment The amount to decrement by. Defaults to 1
|
||||
@returns {Object} The new property value
|
||||
|
@ -8172,9 +8172,9 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
|
|||
/**
|
||||
Set the value of a boolean property to the opposite of it's
|
||||
current value.
|
||||
|
||||
|
||||
starship.toggleProperty('warpDriveEnaged');
|
||||
|
||||
|
||||
@param {String} keyName The name of the property to toggle
|
||||
@returns {Object} The new property value
|
||||
*/
|
||||
|
@ -11603,7 +11603,7 @@ var invokeForState = {
|
|||
`Ember.View` is the class in Ember responsible for encapsulating templates of HTML
|
||||
content, combining templates with data to render as sections of a page's DOM, and
|
||||
registering and responding to user-initiated events.
|
||||
|
||||
|
||||
## HTML Tag
|
||||
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:
|
||||
|
@ -11629,7 +11629,7 @@ var invokeForState = {
|
|||
<div id="ember1" class="ember-view my-class my-other-class"></div>
|
||||
|
||||
`class` attribute values can also be set by providing a `classNameBindings` property
|
||||
set to an array of properties names for the view. The return value of these properties
|
||||
set to an array of properties names for the view. The return value of these properties
|
||||
will be added as part of the value for the view's `class` attribute. These properties
|
||||
can be computed properties:
|
||||
|
||||
|
@ -11658,7 +11658,7 @@ var invokeForState = {
|
|||
|
||||
<div id="ember1" class="ember-view hovered"></div>
|
||||
|
||||
When using boolean class name bindings you can supply a string value other than the
|
||||
When using boolean class name bindings you can supply a string value other than the
|
||||
property name for use as the `class` HTML attribute by appending the preferred value after
|
||||
a ":" character when defining the binding:
|
||||
|
||||
|
@ -11699,11 +11699,11 @@ var invokeForState = {
|
|||
|
||||
<div id="ember1" class="ember-view empty"></div>
|
||||
|
||||
Updates to the the value of a class name binding will result in automatic update
|
||||
Updates to the the value of a class name binding will result in automatic update
|
||||
of the HTML `class` attribute in the view's rendered HTML representation.
|
||||
If the value becomes `false` or `undefined` the class name will be removed.
|
||||
|
||||
Both `classNames` and `classNameBindings` are concatenated properties.
|
||||
Both `classNames` and `classNameBindings` are concatenated properties.
|
||||
See `Ember.Object` documentation for more information about concatenated properties.
|
||||
|
||||
## HTML Attributes
|
||||
|
@ -11749,7 +11749,7 @@ var invokeForState = {
|
|||
}.property()
|
||||
})
|
||||
|
||||
Updates to the the property of an attribute binding will result in automatic update
|
||||
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` documentation
|
||||
|
@ -11840,7 +11840,7 @@ var invokeForState = {
|
|||
primary templates, layouts can be any function that accepts an optional context
|
||||
parameter and returns a string of HTML that will be inserted inside view's tag. Views whose HTML
|
||||
element is self closing (e.g. `<input />`) cannot have a layout and this property will be ignored.
|
||||
|
||||
|
||||
Most typically in Ember a layout will be a compiled Ember.Handlebars template.
|
||||
|
||||
A view's layout can be set directly with the `layout` property or reference an
|
||||
|
@ -11865,7 +11865,7 @@ var invokeForState = {
|
|||
See `Handlebars.helpers.yield` for more information.
|
||||
|
||||
## Responding to Browser Events
|
||||
Views can respond to user-initiated events in one of three ways: method implementation,
|
||||
Views can respond to user-initiated events in one of three ways: method implementation,
|
||||
through an event manager, and through `{{action}}` helper use in their template or layout.
|
||||
|
||||
### Method Implementation
|
||||
|
@ -11882,8 +11882,8 @@ var invokeForState = {
|
|||
### Event Managers
|
||||
Views can define an object as their `eventManager` property. This object can then
|
||||
implement methods that match the desired event names. Matching events that occur
|
||||
on the view's rendered HTML or the rendered HTML of any of its DOM descendants
|
||||
will trigger this method. A `jQuery.Event` object will be passed as the first
|
||||
on the view's rendered HTML or the rendered HTML of any of its DOM descendants
|
||||
will trigger this method. A `jQuery.Event` object will be passed as the first
|
||||
argument to the method and an `Ember.View` object as the second. The `Ember.View`
|
||||
will be the view whose rendered HTML was interacted with. This may be the view with
|
||||
the `eventManager` property or one of its descendent views.
|
||||
|
@ -11917,7 +11917,7 @@ var invokeForState = {
|
|||
|
||||
Similarly a view's event manager will take precedence for events of any views
|
||||
rendered as a descendent. A method name that matches an event name will not be called
|
||||
if the view instance was rendered inside the HTML representation of a view that has
|
||||
if the view instance was rendered inside the HTML representation of a view that has
|
||||
an `eventManager` property defined that handles events of the name. Events not handled
|
||||
by the event manager will still trigger method calls on the descendent.
|
||||
|
||||
|
@ -11939,7 +11939,7 @@ var invokeForState = {
|
|||
// eventManager doesn't handle click events
|
||||
},
|
||||
mouseEnter: function(event){
|
||||
// will never be called if rendered inside
|
||||
// will never be called if rendered inside
|
||||
// an OuterView.
|
||||
}
|
||||
})
|
||||
|
@ -11960,7 +11960,7 @@ var invokeForState = {
|
|||
Form events: 'submit', 'change', 'focusIn', 'focusOut', 'input'
|
||||
|
||||
HTML5 drag and drop events: 'dragStart', 'drag', 'dragEnter', 'dragLeave', 'drop', 'dragEnd'
|
||||
|
||||
|
||||
## 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.
|
||||
|
@ -14338,7 +14338,7 @@ var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
|
|||
@class
|
||||
|
||||
`Ember.CollectionView` is an `Ember.View` descendent responsible for managing a
|
||||
collection (an array or array-like object) by maintaing a child view object and
|
||||
collection (an array or array-like object) by maintaing a child view object and
|
||||
associated DOM representation for each item in the array and ensuring that child
|
||||
views and their associated rendered HTML are updated when items in the array
|
||||
are added, removed, or replaced.
|
||||
|
@ -14382,7 +14382,7 @@ var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
|
|||
|
||||
## Automatic matching of parent/child tagNames
|
||||
|
||||
Setting the `tagName` property of a `CollectionView` to any of
|
||||
Setting the `tagName` property of a `CollectionView` to any of
|
||||
"ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result
|
||||
in the item views receiving an appropriately matched `tagName` property.
|
||||
|
||||
|
@ -15229,15 +15229,15 @@ var arrayForEach = Ember.ArrayPolyfills.forEach;
|
|||
robotManager.getPath('currentState.name') // 'rampaging'
|
||||
|
||||
Transition actions can also be created using the `transitionTo` method of the Ember.State class. The
|
||||
following example StateManagers are equivalent:
|
||||
|
||||
following example StateManagers are equivalent:
|
||||
|
||||
aManager = Ember.StateManager.create({
|
||||
stateOne: Ember.State.create({
|
||||
changeToStateTwo: Ember.State.transitionTo('stateTwo')
|
||||
}),
|
||||
stateTwo: Ember.State.create({})
|
||||
})
|
||||
|
||||
|
||||
bManager = Ember.StateManager.create({
|
||||
stateOne: Ember.State.create({
|
||||
changeToStateTwo: function(manager, context){
|
||||
|
@ -15318,7 +15318,7 @@ Ember.StateManager = Ember.State.extend(
|
|||
@default true
|
||||
*/
|
||||
errorOnUnhandledEvent: true,
|
||||
|
||||
|
||||
send: function(event, context) {
|
||||
Ember.assert('Cannot send event "' + event + '" while currentState is ' + get(this, 'currentState'), get(this, 'currentState'));
|
||||
if (arguments.length === 1) { context = {}; }
|
||||
|
@ -18396,7 +18396,7 @@ EmberHandlebars.ViewHelper = Ember.Object.create({
|
|||
Will result in HTML structure:
|
||||
|
||||
<body>
|
||||
<!-- Note: the handlebars template script
|
||||
<!-- Note: the handlebars template script
|
||||
also results in a rendered Ember.View
|
||||
which is the outer <div> here -->
|
||||
|
||||
|
@ -18418,7 +18418,7 @@ EmberHandlebars.ViewHelper = Ember.Object.create({
|
|||
})
|
||||
|
||||
aView.appendTo('body')
|
||||
|
||||
|
||||
Will result in HTML structure:
|
||||
|
||||
<div id="ember1" class="ember-view">
|
||||
|
@ -18492,7 +18492,7 @@ EmberHandlebars.ViewHelper = Ember.Object.create({
|
|||
Will result in the following HTML:
|
||||
|
||||
<div id="ember1" class="ember-view">
|
||||
<div id="ember2" class="ember-view a-custom-view-class-as-property">
|
||||
<div id="ember2" class="ember-view a-custom-view-class-as-property">
|
||||
hi
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18652,7 +18652,7 @@ var get = Ember.get, getPath = Ember.Handlebars.getPath, fmt = Ember.String.fmt;
|
|||
<p class="ember-view greeting">Howdy Mary</p>
|
||||
<p class="ember-view greeting">Howdy Sara</p>
|
||||
</div>
|
||||
|
||||
|
||||
@name Handlebars.helpers.collection
|
||||
@param {String} path
|
||||
@param {Hash} options
|
||||
|
@ -19266,7 +19266,7 @@ var set = Ember.set, get = Ember.get;
|
|||
/**
|
||||
@class
|
||||
|
||||
Creates an HTML input of type 'checkbox' with HTML related properties
|
||||
Creates an HTML input of type 'checkbox' with HTML related properties
|
||||
applied directly to the input.
|
||||
|
||||
{{view Ember.Checkbox classNames="applicaton-specific-checkbox"}}
|
||||
|
@ -19285,7 +19285,7 @@ var set = Ember.set, get = Ember.get;
|
|||
through the Ember object or by interacting with its rendered element representation
|
||||
via the mouse, keyboard, or touch. Updating the value of the checkbox via jQuery will
|
||||
result in the checked value of the object and its element losing synchronization.
|
||||
|
||||
|
||||
## Layout and LayoutName properties
|
||||
Because HTML `input` elements are self closing `layout` and `layoutName` properties will
|
||||
not be applied. See `Ember.View`'s layout section for more information.
|
||||
|
@ -19397,7 +19397,7 @@ var get = Ember.get, set = Ember.set;
|
|||
## Layout and LayoutName properties
|
||||
Because HTML `input` elements are self closing `layout` and `layoutName` properties will
|
||||
not be applied. See `Ember.View`'s layout section for more information.
|
||||
|
||||
|
||||
@extends Ember.TextSupport
|
||||
*/
|
||||
Ember.TextField = Ember.View.extend(Ember.TextSupport,
|
||||
|
@ -19574,7 +19574,7 @@ var get = Ember.get, set = Ember.set;
|
|||
|
||||
## Layout and LayoutName properties
|
||||
|
||||
Because HTML `textarea` elements do not contain inner HTML the `layout` and `layoutName`
|
||||
Because HTML `textarea` elements do not contain inner HTML the `layout` and `layoutName`
|
||||
properties will not be applied. See `Ember.View`'s layout section for more information.
|
||||
|
||||
@extends Ember.TextSupport
|
||||
|
|
546
assets/javascripts/vendor/sc-routes.js
vendored
Normal file
546
assets/javascripts/vendor/sc-routes.js
vendored
Normal file
|
@ -0,0 +1,546 @@
|
|||
// ==========================================================================
|
||||
// Project: SproutCore - JavaScript Application Framework
|
||||
// Copyright: ©2006-2011 Strobe Inc. and contributors.
|
||||
// Portions ©2008-2011 Apple Inc. All rights reserved.
|
||||
// License: Licensed under MIT license (see license.js)
|
||||
// ==========================================================================
|
||||
|
||||
var get = Ember.get, set = Ember.set;
|
||||
|
||||
/**
|
||||
Wether the browser supports HTML5 history.
|
||||
*/
|
||||
var supportsHistory = !!(window.history && window.history.pushState);
|
||||
|
||||
/**
|
||||
Wether the browser supports the hashchange event.
|
||||
*/
|
||||
var supportsHashChange = ('onhashchange' in window) && (document.documentMode === undefined || document.documentMode > 7);
|
||||
|
||||
/**
|
||||
@class
|
||||
|
||||
Route is a class used internally by Ember.routes. The routes defined by your
|
||||
application are stored in a tree structure, and this is the class for the
|
||||
nodes.
|
||||
*/
|
||||
var Route = Ember.Object.extend(
|
||||
/** @scope Route.prototype */ {
|
||||
|
||||
target: null,
|
||||
|
||||
method: null,
|
||||
|
||||
staticRoutes: null,
|
||||
|
||||
dynamicRoutes: null,
|
||||
|
||||
wildcardRoutes: null,
|
||||
|
||||
add: function(parts, target, method) {
|
||||
var part, nextRoute;
|
||||
|
||||
// clone the parts array because we are going to alter it
|
||||
parts = Ember.copy(parts);
|
||||
|
||||
if (!parts || parts.length === 0) {
|
||||
this.target = target;
|
||||
this.method = method;
|
||||
|
||||
} else {
|
||||
part = parts.shift();
|
||||
|
||||
// there are 3 types of routes
|
||||
switch (part.slice(0, 1)) {
|
||||
|
||||
// 1. dynamic routes
|
||||
case ':':
|
||||
part = part.slice(1, part.length);
|
||||
if (!this.dynamicRoutes) this.dynamicRoutes = {};
|
||||
if (!this.dynamicRoutes[part]) this.dynamicRoutes[part] = this.constructor.create();
|
||||
nextRoute = this.dynamicRoutes[part];
|
||||
break;
|
||||
|
||||
// 2. wildcard routes
|
||||
case '*':
|
||||
part = part.slice(1, part.length);
|
||||
if (!this.wildcardRoutes) this.wildcardRoutes = {};
|
||||
nextRoute = this.wildcardRoutes[part] = this.constructor.create();
|
||||
break;
|
||||
|
||||
// 3. static routes
|
||||
default:
|
||||
if (!this.staticRoutes) this.staticRoutes = {};
|
||||
if (!this.staticRoutes[part]) this.staticRoutes[part] = this.constructor.create();
|
||||
nextRoute = this.staticRoutes[part];
|
||||
}
|
||||
|
||||
// recursively add the rest of the route
|
||||
if (nextRoute) nextRoute.add(parts, target, method);
|
||||
}
|
||||
},
|
||||
|
||||
routeForParts: function(parts, params) {
|
||||
var part, key, route;
|
||||
|
||||
// clone the parts array because we are going to alter it
|
||||
parts = Ember.copy(parts);
|
||||
|
||||
// if parts is empty, we are done
|
||||
if (!parts || parts.length === 0) {
|
||||
return this.method ? this : null;
|
||||
|
||||
} else {
|
||||
part = parts.shift();
|
||||
|
||||
// try to match a static route
|
||||
if (this.staticRoutes && this.staticRoutes[part]) {
|
||||
return this.staticRoutes[part].routeForParts(parts, params);
|
||||
|
||||
} else {
|
||||
|
||||
// else, try to match a dynamic route
|
||||
for (key in this.dynamicRoutes) {
|
||||
route = this.dynamicRoutes[key].routeForParts(parts, params);
|
||||
if (route) {
|
||||
params[key] = part;
|
||||
return route;
|
||||
}
|
||||
}
|
||||
|
||||
// else, try to match a wilcard route
|
||||
for (key in this.wildcardRoutes) {
|
||||
parts.unshift(part);
|
||||
params[key] = parts.join('/');
|
||||
return this.wildcardRoutes[key].routeForParts(null, params);
|
||||
}
|
||||
|
||||
// if nothing was found, it means that there is no match
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
@class
|
||||
|
||||
Ember.routes manages the browser location. You can change the hash part of the
|
||||
current location. The following code
|
||||
|
||||
Ember.routes.set('location', 'notes/edit/4');
|
||||
|
||||
will change the location to http://domain.tld/my_app#notes/edit/4. Adding
|
||||
routes will register a handler that will be called whenever the location
|
||||
changes and matches the route:
|
||||
|
||||
Ember.routes.add(':controller/:action/:id', MyApp, MyApp.route);
|
||||
|
||||
You can pass additional parameters in the location hash that will be relayed
|
||||
to the route handler:
|
||||
|
||||
Ember.routes.set('location', 'notes/show/4?format=xml&language=fr');
|
||||
|
||||
The syntax for the location hash is described in the location property
|
||||
documentation, and the syntax for adding handlers is described in the
|
||||
add method documentation.
|
||||
|
||||
Browsers keep track of the locations in their history, so when the user
|
||||
presses the 'back' or 'forward' button, the location is changed, Ember.route
|
||||
catches it and calls your handler. Except for Internet Explorer versions 7
|
||||
and earlier, which do not modify the history stack when the location hash
|
||||
changes.
|
||||
|
||||
Ember.routes also supports HTML5 history, which uses a '/' instead of a '#'
|
||||
in the URLs, so that all your website's URLs are consistent.
|
||||
*/
|
||||
var routes = Ember.routes = Ember.Object.create(
|
||||
/** @scope Ember.routes.prototype */{
|
||||
|
||||
/**
|
||||
Set this property to true if you want to use HTML5 history, if available on
|
||||
the browser, instead of the location hash.
|
||||
|
||||
HTML 5 history uses the history.pushState method and the window's popstate
|
||||
event.
|
||||
|
||||
By default it is false, so your URLs will look like:
|
||||
|
||||
http://domain.tld/my_app#notes/edit/4
|
||||
|
||||
If set to true and the browser supports pushState(), your URLs will look
|
||||
like:
|
||||
|
||||
http://domain.tld/my_app/notes/edit/4
|
||||
|
||||
You will also need to make sure that baseURI is properly configured, as
|
||||
well as your server so that your routes are properly pointing to your
|
||||
SproutCore application.
|
||||
|
||||
@see http://dev.w3.org/html5/spec/history.html#the-history-interface
|
||||
@property
|
||||
@type {Boolean}
|
||||
*/
|
||||
wantsHistory: false,
|
||||
|
||||
/**
|
||||
A read-only boolean indicating whether or not HTML5 history is used. Based
|
||||
on the value of wantsHistory and the browser's support for pushState.
|
||||
|
||||
@see wantsHistory
|
||||
@property
|
||||
@type {Boolean}
|
||||
*/
|
||||
usesHistory: null,
|
||||
|
||||
/**
|
||||
The base URI used to resolve routes (which are relative URLs). Only used
|
||||
when usesHistory is equal to true.
|
||||
|
||||
The build tools automatically configure this value if you have the
|
||||
html5_history option activated in the Buildfile:
|
||||
|
||||
config :my_app, :html5_history => true
|
||||
|
||||
Alternatively, it uses by default the value of the href attribute of the
|
||||
<base> tag of the HTML document. For example:
|
||||
|
||||
<base href="http://domain.tld/my_app">
|
||||
|
||||
The value can also be customized before or during the exectution of the
|
||||
main() method.
|
||||
|
||||
@see http://www.w3.org/TR/html5/semantics.html#the-base-element
|
||||
@property
|
||||
@type {String}
|
||||
*/
|
||||
baseURI: document.baseURI,
|
||||
|
||||
/** @private
|
||||
A boolean value indicating whether or not the ping method has been called
|
||||
to setup the Ember.routes.
|
||||
|
||||
@property
|
||||
@type {Boolean}
|
||||
*/
|
||||
_didSetup: false,
|
||||
|
||||
/** @private
|
||||
Internal representation of the current location hash.
|
||||
|
||||
@property
|
||||
@type {String}
|
||||
*/
|
||||
_location: null,
|
||||
|
||||
/** @private
|
||||
Routes are stored in a tree structure, this is the root node.
|
||||
|
||||
@property
|
||||
@type {Route}
|
||||
*/
|
||||
_firstRoute: null,
|
||||
|
||||
/** @private
|
||||
An internal reference to the Route class.
|
||||
|
||||
@property
|
||||
*/
|
||||
_Route: Route,
|
||||
|
||||
/** @private
|
||||
Internal method used to extract and merge the parameters of a URL.
|
||||
|
||||
@returns {Hash}
|
||||
*/
|
||||
_extractParametersAndRoute: function(obj) {
|
||||
var params = {},
|
||||
route = obj.route || '',
|
||||
separator, parts, i, len, crumbs, key;
|
||||
|
||||
separator = (route.indexOf('?') < 0 && route.indexOf('&') >= 0) ? '&' : '?';
|
||||
parts = route.split(separator);
|
||||
route = parts[0];
|
||||
if (parts.length === 1) {
|
||||
parts = [];
|
||||
} else if (parts.length === 2) {
|
||||
parts = parts[1].split('&');
|
||||
} else if (parts.length > 2) {
|
||||
parts.shift();
|
||||
}
|
||||
|
||||
// extract the parameters from the route string
|
||||
len = parts.length;
|
||||
for (i = 0; i < len; ++i) {
|
||||
crumbs = parts[i].split('=');
|
||||
params[crumbs[0]] = crumbs[1];
|
||||
}
|
||||
|
||||
// overlay any parameter passed in obj
|
||||
for (key in obj) {
|
||||
if (obj.hasOwnProperty(key) && key !== 'route') {
|
||||
params[key] = '' + obj[key];
|
||||
}
|
||||
}
|
||||
|
||||
// build the route
|
||||
parts = [];
|
||||
for (key in params) {
|
||||
parts.push([key, params[key]].join('='));
|
||||
}
|
||||
params.params = separator + parts.join('&');
|
||||
params.route = route;
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
/**
|
||||
The current location hash. It is the part in the browser's location after
|
||||
the '#' mark.
|
||||
|
||||
The following code
|
||||
|
||||
Ember.routes.set('location', 'notes/edit/4');
|
||||
|
||||
will change the location to http://domain.tld/my_app#notes/edit/4 and call
|
||||
the correct route handler if it has been registered with the add method.
|
||||
|
||||
You can also pass additional parameters. They will be relayed to the route
|
||||
handler. For example, the following code
|
||||
|
||||
Ember.routes.add(':controller/:action/:id', MyApp, MyApp.route);
|
||||
Ember.routes.set('location', 'notes/show/4?format=xml&language=fr');
|
||||
|
||||
will change the location to
|
||||
http://domain.tld/my_app#notes/show/4?format=xml&language=fr and call the
|
||||
MyApp.route method with the following argument:
|
||||
|
||||
{ route: 'notes/show/4',
|
||||
params: '?format=xml&language=fr',
|
||||
controller: 'notes',
|
||||
action: 'show',
|
||||
id: '4',
|
||||
format: 'xml',
|
||||
language: 'fr' }
|
||||
|
||||
The location can also be set with a hash, the following code
|
||||
|
||||
Ember.routes.set('location',
|
||||
{ route: 'notes/edit/4', format: 'xml', language: 'fr' });
|
||||
|
||||
will change the location to
|
||||
http://domain.tld/my_app#notes/show/4?format=xml&language=fr.
|
||||
|
||||
The 'notes/show/4&format=xml&language=fr' syntax for passing parameters,
|
||||
using a '&' instead of a '?', as used in SproutCore 1.0 is still supported.
|
||||
|
||||
@property
|
||||
@type {String}
|
||||
*/
|
||||
location: function(key, value) {
|
||||
this._skipRoute = false;
|
||||
return this._extractLocation(key, value);
|
||||
}.property(),
|
||||
|
||||
_extractLocation: function(key, value) {
|
||||
var crumbs, encodedValue;
|
||||
|
||||
if (value !== undefined) {
|
||||
if (value === null) {
|
||||
value = '';
|
||||
}
|
||||
|
||||
if (typeof(value) === 'object') {
|
||||
crumbs = this._extractParametersAndRoute(value);
|
||||
value = crumbs.route + crumbs.params;
|
||||
}
|
||||
|
||||
if (!Ember.empty(value) || (this._location && this._location !== value)) {
|
||||
encodedValue = encodeURI(value);
|
||||
|
||||
if (this.usesHistory) {
|
||||
if (encodedValue.length > 0) {
|
||||
encodedValue = '/' + encodedValue;
|
||||
}
|
||||
window.history.pushState(null, null, get(this, 'baseURI') + encodedValue);
|
||||
} else {
|
||||
window.location.hash = encodedValue;
|
||||
}
|
||||
}
|
||||
|
||||
this._location = value;
|
||||
}
|
||||
|
||||
return this._location;
|
||||
},
|
||||
|
||||
/**
|
||||
You usually don't need to call this method. It is done automatically after
|
||||
the application has been initialized.
|
||||
|
||||
It registers for the hashchange event if available. If not, it creates a
|
||||
timer that looks for location changes every 150ms.
|
||||
*/
|
||||
ping: function() {
|
||||
var that;
|
||||
|
||||
if (!this._didSetup) {
|
||||
this._didSetup = true;
|
||||
|
||||
if (get(this, 'wantsHistory') && supportsHistory) {
|
||||
this.usesHistory = true;
|
||||
|
||||
popState();
|
||||
jQuery(window).bind('popstate', popState);
|
||||
|
||||
} else {
|
||||
this.usesHistory = false;
|
||||
|
||||
if (supportsHashChange) {
|
||||
hashChange();
|
||||
jQuery(window).bind('hashchange', hashChange);
|
||||
|
||||
} else {
|
||||
// we don't use a Ember.Timer because we don't want
|
||||
// a run loop to be triggered at each ping
|
||||
that = this;
|
||||
this._invokeHashChange = function() {
|
||||
that.hashChange();
|
||||
setTimeout(that._invokeHashChange, 100);
|
||||
};
|
||||
this._invokeHashChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Adds a route handler. Routes have the following format:
|
||||
|
||||
- 'users/show/5' is a static route and only matches this exact string,
|
||||
- ':action/:controller/:id' is a dynamic route and the handler will be
|
||||
called with the 'action', 'controller' and 'id' parameters passed in a
|
||||
hash,
|
||||
- '*url' is a wildcard route, it matches the whole route and the handler
|
||||
will be called with the 'url' parameter passed in a hash.
|
||||
|
||||
Route types can be combined, the following are valid routes:
|
||||
|
||||
- 'users/:action/:id'
|
||||
- ':controller/show/:id'
|
||||
- ':controller/ *url' (ignore the space, because of jslint)
|
||||
|
||||
@param {String} route the route to be registered
|
||||
@param {Object} target the object on which the method will be called, or
|
||||
directly the function to be called to handle the route
|
||||
@param {Function} method the method to be called on target to handle the
|
||||
route, can be a function or a string
|
||||
*/
|
||||
add: function(route, target, method) {
|
||||
if (!this._didSetup) {
|
||||
Ember.run.once(this, 'ping');
|
||||
}
|
||||
|
||||
if (method === undefined && Ember.typeOf(target) === 'function') {
|
||||
method = target;
|
||||
target = null;
|
||||
} else if (Ember.typeOf(method) === 'string') {
|
||||
method = target[method];
|
||||
}
|
||||
|
||||
if (!this._firstRoute) this._firstRoute = Route.create();
|
||||
this._firstRoute.add(route.split('/'), target, method);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
Observer of the 'location' property that calls the correct route handler
|
||||
when the location changes.
|
||||
*/
|
||||
locationDidChange: function() {
|
||||
this.trigger();
|
||||
}.observes('location'),
|
||||
|
||||
/**
|
||||
Triggers a route even if already in that route (does change the location, if it
|
||||
is not already changed, as well).
|
||||
|
||||
If the location is not the same as the supplied location, this simply lets "location"
|
||||
handle it (which ends up coming back to here).
|
||||
*/
|
||||
trigger: function() {
|
||||
var location = get(this, 'location'),
|
||||
params, route;
|
||||
|
||||
if (this._firstRoute) {
|
||||
params = this._extractParametersAndRoute({ route: location });
|
||||
location = params.route;
|
||||
delete params.route;
|
||||
delete params.params;
|
||||
|
||||
route = this.getRoute(location, params);
|
||||
if (route && route.method) {
|
||||
route.method.call(route.target || this, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getRoute: function(route, params) {
|
||||
var firstRoute = this._firstRoute;
|
||||
if (params == null) {
|
||||
params = {}
|
||||
}
|
||||
|
||||
return firstRoute.routeForParts(route.split('/'), params);
|
||||
},
|
||||
|
||||
exists: function(route, params) {
|
||||
route = this.getRoute(route, params);
|
||||
return route != null && route.method != null;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
Event handler for the hashchange event. Called automatically by the browser
|
||||
if it supports the hashchange event, or by our timer if not.
|
||||
*/
|
||||
function hashChange(event) {
|
||||
var loc = window.location.hash;
|
||||
|
||||
// Remove the '#' prefix
|
||||
loc = (loc && loc.length > 0) ? loc.slice(1, loc.length) : '';
|
||||
|
||||
if (!jQuery.browser.mozilla) {
|
||||
// because of bug https://bugzilla.mozilla.org/show_bug.cgi?id=483304
|
||||
loc = decodeURI(loc);
|
||||
}
|
||||
|
||||
if (get(routes, 'location') !== loc && !routes._skipRoute) {
|
||||
Ember.run.once(function() {
|
||||
set(routes, 'location', loc);
|
||||
});
|
||||
}
|
||||
routes._skipRoute = false;
|
||||
}
|
||||
|
||||
function popState(event) {
|
||||
var base = get(routes, 'baseURI'),
|
||||
loc = document.location.href;
|
||||
|
||||
if (loc.slice(0, base.length) === base) {
|
||||
|
||||
// Remove the base prefix and the extra '/'
|
||||
loc = loc.slice(base.length + 1, loc.length);
|
||||
|
||||
if (get(routes, 'location') !== loc && !routes._skipRoute) {
|
||||
Ember.run.once(function() {
|
||||
set(routes, 'location', loc);
|
||||
});
|
||||
}
|
||||
}
|
||||
routes._skipRoute = false;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,7 @@
|
|||
(function() {
|
||||
var artifact, artifacts, build, builds, commits, id, job, jobs, repositories, repository, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m;
|
||||
var artifact, artifacts, build, builds, commits, id, job, jobs, repositories, repository, responseTime, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m;
|
||||
|
||||
responseTime = 0;
|
||||
|
||||
repositories = [
|
||||
{
|
||||
|
@ -33,7 +35,7 @@
|
|||
builds = [
|
||||
{
|
||||
id: 1,
|
||||
repository_id: 'travis-ci/travis-core',
|
||||
repository_id: '1',
|
||||
commit_id: 1,
|
||||
job_ids: [1, 2],
|
||||
number: 1,
|
||||
|
@ -46,7 +48,7 @@
|
|||
result: 0
|
||||
}, {
|
||||
id: 2,
|
||||
repository_id: 'travis-ci/travis-core',
|
||||
repository_id: '1',
|
||||
commit_id: 2,
|
||||
job_ids: [3],
|
||||
number: 2,
|
||||
|
@ -56,7 +58,7 @@
|
|||
}
|
||||
}, {
|
||||
id: 3,
|
||||
repository_id: 'travis-ci/travis-assets',
|
||||
repository_id: '2',
|
||||
commit_id: 3,
|
||||
job_ids: [4],
|
||||
number: 3,
|
||||
|
@ -69,7 +71,7 @@
|
|||
result: 0
|
||||
}, {
|
||||
id: 4,
|
||||
repository_id: 'travis-ci/travis-hub',
|
||||
repository_id: '3',
|
||||
commit_id: 4,
|
||||
job_ids: [5],
|
||||
number: 4,
|
||||
|
@ -197,7 +199,7 @@
|
|||
|
||||
$.mockjax({
|
||||
url: '/repositories',
|
||||
responseTime: 0,
|
||||
responseTime: responseTime,
|
||||
responseText: {
|
||||
repositories: repositories
|
||||
}
|
||||
|
@ -207,7 +209,7 @@
|
|||
repository = repositories[_i];
|
||||
$.mockjax({
|
||||
url: '/' + repository.slug,
|
||||
responseTime: 0,
|
||||
responseTime: responseTime,
|
||||
responseText: {
|
||||
repository: repository
|
||||
}
|
||||
|
@ -218,7 +220,7 @@
|
|||
build = builds[_j];
|
||||
$.mockjax({
|
||||
url: '/builds/' + build.id,
|
||||
responseTime: 0,
|
||||
responseTime: responseTime,
|
||||
responseText: {
|
||||
build: build,
|
||||
commit: commits[build.commit_id - 1],
|
||||
|
@ -241,11 +243,11 @@
|
|||
$.mockjax({
|
||||
url: '/builds',
|
||||
data: {
|
||||
repository_id: 1,
|
||||
repository_id: repository.id,
|
||||
event_type: 'push',
|
||||
orderBy: 'number DESC'
|
||||
},
|
||||
responseTime: 0,
|
||||
responseTime: responseTime,
|
||||
responseText: {
|
||||
builds: (function() {
|
||||
var _l, _len3, _ref, _results;
|
||||
|
@ -275,7 +277,7 @@
|
|||
job = jobs[_l];
|
||||
$.mockjax({
|
||||
url: '/jobs/' + job.id,
|
||||
responseTime: 0,
|
||||
responseTime: responseTime,
|
||||
responseText: {
|
||||
job: job,
|
||||
commit: commits[job.commit_id - 1]
|
||||
|
@ -287,7 +289,7 @@
|
|||
artifact = artifacts[_m];
|
||||
$.mockjax({
|
||||
url: '/artifacts/' + artifact.id,
|
||||
responseTime: 0,
|
||||
responseTime: responseTime,
|
||||
responseText: {
|
||||
artifact: artifact
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
describe('The current build tab', function() {
|
||||
describe('on the "index" state', function() {
|
||||
beforeEach(function() {
|
||||
app('/');
|
||||
app('');
|
||||
return waitFor(buildRendered);
|
||||
});
|
||||
it('displays the build summary', function() {
|
||||
|
@ -23,12 +23,14 @@
|
|||
headers: ['Job', 'Duration', 'Finished', 'Rvm'],
|
||||
jobs: [
|
||||
{
|
||||
id: 1,
|
||||
number: '#1.1',
|
||||
repo: 'travis-ci/travis-core',
|
||||
finishedAt: /\d+ (\w+) ago/,
|
||||
duration: '35 sec',
|
||||
rvm: 'rbx'
|
||||
}, {
|
||||
id: 2,
|
||||
number: '#1.2',
|
||||
repo: 'travis-ci/travis-core',
|
||||
finishedAt: '-',
|
||||
|
@ -42,7 +44,7 @@
|
|||
});
|
||||
return describe('on the "current" state', function() {
|
||||
beforeEach(function() {
|
||||
app('/travis-ci/travis-core');
|
||||
app('!/travis-ci/travis-core');
|
||||
waitFor(repositoriesRendered);
|
||||
return waitFor(buildRendered);
|
||||
});
|
||||
|
@ -63,12 +65,14 @@
|
|||
headers: ['Job', 'Duration', 'Finished', 'Rvm'],
|
||||
jobs: [
|
||||
{
|
||||
id: 1,
|
||||
number: '#1.1',
|
||||
repo: 'travis-ci/travis-core',
|
||||
finishedAt: /\d+ (\w+) ago/,
|
||||
duration: '35 sec',
|
||||
rvm: 'rbx'
|
||||
}, {
|
||||
id: 2,
|
||||
number: '#1.2',
|
||||
repo: 'travis-ci/travis-core',
|
||||
finishedAt: '-',
|
||||
|
@ -87,18 +91,18 @@
|
|||
|
||||
describe('The repositories list', function() {
|
||||
beforeEach(function() {
|
||||
app('/');
|
||||
app('');
|
||||
return waitFor(repositoriesRendered);
|
||||
});
|
||||
it('lists repositories', function() {
|
||||
var href;
|
||||
href = $('#repositories a.slug').attr('href');
|
||||
return expect(href).toEqual('#/travis-ci/travis-core');
|
||||
href = $('#repositories a.current').attr('href');
|
||||
return expect(href).toEqual('#!/travis-ci/travis-core');
|
||||
});
|
||||
return it("links to the repository's last build action", function() {
|
||||
var href;
|
||||
href = $('#repositories a.last_build').attr('href');
|
||||
return expect(href).toEqual('#/travis-ci/travis-core/builds/1');
|
||||
return expect(href).toEqual('#!/travis-ci/travis-core/builds/1');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -107,7 +111,7 @@
|
|||
|
||||
describe('The repository view', function() {
|
||||
beforeEach(function() {
|
||||
app('/');
|
||||
app('');
|
||||
return waitFor(repositoriesRendered);
|
||||
});
|
||||
return it('displays the repository header', function() {
|
||||
|
@ -126,18 +130,16 @@
|
|||
if (Travis.app) {
|
||||
Travis.app.destroy();
|
||||
}
|
||||
return $('body #content').empty();
|
||||
return $('body #content').remove();
|
||||
};
|
||||
|
||||
this.app = function(url) {
|
||||
var router;
|
||||
router = Travis.Router.create({
|
||||
location: Em.NoneLocation.create()
|
||||
$('body').append('<div id="content"></div>');
|
||||
Travis.app = Travis.App.create({
|
||||
rootElement: '#content'
|
||||
});
|
||||
Travis.app = Travis.App.create();
|
||||
Travis.app.set('rootElement', '#content');
|
||||
Travis.app.initialize(router);
|
||||
return router.route(url);
|
||||
Travis.app.initialize();
|
||||
return Em.routes.set('location', url);
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
|
@ -148,7 +150,7 @@
|
|||
(function() {
|
||||
|
||||
this.repositoriesRendered = function() {
|
||||
return $('#repositories li').length > 0;
|
||||
return $('#repositories li a.current').text() !== '';
|
||||
};
|
||||
|
||||
this.buildRendered = function() {
|
||||
|
@ -165,7 +167,7 @@
|
|||
this.displaysBuildSummary = function(data) {
|
||||
var element;
|
||||
element = $('#build .summary .number a');
|
||||
expect(element.attr('href')).toEqual("#/" + data.repo + "/builds/" + data.id);
|
||||
expect(element.attr('href')).toEqual("#!/" + data.repo + "/builds/" + data.id);
|
||||
element = $('#build .summary .finished_at');
|
||||
expect(element.text()).toMatch(/\d+ (\w+) ago/);
|
||||
element = $('#build .summary .duration');
|
||||
|
@ -200,7 +202,7 @@
|
|||
element = $("#jobs tr:nth-child(" + ix + ") td.number");
|
||||
expect(element.text()).toEqual(job.number);
|
||||
element = $("#jobs tr:nth-child(" + ix + ") td.number a");
|
||||
expect(element.attr('href')).toEqual("#/" + job.repo + "/jobs/" + job.id);
|
||||
expect(element.attr('href')).toEqual("#!/" + job.repo + "/jobs/" + job.id);
|
||||
element = $("#jobs tr:nth-child(" + ix + ") td.duration");
|
||||
expect(element.text()).toEqual(job.duration);
|
||||
element = $("#jobs tr:nth-child(" + ix + ") td.finished_at");
|
||||
|
@ -232,6 +234,35 @@
|
|||
}).call(this);
|
||||
(function() {
|
||||
|
||||
|
||||
describe('The tabs view', function() {
|
||||
describe('on the "index" state', function() {
|
||||
beforeEach(function() {
|
||||
app('');
|
||||
return waitFor(repositoriesRendered);
|
||||
});
|
||||
it('has a "current" tab linking to the current build', function() {
|
||||
var href;
|
||||
href = $('#main .tabs a.current').attr('href');
|
||||
return expect(href).toEqual('#!/travis-ci/travis-core');
|
||||
});
|
||||
return it('has a "history" tab linking to the builds list', function() {
|
||||
var href;
|
||||
href = $('#main .tabs a.history').attr('href');
|
||||
return expect(href).toEqual('#!/travis-ci/travis-core/builds');
|
||||
});
|
||||
});
|
||||
return describe('on the "current" state', function() {
|
||||
beforeEach(function() {
|
||||
app('!/travis-ci/travis-core');
|
||||
waitFor(repositoriesRendered);
|
||||
return waitFor(buildRendered);
|
||||
});
|
||||
return it('has a "current" tab linking to the current build', function() {
|
||||
var href;
|
||||
href = $('#main .tabs a.current').attr('href');
|
||||
return expect(href).toEqual('#!/travis-ci/travis-core');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -13,7 +13,6 @@
|
|||
<script src="javascripts/specs/specs.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<script>
|
||||
for(key in minispade.modules)
|
||||
if(key.match(/_spec$/))
|
||||
|
|
Loading…
Reference in New Issue
Block a user