mirror of
https://github.com/discourse/discourse.git
synced 2024-11-28 03:10:11 +08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
24f1832cca
|
@ -1,3 +1,15 @@
|
|||
GIT
|
||||
remote: https://github.com/dysania/onebox.git
|
||||
revision: 6a2f6e6a08f183a4df52f9a51187f566b8ae3a00
|
||||
specs:
|
||||
onebox (1.1.0)
|
||||
hexpress (~> 1.2)
|
||||
moneta (~> 0.7)
|
||||
multi_json (~> 1.7)
|
||||
mustache (~> 0.99)
|
||||
nokogiri (~> 1.6.1)
|
||||
opengraph_parser (~> 0.2.3)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/rails/actionpack-action_caching.git
|
||||
revision: a45e97298f6a77a4d74662521715d5656b821f24
|
||||
|
@ -7,47 +19,47 @@ GIT
|
|||
|
||||
GIT
|
||||
remote: https://github.com/rails/rails.git
|
||||
revision: 4aae538d9ffff3a00a81f3da52fa70f7fd79ac74
|
||||
revision: 3a428f38b2f9a1e995070a4a049645b622c7094a
|
||||
specs:
|
||||
actionmailer (4.1.0.beta)
|
||||
actionpack (= 4.1.0.beta)
|
||||
actionview (= 4.1.0.beta)
|
||||
actionmailer (4.1.0.beta1)
|
||||
actionpack (= 4.1.0.beta1)
|
||||
actionview (= 4.1.0.beta1)
|
||||
mail (~> 2.5.4)
|
||||
actionpack (4.1.0.beta)
|
||||
actionview (= 4.1.0.beta)
|
||||
activesupport (= 4.1.0.beta)
|
||||
actionpack (4.1.0.beta1)
|
||||
actionview (= 4.1.0.beta1)
|
||||
activesupport (= 4.1.0.beta1)
|
||||
rack (~> 1.5.2)
|
||||
rack-test (~> 0.6.2)
|
||||
actionview (4.1.0.beta)
|
||||
activesupport (= 4.1.0.beta)
|
||||
builder (~> 3.1.0)
|
||||
actionview (4.1.0.beta1)
|
||||
activesupport (= 4.1.0.beta1)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
activemodel (4.1.0.beta)
|
||||
activesupport (= 4.1.0.beta)
|
||||
builder (~> 3.1.0)
|
||||
activerecord (4.1.0.beta)
|
||||
activemodel (= 4.1.0.beta)
|
||||
activesupport (= 4.1.0.beta)
|
||||
activemodel (4.1.0.beta1)
|
||||
activesupport (= 4.1.0.beta1)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.1.0.beta1)
|
||||
activemodel (= 4.1.0.beta1)
|
||||
activesupport (= 4.1.0.beta1)
|
||||
arel (~> 5.0.0)
|
||||
activesupport (4.1.0.beta)
|
||||
i18n (~> 0.6, >= 0.6.4)
|
||||
activesupport (4.1.0.beta1)
|
||||
i18n (~> 0.6, >= 0.6.9)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo (~> 1.1)
|
||||
rails (4.1.0.beta)
|
||||
actionmailer (= 4.1.0.beta)
|
||||
actionpack (= 4.1.0.beta)
|
||||
actionview (= 4.1.0.beta)
|
||||
activemodel (= 4.1.0.beta)
|
||||
activerecord (= 4.1.0.beta)
|
||||
activesupport (= 4.1.0.beta)
|
||||
rails (4.1.0.beta1)
|
||||
actionmailer (= 4.1.0.beta1)
|
||||
actionpack (= 4.1.0.beta1)
|
||||
actionview (= 4.1.0.beta1)
|
||||
activemodel (= 4.1.0.beta1)
|
||||
activerecord (= 4.1.0.beta1)
|
||||
activesupport (= 4.1.0.beta1)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.1.0.beta)
|
||||
railties (= 4.1.0.beta1)
|
||||
sprockets-rails (~> 2.0.0)
|
||||
railties (4.1.0.beta)
|
||||
actionpack (= 4.1.0.beta)
|
||||
activesupport (= 4.1.0.beta)
|
||||
railties (4.1.0.beta1)
|
||||
actionpack (= 4.1.0.beta1)
|
||||
activesupport (= 4.1.0.beta1)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
|
||||
|
@ -84,7 +96,7 @@ GEM
|
|||
erubis (>= 2.6.6)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
builder (3.1.4)
|
||||
builder (3.2.2)
|
||||
celluloid (0.15.2)
|
||||
timers (~> 1.1.0)
|
||||
certified (0.1.1)
|
||||
|
@ -144,16 +156,15 @@ GEM
|
|||
fspath (2.0.5)
|
||||
given_core (3.1.1)
|
||||
sorcerer (>= 0.3.7)
|
||||
guess_html_encoding (0.0.9)
|
||||
handlebars-source (1.1.2)
|
||||
hashie (2.0.5)
|
||||
hexpress (1.2.0)
|
||||
highline (1.6.20)
|
||||
hike (1.2.3)
|
||||
hiredis (0.4.5)
|
||||
html_truncator (0.3.1)
|
||||
nokogiri (~> 1.5)
|
||||
httpauth (0.2.0)
|
||||
i18n (0.6.9)
|
||||
ice_cube (0.11.1)
|
||||
image_optim (0.9.1)
|
||||
exifr (~> 1.1.3)
|
||||
fspath (~> 2.0.5)
|
||||
|
@ -186,19 +197,20 @@ GEM
|
|||
metaclass (0.0.1)
|
||||
method_source (0.8.2)
|
||||
mime-types (1.25.1)
|
||||
mini_portile (0.5.1)
|
||||
minitest (5.1.0)
|
||||
mini_portile (0.5.2)
|
||||
minitest (5.2.2)
|
||||
mocha (0.14.0)
|
||||
metaclass (~> 0.0.1)
|
||||
mock_redis (0.9.0)
|
||||
moneta (0.7.20)
|
||||
msgpack (0.5.7)
|
||||
multi_json (1.8.2)
|
||||
multi_json (1.8.4)
|
||||
multipart-post (1.2.0)
|
||||
mustache (0.99.4)
|
||||
net-scp (1.1.2)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (2.7.0)
|
||||
nokogiri (1.6.0)
|
||||
nokogiri (1.6.1)
|
||||
mini_portile (~> 0.5.0)
|
||||
oauth (0.4.7)
|
||||
oauth2 (0.8.1)
|
||||
|
@ -236,6 +248,9 @@ GEM
|
|||
omniauth-twitter (1.0.1)
|
||||
multi_json (~> 1.3)
|
||||
omniauth-oauth (~> 1.0)
|
||||
opengraph_parser (0.2.3)
|
||||
addressable
|
||||
nokogiri
|
||||
openid-redis-store (0.0.2)
|
||||
redis
|
||||
ruby-openid
|
||||
|
@ -269,7 +284,7 @@ GEM
|
|||
rails-observers (0.1.2)
|
||||
activemodel (~> 4.0)
|
||||
raindrops (0.12.0)
|
||||
rake (10.1.0)
|
||||
rake (10.1.1)
|
||||
rake-compiler (0.9.2)
|
||||
rake
|
||||
rb-fsevent (0.9.3)
|
||||
|
@ -281,24 +296,8 @@ GEM
|
|||
trollop (>= 1.16.2)
|
||||
redcarpet (3.0.0)
|
||||
redis (3.0.6)
|
||||
redis-actionpack (4.0.0)
|
||||
actionpack (~> 4)
|
||||
redis-rack (~> 1.5.0)
|
||||
redis-store (~> 1.1.0)
|
||||
redis-activesupport (4.0.0)
|
||||
activesupport (~> 4)
|
||||
redis-store (~> 1.1.0)
|
||||
redis-namespace (1.3.2)
|
||||
redis (~> 3.0.4)
|
||||
redis-rack (1.5.0)
|
||||
rack (~> 1.5)
|
||||
redis-store (~> 1.1.0)
|
||||
redis-rails (4.0.0)
|
||||
redis-actionpack (~> 4)
|
||||
redis-activesupport (~> 4)
|
||||
redis-store (~> 1.1.0)
|
||||
redis-store (1.1.4)
|
||||
redis (>= 2.2)
|
||||
ref (1.0.5)
|
||||
rest-client (1.6.7)
|
||||
mime-types (>= 1.16)
|
||||
|
@ -323,6 +322,9 @@ GEM
|
|||
rspec-mocks (~> 2.14.0)
|
||||
ruby-hmac (0.4.0)
|
||||
ruby-openid (2.3.0)
|
||||
ruby-readability (0.6.0)
|
||||
guess_html_encoding (>= 0.0.4)
|
||||
nokogiri (>= 1.4.2)
|
||||
sanitize (2.0.6)
|
||||
nokogiri (>= 1.4.4)
|
||||
sass (3.2.12)
|
||||
|
@ -347,10 +349,7 @@ GEM
|
|||
redis-namespace (>= 1.3.1)
|
||||
sidekiq-failures (0.2.2)
|
||||
sidekiq (>= 2.9.0)
|
||||
sidetiq (0.4.3)
|
||||
celluloid (>= 0.14.1)
|
||||
ice_cube (~> 0.11.0)
|
||||
sidekiq (~> 2.15.0)
|
||||
simple-rss (1.3.1)
|
||||
simplecov (0.7.1)
|
||||
multi_json (~> 1.0)
|
||||
simplecov-html (~> 0.7.1)
|
||||
|
@ -378,7 +377,7 @@ GEM
|
|||
activesupport (>= 3.0)
|
||||
sprockets (~> 2.8)
|
||||
temple (0.6.7)
|
||||
therubyracer-discourse (0.12.0)
|
||||
therubyracer (0.12.1)
|
||||
libv8 (~> 3.16.14.0)
|
||||
ref
|
||||
thin (1.6.1)
|
||||
|
@ -436,7 +435,6 @@ DEPENDENCIES
|
|||
handlebars-source (~> 1.1.2)
|
||||
highline
|
||||
hiredis
|
||||
html_truncator
|
||||
image_optim
|
||||
image_sorcery
|
||||
librarian (>= 0.0.25)
|
||||
|
@ -458,6 +456,7 @@ DEPENDENCIES
|
|||
omniauth-oauth2
|
||||
omniauth-openid
|
||||
omniauth-twitter
|
||||
onebox!
|
||||
openid-redis-store
|
||||
pg (= 0.15.1)
|
||||
pry-nav
|
||||
|
@ -465,7 +464,7 @@ DEPENDENCIES
|
|||
puma
|
||||
qunit-rails
|
||||
rack-cors
|
||||
rack-mini-profiler (= 0.9.0.pre)
|
||||
rack-mini-profiler
|
||||
rack-protection
|
||||
rails!
|
||||
rails-observers
|
||||
|
@ -476,11 +475,11 @@ DEPENDENCIES
|
|||
rbtrace
|
||||
redcarpet
|
||||
redis
|
||||
redis-rails
|
||||
rest-client
|
||||
rinku
|
||||
rspec-given
|
||||
rspec-rails
|
||||
ruby-readability
|
||||
sanitize
|
||||
sass
|
||||
sass-rails
|
||||
|
@ -488,12 +487,12 @@ DEPENDENCIES
|
|||
shoulda
|
||||
sidekiq (= 2.15.1)
|
||||
sidekiq-failures
|
||||
sidetiq (>= 0.3.6)
|
||||
simple-rss
|
||||
simplecov
|
||||
sinatra
|
||||
slim
|
||||
spork-rails
|
||||
therubyracer-discourse
|
||||
therubyracer
|
||||
thin
|
||||
timecop
|
||||
uglifier
|
||||
|
|
|
@ -24,6 +24,8 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({
|
|||
return Discourse.SiteSettings.must_approve_users;
|
||||
}.property(),
|
||||
|
||||
primaryGroupDirty: Discourse.computed.propertyNotEqual('originalPrimaryGroupId', 'primary_group_id'),
|
||||
|
||||
actions: {
|
||||
toggleTitleEdit: function() {
|
||||
this.toggleProperty('editingTitle');
|
||||
|
@ -44,6 +46,22 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({
|
|||
this.get('model').generateApiKey();
|
||||
},
|
||||
|
||||
savePrimaryGroup: function() {
|
||||
var self = this;
|
||||
Discourse.ajax("/admin/users/" + this.get('id') + "/primary_group", {
|
||||
type: 'PUT',
|
||||
data: {primary_group_id: this.get('primary_group_id')}
|
||||
}).then(function () {
|
||||
self.set('originalPrimaryGroupId', self.get('primary_group_id'));
|
||||
}).catch(function() {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
},
|
||||
|
||||
resetPrimaryGroup: function() {
|
||||
this.set('primary_group_id', this.get('originalPrimaryGroupId'));
|
||||
},
|
||||
|
||||
regenerateApiKey: function() {
|
||||
var self = this;
|
||||
bootbox.confirm(I18n.t("admin.api.confirm_regen"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||
|
|
|
@ -19,7 +19,7 @@ Discourse.AdminDashboard.reopenClass({
|
|||
@return {jqXHR} a jQuery Promise object
|
||||
**/
|
||||
find: function() {
|
||||
return Discourse.ajax("/admin/dashboard").then(function(json) {
|
||||
return Discourse.ajax("/admin/dashboard.json").then(function(json) {
|
||||
var model = Discourse.AdminDashboard.create(json);
|
||||
model.set('loaded', true);
|
||||
return model;
|
||||
|
@ -34,7 +34,7 @@ Discourse.AdminDashboard.reopenClass({
|
|||
@return {jqXHR} a jQuery Promise object
|
||||
**/
|
||||
fetchProblems: function() {
|
||||
return Discourse.ajax("/admin/dashboard/problems", {
|
||||
return Discourse.ajax("/admin/dashboard/problems.json", {
|
||||
type: 'GET',
|
||||
dataType: 'json'
|
||||
}).then(function(json) {
|
||||
|
|
|
@ -388,7 +388,7 @@ Discourse.AdminUser.reopenClass({
|
|||
},
|
||||
|
||||
find: function(username) {
|
||||
return Discourse.ajax("/admin/users/" + username).then(function (result) {
|
||||
return Discourse.ajax("/admin/users/" + username + ".json").then(function (result) {
|
||||
result.loadedDetails = true;
|
||||
return Discourse.AdminUser.create(result);
|
||||
});
|
||||
|
|
|
@ -23,10 +23,16 @@ Discourse.AdminUserRoute = Discourse.Route.extend({
|
|||
afterModel: function(adminUser) {
|
||||
var controller = this.controllerFor('adminUser');
|
||||
|
||||
adminUser.loadDetails().then(function () {
|
||||
return adminUser.loadDetails().then(function () {
|
||||
adminUser.setOriginalTrustLevel();
|
||||
controller.set('model', adminUser);
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties({
|
||||
originalPrimaryGroupId: model.get('primary_group_id'),
|
||||
model: model
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -46,6 +46,25 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n admin.groups.primary}}</div>
|
||||
<div class='value'>
|
||||
{{#if custom_groups}}
|
||||
{{combobox content=custom_groups value=primary_group_id nameProperty="name" none="admin.groups.no_primary"}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if primaryGroupDirty}}
|
||||
<div>
|
||||
<button class='btn ok' {{action savePrimaryGroup}}><i class='fa fa-check'></i></button>
|
||||
<button class='btn cancel' {{action resetPrimaryGroup}}><i class='fa fa-times'></i></button>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n user.ip_address.title}}</div>
|
||||
<div class='value'>{{ip_address}}</div>
|
||||
|
@ -317,7 +336,7 @@
|
|||
|
||||
<section>
|
||||
<hr/>
|
||||
<button {{bind-attr class=":btn :btn-danger :pull-right deleteForbidden:hidden"}} {{action destroy target="content"}} {{bind-attr disabled="deleteForbidden"}} {{bind-attr}}>
|
||||
<button {{bind-attr class=":btn :btn-danger :pull-right deleteForbidden:hidden"}} {{action destroy target="content"}} {{bind-attr disabled="deleteForbidden"}}>
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
{{i18n admin.user.delete}}
|
||||
</button>
|
||||
|
|
10
app/assets/javascripts/admin/views/admin_user_view.js
Normal file
10
app/assets/javascripts/admin/views/admin_user_view.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
The view class for an Admin User
|
||||
|
||||
@class AdminUserView
|
||||
@extends Discourse.View
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.AdminUserView = Discourse.View.extend(Discourse.ScrollTop);
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
Displays a list of groups that a user belongs to.
|
||||
|
||||
@class Discourse.GroupsListComponent
|
||||
@extends Ember.Component
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.GroupsListComponent = Em.Component.extend({
|
||||
classNames: ['groups']
|
||||
});
|
||||
|
|
@ -25,7 +25,10 @@ Discourse.PostGapComponent = Ember.Component.extend({
|
|||
if (this.get('loading')) {
|
||||
buffer.push(I18n.t('loading'));
|
||||
} else {
|
||||
buffer.push(I18n.t('post.gap', {count: this.get('gap.length')}));
|
||||
var gapLength = this.get('gap.length');
|
||||
if (gapLength) {
|
||||
buffer.push(I18n.t('post.gap', {count: gapLength}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ Discourse.StaticController = Discourse.Controller.extend({
|
|||
text = text.match(/<!-- preload-content: -->((?:.|[\n\r])*)<!-- :preload-content -->/)[1];
|
||||
this.set('content', text);
|
||||
} else {
|
||||
return Discourse.ajax(path, {dataType: 'html'}).then(function (result) {
|
||||
return Discourse.ajax(path + ".html", {dataType: 'html'}).then(function (result) {
|
||||
self.set('content', result);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -37,6 +37,11 @@ Discourse.ScreenTrack = Ember.Object.extend({
|
|||
},
|
||||
|
||||
stop: function() {
|
||||
if(!this.get('topicId')) {
|
||||
// already stopped no need to "extra stop"
|
||||
return;
|
||||
}
|
||||
|
||||
this.tick();
|
||||
this.flush();
|
||||
this.reset();
|
||||
|
@ -105,9 +110,10 @@ Discourse.ScreenTrack = Ember.Object.extend({
|
|||
var highestSeenByTopic = Discourse.Session.currentProp('highestSeenByTopic');
|
||||
if ((highestSeenByTopic[topicId] || 0) < highestSeen) {
|
||||
highestSeenByTopic[topicId] = highestSeen;
|
||||
Discourse.TopicTrackingState.current().updateSeen(topicId, highestSeen);
|
||||
}
|
||||
|
||||
Discourse.TopicTrackingState.current().updateSeen(topicId, highestSeen);
|
||||
|
||||
if (!$.isEmptyObject(newTimings)) {
|
||||
Discourse.ajax('/topics/timings', {
|
||||
data: {
|
||||
|
|
18
app/assets/javascripts/discourse/mixins/scroll_top.js
Normal file
18
app/assets/javascripts/discourse/mixins/scroll_top.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
This mixin will cause a view to scroll the viewport to the top once it has been inserted
|
||||
|
||||
@class Discourse.ScrollTop
|
||||
@extends Ember.Mixin
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.ScrollTop = Em.Mixin.create({
|
||||
|
||||
_scrollTop: function() {
|
||||
Em.run.schedule('afterRender', function() {
|
||||
$(document).scrollTop(0);
|
||||
});
|
||||
}.on('didInsertElement'),
|
||||
|
||||
});
|
||||
|
|
@ -39,8 +39,9 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
|||
},
|
||||
|
||||
updateSeen: function(topicId, highestSeen) {
|
||||
if(!topicId || !highestSeen) { return; }
|
||||
var state = this.states["t" + topicId];
|
||||
if(state && state.last_read_post_number < highestSeen) {
|
||||
if(state && (!state.last_read_post_number || state.last_read_post_number < highestSeen)) {
|
||||
state.last_read_post_number = highestSeen;
|
||||
this.incrementMessageCount();
|
||||
}
|
||||
|
@ -84,9 +85,24 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
|||
|
||||
sync: function(list, filter){
|
||||
var tracker = this;
|
||||
var states = this.states;
|
||||
|
||||
if(!list || !list.topics) { return; }
|
||||
|
||||
// compensate for delayed "new" topics
|
||||
// client side we know they are not new, server side we think they are
|
||||
for(var i=list.topics.length-1; i>=0; i--){
|
||||
var state = states["t"+ list.topics[i].id];
|
||||
if(state && state.last_read_post_number > 0){
|
||||
if(filter === "new"){
|
||||
list.topics.splice(i, 1);
|
||||
} else {
|
||||
list.topics[i].unseen = false;
|
||||
list.topics[i].dont_sync = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(filter === "new" && !list.more_topics_url){
|
||||
// scrub all new rows and reload from list
|
||||
_.each(this.states, function(state){
|
||||
|
@ -112,10 +128,11 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
|||
if(topic.unseen) {
|
||||
row.last_read_post_number = null;
|
||||
} else if (topic.unread || topic.new_posts){
|
||||
// subtle issue here
|
||||
row.last_read_post_number = topic.highest_post_number - ((topic.unread||0) + (topic.new_posts||0));
|
||||
} else {
|
||||
delete tracker.states["t" + topic.id];
|
||||
if(!topic.dont_sync) {
|
||||
delete tracker.states["t" + topic.id];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,9 @@ function buildTopicRoute(filter) {
|
|||
},
|
||||
|
||||
model: function() {
|
||||
// attempt to stop early cause we need this to be called before .sync
|
||||
Discourse.ScreenTrack.current().stop();
|
||||
|
||||
return Discourse.TopicList.list(filter).then(function(list) {
|
||||
var tracking = Discourse.TopicTrackingState.current();
|
||||
if (tracking) {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{{#if groups}}
|
||||
{{i18n groups.title count=groups.length}}:
|
||||
{{#each groups}}
|
||||
{{#link-to 'group' this class="group-link"}}{{name}}{{/link-to}}
|
||||
{{/each}}
|
||||
{{/if}}
|
|
@ -29,7 +29,7 @@
|
|||
{{/each}}
|
||||
{{else}}
|
||||
<label>{{i18n category.parent}}</label>
|
||||
{{categoryChooser valueAttribute="id" value=parent_category_id categories=parentCategories}}
|
||||
{{categoryChooser valueAttribute="id" value=parent_category_id categories=parentCategories rootNone=true}}
|
||||
{{/if}}
|
||||
</section>
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
{{/if}}
|
||||
|
||||
{{#if user_title}}<div class="user-title" {{action showPosterExpansion this}}>{{user_title}}</div>{{/if}}
|
||||
{{#if primary_group_name}}<div><a href='/groups/{{unbound primary_group_name}}' class='user-group'>{{unbound primary_group_name}}</a></div>{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="contents">
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
<h3>{{i18n last_post}} {{unboundDate path="user.last_posted_at" leaveAgo="true"}}</h3>
|
||||
<h3>{{i18n joined}} {{unboundDate path="user.created_at" leaveAgo="true"}}</h3>
|
||||
|
||||
{{groups-list groups=user.custom_groups}}
|
||||
|
||||
<div class='bottom'>
|
||||
{{#if user.bio_cooked}}<div class='bio'>{{{user.bio_cooked}}}</div>{{/if}}
|
||||
|
||||
|
|
|
@ -43,14 +43,7 @@
|
|||
|
||||
<div class='bio'>{{{bio_cooked}}}</div>
|
||||
|
||||
{{#if custom_groups}}
|
||||
<div class='groups'>
|
||||
{{i18n groups.title count=custom_groups.length}}:
|
||||
{{#each custom_groups}}
|
||||
{{#link-to 'group' this class="group-link"}}{{name}}{{/link-to}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{groups-list groups=custom_groups}}
|
||||
|
||||
{{#if isSuspended}}
|
||||
<div class='suspended'>
|
||||
|
|
|
@ -26,7 +26,11 @@ Discourse.CategoryChooserView = Discourse.ComboboxView.extend({
|
|||
|
||||
none: function() {
|
||||
if (Discourse.User.currentProp('staff') || Discourse.SiteSettings.allow_uncategorized_topics) {
|
||||
return 'category.none';
|
||||
if (this.get('rootNone')) {
|
||||
return "category.none";
|
||||
} else {
|
||||
return Discourse.Category.list().findBy('id', Discourse.Site.currentProp('uncategorized_category_id'));
|
||||
}
|
||||
} else {
|
||||
return 'category.choose';
|
||||
}
|
||||
|
|
|
@ -11,41 +11,45 @@ Discourse.ComboboxView = Discourse.View.extend({
|
|||
classNames: ['combobox'],
|
||||
valueAttribute: 'id',
|
||||
|
||||
render: function(buffer) {
|
||||
buildData: function(o) {
|
||||
var data = "";
|
||||
if (this.dataAttributes) {
|
||||
this.dataAttributes.forEach(function(a) {
|
||||
data += "data-" + a + "=\"" + o.get(a) + "\" ";
|
||||
});
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
var nameProperty = this.get('nameProperty') || 'name';
|
||||
render: function(buffer) {
|
||||
var nameProperty = this.get('nameProperty') || 'name',
|
||||
none = this.get('none');
|
||||
|
||||
// Add none option if required
|
||||
if (this.get('none')) {
|
||||
buffer.push('<option value="">' + (I18n.t(this.get('none'))) + "</option>");
|
||||
if (typeof none === "string") {
|
||||
buffer.push('<option value="">' + I18n.t(none) + "</option>");
|
||||
} else if (typeof none === "object") {
|
||||
buffer.push("<option value=\"\" " + this.buildData(none) + ">" + Em.get(none, nameProperty) + "</option>");
|
||||
}
|
||||
|
||||
var selected = this.get('value');
|
||||
if (selected) { selected = selected.toString(); }
|
||||
|
||||
if (this.get('content')) {
|
||||
|
||||
var comboboxView = this;
|
||||
_.each(this.get('content'),function(o) {
|
||||
var val = o[comboboxView.get('valueAttribute')];
|
||||
var self = this;
|
||||
this.get('content').forEach(function(o) {
|
||||
var val = o[self.get('valueAttribute')];
|
||||
if (val) { val = val.toString(); }
|
||||
|
||||
var selectedText = (val === selected) ? "selected" : "";
|
||||
|
||||
var data = "";
|
||||
if (comboboxView.dataAttributes) {
|
||||
comboboxView.dataAttributes.forEach(function(a) {
|
||||
data += "data-" + a + "=\"" + o.get(a) + "\" ";
|
||||
});
|
||||
}
|
||||
buffer.push("<option " + selectedText + " value=\"" + val + "\" " + data + ">" + Em.get(o, nameProperty) + "</option>");
|
||||
buffer.push("<option " + selectedText + " value=\"" + val + "\" " + self.buildData(o) + ">" + Em.get(o, nameProperty) + "</option>");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
valueChanged: function() {
|
||||
var $combo = this.$();
|
||||
var val = this.get('value');
|
||||
var $combo = this.$(),
|
||||
val = this.get('value');
|
||||
if (val !== undefined && val !== null) {
|
||||
$combo.val(val.toString());
|
||||
} else {
|
||||
|
@ -55,8 +59,8 @@ Discourse.ComboboxView = Discourse.View.extend({
|
|||
}.observes('value'),
|
||||
|
||||
didInsertElement: function() {
|
||||
var $elem = this.$();
|
||||
var comboboxView = this;
|
||||
var $elem = this.$(),
|
||||
self = this;
|
||||
|
||||
$elem.chosen({ template: this.template, disable_search_threshold: 5 });
|
||||
if (this.overrideWidths) {
|
||||
|
@ -74,7 +78,7 @@ Discourse.ComboboxView = Discourse.View.extend({
|
|||
}
|
||||
|
||||
$elem.chosen().change(function(e) {
|
||||
comboboxView.set('value', $(e.target).val());
|
||||
self.set('value', $(e.target).val());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,15 +7,9 @@
|
|||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.DiscoveryTopicsView = Discourse.View.extend(Discourse.LoadMore, {
|
||||
Discourse.DiscoveryTopicsView = Discourse.View.extend(Discourse.ScrollTop, Discourse.LoadMore, {
|
||||
eyelineSelector: '.topic-list-item',
|
||||
|
||||
_scrollTop: function() {
|
||||
Em.run.schedule('afterRender', function() {
|
||||
$(document).scrollTop(0);
|
||||
});
|
||||
}.on('didInsertElement'),
|
||||
|
||||
actions: {
|
||||
loadMore: function() {
|
||||
var self = this;
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
Discourse.GroupIndexView = Discourse.View.extend(Discourse.LoadMore, {
|
||||
/**
|
||||
Displays all posts within a group
|
||||
|
||||
@class Discourse.GroupIndexView
|
||||
@extends Ember.Mixin
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.GroupIndexView = Discourse.View.extend(Discourse.ScrollTop, Discourse.LoadMore, {
|
||||
eyelineSelector: '.user-stream .item'
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -12,13 +12,21 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
|||
classNameBindings: ['postTypeClass',
|
||||
'selected',
|
||||
'post.hidden:post-hidden',
|
||||
'post.deleted'],
|
||||
'post.deleted',
|
||||
'groupNameClass'],
|
||||
postBinding: 'content',
|
||||
|
||||
postTypeClass: function() {
|
||||
return this.get('post.post_type') === Discourse.Site.currentProp('post_types.moderator_action') ? 'moderator' : 'regular';
|
||||
}.property('post.post_type'),
|
||||
|
||||
groupNameClass: function() {
|
||||
var primaryGroupName = this.get('post.primary_group_name');
|
||||
if (primaryGroupName) {
|
||||
return "group-" + primaryGroupName;
|
||||
}
|
||||
}.property('post.primary_group_name'),
|
||||
|
||||
// If the cooked content changed, add the quote controls
|
||||
cookedChanged: function() {
|
||||
var postView = this;
|
||||
|
|
|
@ -40,6 +40,16 @@
|
|||
margin-top: 0px;
|
||||
color: $primary_medium;
|
||||
}
|
||||
.groups {
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
margin-top: 0px;
|
||||
color: $primary_medium;
|
||||
|
||||
.group-link {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
clear: both;
|
||||
|
|
|
@ -461,17 +461,26 @@ iframe {
|
|||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.contents {
|
||||
|
||||
.contents {
|
||||
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
a {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
a.user-group {
|
||||
margin: 4px 0 0 0;
|
||||
padding: 0px;
|
||||
color: $primary_light;
|
||||
font-size: 80%;
|
||||
width: 100%;
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
h3 a {
|
||||
display: inline;
|
||||
width: auto;
|
||||
|
@ -614,6 +623,7 @@ position: relative;
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.user-title {
|
||||
margin-top: 8px;
|
||||
color: $primary_light;
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
@import "common/foundation/variables";
|
||||
@import "common/foundation/mixins";
|
||||
|
||||
.groups {
|
||||
.group-link {
|
||||
color: $tertiary_lightest;
|
||||
}
|
||||
}
|
||||
|
||||
.user-preferences {
|
||||
input.category-group {
|
||||
width: 500px;
|
||||
|
@ -210,9 +216,6 @@
|
|||
|
||||
h1, h2 {margin-top: 10px;}
|
||||
|
||||
.group-link {
|
||||
color: $tertiary_lightest;
|
||||
}
|
||||
.bio {
|
||||
color: $primary_lighter;
|
||||
|
||||
|
|
|
@ -442,6 +442,7 @@ iframe {
|
|||
float: left;
|
||||
}
|
||||
|
||||
|
||||
.user-title {
|
||||
color: #aaa;
|
||||
padding-top: 2px;
|
||||
|
|
|
@ -17,6 +17,7 @@ class Admin::UsersController < Admin::AdminController
|
|||
:block,
|
||||
:unblock,
|
||||
:trust_level,
|
||||
:primary_group,
|
||||
:generate_api_key,
|
||||
:revoke_api_key]
|
||||
|
||||
|
@ -94,6 +95,13 @@ class Admin::UsersController < Admin::AdminController
|
|||
render_serialized(@user, AdminUserSerializer)
|
||||
end
|
||||
|
||||
def primary_group
|
||||
guardian.ensure_can_change_primary_group!(@user)
|
||||
@user.primary_group_id = params[:primary_group_id]
|
||||
@user.save!
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
def trust_level
|
||||
guardian.ensure_can_change_trust_level!(@user)
|
||||
logger = StaffActionLogger.new(current_user)
|
||||
|
|
|
@ -40,6 +40,11 @@ class ListController < ApplicationController
|
|||
list_opts = build_topic_list_options
|
||||
list_opts.merge!(options) if options
|
||||
user = list_target_user
|
||||
|
||||
if filter == :latest && params[:category].blank?
|
||||
list_opts[:no_definitions] = true
|
||||
end
|
||||
|
||||
list = TopicQuery.new(user, list_opts).public_send("list_#{filter}")
|
||||
list.more_topics_url = construct_url_with(list_opts)
|
||||
if Discourse.anonymous_filters.include?(filter)
|
||||
|
|
|
@ -30,7 +30,8 @@ module Jobs
|
|||
)', post.topic.category_id, CategoryUser.notification_levels[:muted])
|
||||
.each do |user|
|
||||
if Guardian.new(user).can_see?(post)
|
||||
UserNotifications.mailing_list_notify(user, post).deliver
|
||||
message = UserNotifications.mailing_list_notify(user, post)
|
||||
Email::Sender.new(message, :mailing_list, user).send
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Jobs
|
||||
# various consistency checks
|
||||
class EnsureDbConsistency < Jobs::Scheduled
|
||||
every 1.day
|
||||
every 12.hours
|
||||
|
||||
def execute(args)
|
||||
TopicUser.ensure_consistency!
|
||||
|
|
|
@ -220,6 +220,7 @@ class Group < ActiveRecord::Base
|
|||
if @deletions
|
||||
@deletions.each do |gu|
|
||||
gu.destroy
|
||||
User.update_all 'primary_group_id = NULL', ['id = ? AND primary_group_id = ?', gu.user_id, gu.group_id]
|
||||
end
|
||||
end
|
||||
@deletions = nil
|
||||
|
|
|
@ -14,12 +14,14 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
|||
:private_topics_count,
|
||||
:can_delete_all_posts,
|
||||
:can_be_deleted,
|
||||
:suspend_reason
|
||||
:suspend_reason,
|
||||
:primary_group_id
|
||||
|
||||
has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
|
||||
has_one :api_key, serializer: ApiKeySerializer, embed: :objects
|
||||
has_one :suspended_by, serializer: BasicUserSerializer, embed: :objects
|
||||
has_one :leader_requirements, serializer: LeaderRequirementsSerializer, embed: :objects
|
||||
has_many :custom_groups, embed: :object, serializer: BasicGroupSerializer
|
||||
|
||||
def can_revoke_admin
|
||||
scope.can_revoke_admin?(object)
|
||||
|
|
|
@ -29,31 +29,36 @@ class ListableTopicSerializer < BasicTopicSerializer
|
|||
end
|
||||
|
||||
def seen
|
||||
object.user_data.present?
|
||||
return true if !scope || !scope.user
|
||||
return true if object.user_data && !object.user_data.last_read_post_number.nil?
|
||||
return true if object.created_at < scope.user.treat_as_new_topic_start_date
|
||||
false
|
||||
end
|
||||
|
||||
def unseen
|
||||
return false if scope.blank?
|
||||
return false if scope.user.blank?
|
||||
return false if object.user_data.present?
|
||||
return false if object.created_at < scope.user.treat_as_new_topic_start_date
|
||||
true
|
||||
!seen
|
||||
end
|
||||
|
||||
def last_read_post_number
|
||||
return nil unless object.user_data
|
||||
object.user_data.last_read_post_number
|
||||
end
|
||||
alias :include_last_read_post_number? :seen
|
||||
|
||||
def has_user_data
|
||||
!!object.user_data
|
||||
end
|
||||
|
||||
alias :include_last_read_post_number? :has_user_data
|
||||
|
||||
def unread
|
||||
unread_helper.unread_posts
|
||||
end
|
||||
alias :include_unread? :seen
|
||||
alias :include_unread? :has_user_data
|
||||
|
||||
def new_posts
|
||||
unread_helper.new_posts
|
||||
end
|
||||
alias :include_new_posts? :seen
|
||||
alias :include_new_posts? :has_user_data
|
||||
|
||||
def include_excerpt?
|
||||
pinned
|
||||
|
|
|
@ -23,6 +23,7 @@ class PostSerializer < BasicPostSerializer
|
|||
:topic_slug,
|
||||
:topic_id,
|
||||
:display_username,
|
||||
:primary_group_name,
|
||||
:version,
|
||||
:can_edit,
|
||||
:can_delete,
|
||||
|
@ -75,6 +76,11 @@ class PostSerializer < BasicPostSerializer
|
|||
object.user.try(:name)
|
||||
end
|
||||
|
||||
def primary_group_name
|
||||
return nil unless object.user && @topic_view
|
||||
return @topic_view.primary_group_names[object.user.primary_group_id] if object.user.primary_group_id
|
||||
end
|
||||
|
||||
def link_counts
|
||||
return @single_post_link_counts if @single_post_link_counts.present?
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ class TopicListItemSerializer < ListableTopicSerializer
|
|||
def starred
|
||||
object.user_data.starred?
|
||||
end
|
||||
alias :include_starred? :seen
|
||||
alias :include_starred? :has_user_data
|
||||
|
||||
def posters
|
||||
object.posters || []
|
||||
|
|
3
bin/bundle
Executable file
3
bin/bundle
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env ruby
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
||||
load Gem.bin_path('bundler', 'bundle')
|
4
bin/rails
Executable file
4
bin/rails
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env ruby
|
||||
APP_PATH = File.expand_path('../../config/application', __FILE__)
|
||||
require_relative '../config/boot'
|
||||
require 'rails/commands'
|
4
bin/rake
Executable file
4
bin/rake
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env ruby
|
||||
require_relative '../config/boot'
|
||||
require 'rake'
|
||||
Rake.application.run
|
|
@ -1267,6 +1267,8 @@ en:
|
|||
other: "spam x{{count}}"
|
||||
|
||||
groups:
|
||||
primary: "Primary Group"
|
||||
no_primary: "(no primary group)"
|
||||
title: "Groups"
|
||||
edit: "Edit Groups"
|
||||
selector_placeholder: "add users"
|
||||
|
|
|
@ -656,7 +656,23 @@ fr:
|
|||
read_more: "Vous voulez en lire plus? {{catLink}} or {{latestLink}}."
|
||||
|
||||
# keys ending with _MF use message format, see /spec/components/js_local_helper_spec.rb for samples
|
||||
read_more_MF: "Il y a { UNREAD, plural, =0 {} one { is <a href='/unread'>1 unread</a> } other { are <a href='/unread'># unread</a> } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} <a href='/new'>1 new</a> topic} other { {BOTH, select, true{and } false {are } other{}} <a href='/new'># new</a> topics} } remaining, or {CATEGORY, select, true {browse other topics in {catLink}} false {{latestLink}} other {}}."
|
||||
read_more_MF: "Il y a {
|
||||
UNREAD, plural,
|
||||
=0 {}
|
||||
one {
|
||||
<a href='/unread'>1 discussion non-lue</a>
|
||||
} other {
|
||||
<a href='/unread'># discussions non-lues</a>
|
||||
}
|
||||
} {
|
||||
NEW, plural,
|
||||
=0 {}
|
||||
one {
|
||||
{BOTH, select, true{et } false {} other{}} <a href='/new'>1 nouvelle</a> discussion
|
||||
} other {
|
||||
{BOTH, select, true{et } false {} other{}} <a href='/new'># nouvelles</a> discussions
|
||||
}
|
||||
} à lire, ou {CATEGORY, select, true {allez voir les nouvelles discussions dans {catLink}} false {{latestLink}} other {}}."
|
||||
|
||||
browse_all_categories: Voir toutes les catégories
|
||||
|
||||
|
@ -1056,6 +1072,8 @@ fr:
|
|||
categories_list: "Liste des Catégories"
|
||||
|
||||
filters:
|
||||
with_topics: "Discussions %{filter}"
|
||||
with_category: "Discussions %{filter} dans %{category}"
|
||||
latest:
|
||||
title: "Récentes"
|
||||
help: "discussions récentes"
|
||||
|
|
|
@ -58,6 +58,7 @@ Discourse::Application.routes.draw do
|
|||
put "block"
|
||||
put "unblock"
|
||||
put "trust_level"
|
||||
put "primary_group"
|
||||
get "leader_requirements"
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddPrimaryGroupIdToUsers < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :users, :primary_group_id, :integer, null: true
|
||||
end
|
||||
end
|
|
@ -133,6 +133,10 @@ class Guardian
|
|||
user && is_staff?
|
||||
end
|
||||
|
||||
def can_change_primary_group?(user)
|
||||
user && is_staff?
|
||||
end
|
||||
|
||||
def can_change_trust_level?(user)
|
||||
user && is_staff?
|
||||
end
|
||||
|
|
|
@ -70,19 +70,10 @@ class PostCreator
|
|||
@post.save_reply_relationships
|
||||
end
|
||||
|
||||
if @spam
|
||||
GroupMessage.create( Group[:moderators].name,
|
||||
:spam_post_blocked,
|
||||
{ user: @user,
|
||||
limit_once_per: 24.hours,
|
||||
message_params: {domains: @post.linked_hosts.keys.join(', ')} } )
|
||||
elsif @post && !@post.errors.present? && !@opts[:skip_validations]
|
||||
SpamRulesEnforcer.enforce!(@post)
|
||||
end
|
||||
|
||||
handle_spam
|
||||
track_latest_on_category
|
||||
|
||||
enqueue_jobs
|
||||
|
||||
@post
|
||||
end
|
||||
|
||||
|
@ -92,9 +83,7 @@ class PostCreator
|
|||
end
|
||||
|
||||
def self.before_create_tasks(post)
|
||||
if post.reply_to_post_number.present?
|
||||
post.reply_to_user_id ||= Post.select(:user_id).where(topic_id: post.topic_id, post_number: post.reply_to_post_number).first.try(:user_id)
|
||||
end
|
||||
set_reply_user_id(post)
|
||||
|
||||
post.word_count = post.raw.scan(/\w+/).size
|
||||
post.post_number ||= Topic.next_post_number(post.topic_id, post.reply_to_post_number.present?)
|
||||
|
@ -108,18 +97,33 @@ class PostCreator
|
|||
post.last_version_at ||= Time.now
|
||||
end
|
||||
|
||||
def self.set_reply_user_id(post)
|
||||
return unless post.reply_to_post_number.present?
|
||||
|
||||
post.reply_to_user_id ||= Post.select(:user_id).where(topic_id: post.topic_id, post_number: post.reply_to_post_number).first.try(:user_id)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def track_latest_on_category
|
||||
if @post && @post.errors.count == 0 && @topic && @topic.category_id
|
||||
Category.where(id: @topic.category_id).update_all(latest_post_id: @post.id)
|
||||
if @post.post_number == 1
|
||||
Category.where(id: @topic.category_id).update_all(latest_topic_id: @topic.id)
|
||||
end
|
||||
def handle_spam
|
||||
if @spam
|
||||
GroupMessage.create( Group[:moderators].name,
|
||||
:spam_post_blocked,
|
||||
{ user: @user,
|
||||
limit_once_per: 24.hours,
|
||||
message_params: {domains: @post.linked_hosts.keys.join(', ')} } )
|
||||
elsif @post && !@post.errors.present? && !@opts[:skip_validations]
|
||||
SpamRulesEnforcer.enforce!(@post)
|
||||
end
|
||||
end
|
||||
|
||||
def track_latest_on_category
|
||||
return unless @post && @post.errors.count == 0 && @topic && @topic.category_id
|
||||
|
||||
Category.where(id: @topic.category_id).update_all(latest_post_id: @post.id)
|
||||
Category.where(id: @topic.category_id).update_all(latest_topic_id: @topic.id) if @post.post_number == 1
|
||||
end
|
||||
|
||||
def ensure_in_allowed_users
|
||||
return unless @topic.private_message?
|
||||
|
||||
|
@ -135,24 +139,25 @@ class PostCreator
|
|||
end
|
||||
|
||||
def after_post_create
|
||||
if !@topic.private_message? && @post.post_type != Post.types[:moderator_action]
|
||||
if @post.post_number > 1
|
||||
TopicTrackingState.publish_unread(@post)
|
||||
end
|
||||
if SiteSetting.enable_mailing_list_mode
|
||||
Jobs.enqueue_in(
|
||||
SiteSetting.email_time_window_mins.minutes,
|
||||
:notify_mailing_list_subscribers,
|
||||
post_id: @post.id
|
||||
)
|
||||
end
|
||||
return if @topic.private_message? || @post.post_type == Post.types[:moderator_action]
|
||||
|
||||
if @post.post_number > 1
|
||||
TopicTrackingState.publish_unread(@post)
|
||||
end
|
||||
|
||||
if SiteSetting.enable_mailing_list_mode
|
||||
Jobs.enqueue_in(
|
||||
SiteSetting.email_time_window_mins.minutes,
|
||||
:notify_mailing_list_subscribers,
|
||||
post_id: @post.id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def after_topic_create
|
||||
return unless @new_topic
|
||||
# Don't publish invisible topics
|
||||
return unless @topic.visible?
|
||||
|
||||
return if @topic.private_message? || @post.post_type == Post.types[:moderator_action]
|
||||
|
||||
@topic.posters = @topic.posters_summary
|
||||
|
@ -218,13 +223,13 @@ class PostCreator
|
|||
reply_to_post_number: @opts[:reply_to_post_number])
|
||||
|
||||
# Attributes we pass through to the post instance if present
|
||||
[:post_type, :no_bump, :cooking_options, :image_sizes, :acting_user, :invalidate_oneboxes].each do |a|
|
||||
[:post_type, :no_bump, :cooking_options, :image_sizes, :acting_user, :invalidate_oneboxes, :cook_method].each do |a|
|
||||
post.send("#{a}=", @opts[a]) if @opts[a].present?
|
||||
end
|
||||
|
||||
post.cook_method = @opts[:cook_method] if @opts[:cook_method].present?
|
||||
post.extract_quoted_post_numbers
|
||||
post.created_at = Time.zone.parse(@opts[:created_at].to_s) if @opts[:created_at].present?
|
||||
|
||||
@post = post
|
||||
end
|
||||
|
||||
|
@ -249,9 +254,9 @@ class PostCreator
|
|||
end
|
||||
|
||||
def consider_clearing_flags
|
||||
if @topic.private_message? && @post.post_number > 1 && @topic.user_id != @post.user_id
|
||||
clear_possible_flags(@topic)
|
||||
end
|
||||
return unless @topic.private_message? && @post.post_number > 1 && @topic.user_id != @post.user_id
|
||||
|
||||
clear_possible_flags(@topic)
|
||||
end
|
||||
|
||||
def update_user_counts
|
||||
|
@ -266,16 +271,16 @@ class PostCreator
|
|||
end
|
||||
|
||||
def publish
|
||||
if @post.post_number > 1
|
||||
MessageBus.publish("/topic/#{@post.topic_id}",{
|
||||
id: @post.id,
|
||||
created_at: @post.created_at,
|
||||
user: BasicUserSerializer.new(@post.user).as_json(root: false),
|
||||
post_number: @post.post_number
|
||||
},
|
||||
group_ids: secure_group_ids(@topic)
|
||||
)
|
||||
end
|
||||
return unless @post.post_number > 1
|
||||
|
||||
MessageBus.publish("/topic/#{@post.topic_id}",{
|
||||
id: @post.id,
|
||||
created_at: @post.created_at,
|
||||
user: BasicUserSerializer.new(@post.user).as_json(root: false),
|
||||
post_number: @post.post_number
|
||||
},
|
||||
group_ids: secure_group_ids(@topic)
|
||||
)
|
||||
end
|
||||
|
||||
def extract_links
|
||||
|
@ -283,26 +288,26 @@ class PostCreator
|
|||
end
|
||||
|
||||
def track_topic
|
||||
unless @opts[:auto_track] == false
|
||||
TopicUser.auto_track(@user.id, @topic.id, TopicUser.notification_reasons[:created_post])
|
||||
# Update topic user data
|
||||
TopicUser.change(@post.user.id,
|
||||
@post.topic.id,
|
||||
posted: true,
|
||||
last_read_post_number: @post.post_number,
|
||||
seen_post_count: @post.post_number)
|
||||
end
|
||||
return if @opts[:auto_track] == false
|
||||
|
||||
TopicUser.auto_track(@user.id, @topic.id, TopicUser.notification_reasons[:created_post])
|
||||
# Update topic user data
|
||||
TopicUser.change(@post.user.id,
|
||||
@post.topic.id,
|
||||
posted: true,
|
||||
last_read_post_number: @post.post_number,
|
||||
seen_post_count: @post.post_number)
|
||||
end
|
||||
|
||||
def enqueue_jobs
|
||||
if @post && !@post.errors.present?
|
||||
# We need to enqueue jobs after the transaction. Otherwise they might begin before the data has
|
||||
# been comitted.
|
||||
topic_id = @opts[:topic_id] || @topic.try(:id)
|
||||
Jobs.enqueue(:feature_topic_users, topic_id: @topic.id) if topic_id.present?
|
||||
@post.trigger_post_process
|
||||
after_post_create
|
||||
after_topic_create if @new_topic
|
||||
end
|
||||
return unless @post && !@post.errors.present?
|
||||
|
||||
# We need to enqueue jobs after the transaction. Otherwise they might begin before the data has
|
||||
# been comitted.
|
||||
topic_id = @opts[:topic_id] || @topic.try(:id)
|
||||
Jobs.enqueue(:feature_topic_users, topic_id: @topic.id) if topic_id.present?
|
||||
@post.trigger_post_process
|
||||
after_post_create
|
||||
after_topic_create
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,15 @@ module Scheduler
|
|||
RailsMultisite::ConnectionManagement.with_connection("default") do
|
||||
@manager = Scheduler::Manager.without_runner
|
||||
@schedules = Scheduler::Manager.discover_schedules.sort do |a,b|
|
||||
a.schedule_info.next_run <=> b.schedule_info.next_run
|
||||
a_next = a.schedule_info.next_run
|
||||
b_next = b.schedule_info.next_run
|
||||
if a_next && b_next
|
||||
a_next <=> b_next
|
||||
elsif a_next
|
||||
-1
|
||||
else
|
||||
1
|
||||
end
|
||||
end
|
||||
erb File.read(File.join(VIEWS, 'scheduler.erb')), locals: {view_path: VIEWS}
|
||||
end
|
||||
|
|
|
@ -240,8 +240,8 @@ class TopicQuery
|
|||
result = result.listable_topics.includes(category: :topic_only_relative_url)
|
||||
result = result.where('categories.name is null or categories.name <> ?', options[:exclude_category]).references(:categories) if options[:exclude_category]
|
||||
|
||||
# Don't include the category topic unless restricted to that category
|
||||
if options[:category].blank? || options[:no_definitions]
|
||||
# Don't include the category topics if excluded
|
||||
if options[:no_definitions]
|
||||
result = result.where('COALESCE(categories.topic_id, 0) <> topics.id')
|
||||
end
|
||||
|
||||
|
|
|
@ -111,6 +111,22 @@ class TopicView
|
|||
filter_posts_paged(opts[:page].to_i)
|
||||
end
|
||||
|
||||
def primary_group_names
|
||||
return @group_names if @group_names
|
||||
|
||||
primary_group_ids = Set.new
|
||||
@posts.each do |p|
|
||||
primary_group_ids << p.user.primary_group_id if p.user.try(:primary_group_id)
|
||||
end
|
||||
|
||||
result = {}
|
||||
unless primary_group_ids.empty?
|
||||
Group.where(id: primary_group_ids.to_a).pluck(:id, :name).each do |g|
|
||||
result[g[0]] = g[1]
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# Find the sort order for a post in the topic
|
||||
def sort_order_for_post_number(post_number)
|
||||
|
|
|
@ -25,7 +25,7 @@ describe Scheduler::Manager do
|
|||
end
|
||||
end
|
||||
|
||||
let(:manager) { Scheduler::Manager.new(Redis.new) }
|
||||
let(:manager) { Scheduler::Manager.new(DiscourseRedis.new) }
|
||||
|
||||
before do
|
||||
$redis.del manager.class.queue_key
|
||||
|
@ -46,7 +46,7 @@ describe Scheduler::Manager do
|
|||
|
||||
(0..5).map do
|
||||
Thread.new do
|
||||
manager = Scheduler::Manager.new(Redis.new)
|
||||
manager = Scheduler::Manager.new(DiscourseRedis.new)
|
||||
manager.blocking_tick
|
||||
end
|
||||
end.map(&:join)
|
||||
|
|
|
@ -30,12 +30,12 @@ describe TopicQuery do
|
|||
# mods can see hidden topics
|
||||
TopicQuery.new(moderator).list_latest.topics.count.should == 1
|
||||
# admins can see all the topics
|
||||
TopicQuery.new(admin).list_latest.topics.count.should == 2
|
||||
TopicQuery.new(admin).list_latest.topics.count.should == 3
|
||||
|
||||
group.add(user)
|
||||
group.save
|
||||
|
||||
TopicQuery.new(user).list_latest.topics.count.should == 1
|
||||
TopicQuery.new(user).list_latest.topics.count.should == 2
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -140,6 +140,29 @@ describe Admin::UsersController do
|
|||
end
|
||||
end
|
||||
|
||||
context '.primary_group' do
|
||||
before do
|
||||
@another_user = Fabricate(:coding_horror)
|
||||
end
|
||||
|
||||
it "raises an error when the user doesn't have permission" do
|
||||
Guardian.any_instance.expects(:can_change_primary_group?).with(@another_user).returns(false)
|
||||
xhr :put, :primary_group, user_id: @another_user.id
|
||||
response.should be_forbidden
|
||||
end
|
||||
|
||||
it "returns a 404 if the user doesn't exist" do
|
||||
xhr :put, :primary_group, user_id: 123123
|
||||
response.should be_forbidden
|
||||
end
|
||||
|
||||
it "changes the user's primary group" do
|
||||
xhr :put, :primary_group, user_id: @another_user.id, primary_group_id: 2
|
||||
@another_user.reload
|
||||
@another_user.primary_group_id.should == 2
|
||||
end
|
||||
end
|
||||
|
||||
context '.trust_level' do
|
||||
before do
|
||||
@another_user = Fabricate(:coding_horror)
|
||||
|
|
|
@ -1050,4 +1050,35 @@ describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe "primary_group_id" do
|
||||
let!(:user) { Fabricate(:user) }
|
||||
|
||||
it "has no primary_group_id by default" do
|
||||
user.primary_group_id.should be_nil
|
||||
end
|
||||
|
||||
context "when the user has a group" do
|
||||
let!(:group) { Fabricate(:group) }
|
||||
|
||||
before do
|
||||
group.usernames = user.username
|
||||
group.save
|
||||
user.primary_group_id = group.id
|
||||
user.save
|
||||
user.reload
|
||||
end
|
||||
|
||||
it "should allow us to use it as a primary group" do
|
||||
user.primary_group_id.should == group.id
|
||||
|
||||
# If we remove the user from the group
|
||||
group.usernames = ""
|
||||
group.save
|
||||
|
||||
# It should unset it from the primary_group_id
|
||||
user.reload
|
||||
user.primary_group_id.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
21
test/javascripts/models/topic_tracking_state_test.js
Normal file
21
test/javascripts/models/topic_tracking_state_test.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
module("Discourse.TopicTrackingState");
|
||||
|
||||
test("sync", function () {
|
||||
|
||||
var state = Discourse.TopicTrackingState.create();
|
||||
// fake track it
|
||||
state.states["t111"] = {last_read_post_number: null};
|
||||
|
||||
state.updateSeen(111, 7);
|
||||
var list = {topics: [{
|
||||
highest_post_number: null,
|
||||
id: 111,
|
||||
unread: 10,
|
||||
new_posts: 10
|
||||
}]};
|
||||
|
||||
state.sync(list, "new");
|
||||
|
||||
equal(list.topics.length, 0, "expect new topic to be removed as it was seen");
|
||||
|
||||
});
|
186
vendor/assets/javascripts/development/ember.js
vendored
186
vendor/assets/javascripts/development/ember.js
vendored
|
@ -5,7 +5,7 @@
|
|||
* Portions Copyright 2008-2011 Apple Inc. All rights reserved.
|
||||
* @license Licensed under MIT license
|
||||
* See https://raw.github.com/emberjs/ember.js/master/LICENSE
|
||||
* @version 1.3.2+pre.773be0ec
|
||||
* @version 1.3.2
|
||||
*/
|
||||
|
||||
|
||||
|
@ -203,7 +203,7 @@ if (!Ember.testing) {
|
|||
* Portions Copyright 2008-2011 Apple Inc. All rights reserved.
|
||||
* @license Licensed under MIT license
|
||||
* See https://raw.github.com/emberjs/ember.js/master/LICENSE
|
||||
* @version 1.3.2+pre.773be0ec
|
||||
* @version 1.3.2
|
||||
*/
|
||||
|
||||
|
||||
|
@ -286,7 +286,7 @@ var define, requireModule, require, requirejs;
|
|||
|
||||
@class Ember
|
||||
@static
|
||||
@version 1.3.2+pre.773be0ec
|
||||
@version 1.3.2
|
||||
*/
|
||||
|
||||
if ('undefined' === typeof Ember) {
|
||||
|
@ -313,10 +313,10 @@ Ember.toString = function() { return "Ember"; };
|
|||
/**
|
||||
@property VERSION
|
||||
@type String
|
||||
@default '1.3.2+pre.773be0ec'
|
||||
@default '1.3.2'
|
||||
@static
|
||||
*/
|
||||
Ember.VERSION = '1.3.2+pre.773be0ec';
|
||||
Ember.VERSION = '1.3.2';
|
||||
|
||||
/**
|
||||
Standard environmental variables. You can define these in a global `EmberENV`
|
||||
|
@ -1029,7 +1029,7 @@ Ember.guidFor = function guidFor(obj) {
|
|||
// META
|
||||
//
|
||||
|
||||
var META_DESC = Ember.META_DESC = {
|
||||
var META_DESC = {
|
||||
writable: true,
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
|
@ -2505,7 +2505,7 @@ ObserverSet.prototype.clear = function() {
|
|||
|
||||
|
||||
(function() {
|
||||
var META_KEY = Ember.META_KEY,
|
||||
var metaFor = Ember.meta,
|
||||
guidFor = Ember.guidFor,
|
||||
tryFinally = Ember.tryFinally,
|
||||
sendEvent = Ember.sendEvent,
|
||||
|
@ -2536,10 +2536,10 @@ var META_KEY = Ember.META_KEY,
|
|||
@return {void}
|
||||
*/
|
||||
function propertyWillChange(obj, keyName) {
|
||||
var m = obj[META_KEY],
|
||||
watching = (m && m.watching[keyName] > 0) || keyName === 'length',
|
||||
proto = m && m.proto,
|
||||
desc = m && m.descs[keyName];
|
||||
var m = metaFor(obj, false),
|
||||
watching = m.watching[keyName] > 0 || keyName === 'length',
|
||||
proto = m.proto,
|
||||
desc = m.descs[keyName];
|
||||
|
||||
if (!watching) { return; }
|
||||
if (proto === obj) { return; }
|
||||
|
@ -2566,10 +2566,10 @@ Ember.propertyWillChange = propertyWillChange;
|
|||
@return {void}
|
||||
*/
|
||||
function propertyDidChange(obj, keyName) {
|
||||
var m = obj[META_KEY],
|
||||
watching = (m && m.watching[keyName] > 0) || keyName === 'length',
|
||||
proto = m && m.proto,
|
||||
desc = m && m.descs[keyName];
|
||||
var m = metaFor(obj, false),
|
||||
watching = m.watching[keyName] > 0 || keyName === 'length',
|
||||
proto = m.proto,
|
||||
desc = m.descs[keyName];
|
||||
|
||||
if (proto === obj) { return; }
|
||||
|
||||
|
@ -2642,7 +2642,7 @@ function chainsWillChange(obj, keyName, m) {
|
|||
}
|
||||
|
||||
function chainsDidChange(obj, keyName, m, suppressEvents) {
|
||||
if (!(m && m.hasOwnProperty('chainWatchers') &&
|
||||
if (!(m.hasOwnProperty('chainWatchers') &&
|
||||
m.chainWatchers[keyName])) {
|
||||
return;
|
||||
}
|
||||
|
@ -3645,11 +3645,11 @@ var metaFor = Ember.meta, // utils.js
|
|||
MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
|
||||
o_defineProperty = Ember.platform.defineProperty;
|
||||
|
||||
Ember.watchKey = function(obj, keyName, meta) {
|
||||
Ember.watchKey = function(obj, keyName) {
|
||||
// can't watch length on Array - it is special...
|
||||
if (keyName === 'length' && typeOf(obj) === 'array') { return; }
|
||||
|
||||
var m = meta || metaFor(obj), watching = m.watching;
|
||||
var m = metaFor(obj), watching = m.watching;
|
||||
|
||||
// activate watching first time
|
||||
if (!watching[keyName]) {
|
||||
|
@ -3674,8 +3674,8 @@ Ember.watchKey = function(obj, keyName, meta) {
|
|||
};
|
||||
|
||||
|
||||
Ember.unwatchKey = function(obj, keyName, meta) {
|
||||
var m = meta || metaFor(obj), watching = m.watching;
|
||||
Ember.unwatchKey = function(obj, keyName) {
|
||||
var m = metaFor(obj), watching = m.watching;
|
||||
|
||||
if (watching[keyName] === 1) {
|
||||
watching[keyName] = 0;
|
||||
|
@ -3718,8 +3718,7 @@ var metaFor = Ember.meta, // utils.js
|
|||
warn = Ember.warn,
|
||||
watchKey = Ember.watchKey,
|
||||
unwatchKey = Ember.unwatchKey,
|
||||
FIRST_KEY = /^([^\.\*]+)/,
|
||||
META_KEY = Ember.META_KEY;
|
||||
FIRST_KEY = /^([^\.\*]+)/;
|
||||
|
||||
function firstKey(path) {
|
||||
return path.match(FIRST_KEY)[0];
|
||||
|
@ -3753,24 +3752,24 @@ function addChainWatcher(obj, keyName, node) {
|
|||
|
||||
if (!nodes[keyName]) { nodes[keyName] = []; }
|
||||
nodes[keyName].push(node);
|
||||
watchKey(obj, keyName, m);
|
||||
watchKey(obj, keyName);
|
||||
}
|
||||
|
||||
var removeChainWatcher = Ember.removeChainWatcher = function(obj, keyName, node) {
|
||||
if (!obj || 'object' !== typeof obj) { return; } // nothing to do
|
||||
|
||||
var m = obj[META_KEY];
|
||||
if (m && !m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
|
||||
var m = metaFor(obj, false);
|
||||
if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
|
||||
|
||||
var nodes = m && m.chainWatchers;
|
||||
var nodes = m.chainWatchers;
|
||||
|
||||
if (nodes && nodes[keyName]) {
|
||||
if (nodes[keyName]) {
|
||||
nodes = nodes[keyName];
|
||||
for (var i = 0, l = nodes.length; i < l; i++) {
|
||||
if (nodes[i] === node) { nodes.splice(i, 1); }
|
||||
}
|
||||
}
|
||||
unwatchKey(obj, keyName, m);
|
||||
unwatchKey(obj, keyName);
|
||||
};
|
||||
|
||||
// A ChainNode watches a single key on an object. If you provide a starting
|
||||
|
@ -3810,14 +3809,14 @@ var ChainNodePrototype = ChainNode.prototype;
|
|||
function lazyGet(obj, key) {
|
||||
if (!obj) return undefined;
|
||||
|
||||
var meta = obj[META_KEY];
|
||||
var meta = metaFor(obj, false);
|
||||
// check if object meant only to be a prototype
|
||||
if (meta && meta.proto === obj) return undefined;
|
||||
if (meta.proto === obj) return undefined;
|
||||
|
||||
if (key === "@each") return get(obj, key);
|
||||
|
||||
// if a CP only return cached value
|
||||
var desc = meta && meta.descs[key];
|
||||
var desc = meta.descs[key];
|
||||
if (desc && desc._cacheable) {
|
||||
if (key in meta.cache) {
|
||||
return meta.cache[key];
|
||||
|
@ -4029,14 +4028,12 @@ ChainNodePrototype.didChange = function(events) {
|
|||
};
|
||||
|
||||
Ember.finishChains = function(obj) {
|
||||
// We only create meta if we really have to
|
||||
var m = obj[META_KEY], chains = m && m.chains;
|
||||
var m = metaFor(obj, false), chains = m.chains;
|
||||
if (chains) {
|
||||
if (chains.value() !== obj) {
|
||||
metaFor(obj).chains = chains = chains.copy(obj);
|
||||
} else {
|
||||
chains.didChange(null);
|
||||
m.chains = chains = chains.copy(obj);
|
||||
}
|
||||
chains.didChange(null);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -4058,8 +4055,8 @@ var metaFor = Ember.meta, // utils.js
|
|||
// get the chains for the current object. If the current object has
|
||||
// chains inherited from the proto they will be cloned and reconfigured for
|
||||
// the current object.
|
||||
function chainsFor(obj, meta) {
|
||||
var m = meta || metaFor(obj), ret = m.chains;
|
||||
function chainsFor(obj) {
|
||||
var m = metaFor(obj), ret = m.chains;
|
||||
if (!ret) {
|
||||
ret = m.chains = new ChainNode(null, null, obj);
|
||||
} else if (ret.value() !== obj) {
|
||||
|
@ -4068,26 +4065,26 @@ function chainsFor(obj, meta) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
Ember.watchPath = function(obj, keyPath, meta) {
|
||||
Ember.watchPath = function(obj, keyPath) {
|
||||
// can't watch length on Array - it is special...
|
||||
if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
|
||||
|
||||
var m = meta || metaFor(obj), watching = m.watching;
|
||||
var m = metaFor(obj), watching = m.watching;
|
||||
|
||||
if (!watching[keyPath]) { // activate watching first time
|
||||
watching[keyPath] = 1;
|
||||
chainsFor(obj, m).add(keyPath);
|
||||
chainsFor(obj).add(keyPath);
|
||||
} else {
|
||||
watching[keyPath] = (watching[keyPath] || 0) + 1;
|
||||
}
|
||||
};
|
||||
|
||||
Ember.unwatchPath = function(obj, keyPath, meta) {
|
||||
var m = meta || metaFor(obj), watching = m.watching;
|
||||
Ember.unwatchPath = function(obj, keyPath) {
|
||||
var m = metaFor(obj), watching = m.watching;
|
||||
|
||||
if (watching[keyPath] === 1) {
|
||||
watching[keyPath] = 0;
|
||||
chainsFor(obj, m).remove(keyPath);
|
||||
chainsFor(obj).remove(keyPath);
|
||||
} else if (watching[keyPath] > 1) {
|
||||
watching[keyPath]--;
|
||||
}
|
||||
|
@ -4131,14 +4128,14 @@ function isKeyName(path) {
|
|||
@param obj
|
||||
@param {String} keyName
|
||||
*/
|
||||
Ember.watch = function(obj, _keyPath, m) {
|
||||
Ember.watch = function(obj, _keyPath) {
|
||||
// can't watch length on Array - it is special...
|
||||
if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
|
||||
|
||||
if (isKeyName(_keyPath)) {
|
||||
watchKey(obj, _keyPath, m);
|
||||
watchKey(obj, _keyPath);
|
||||
} else {
|
||||
watchPath(obj, _keyPath, m);
|
||||
watchPath(obj, _keyPath);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -4149,14 +4146,14 @@ Ember.isWatching = function isWatching(obj, key) {
|
|||
|
||||
Ember.watch.flushPending = Ember.flushPendingChains;
|
||||
|
||||
Ember.unwatch = function(obj, _keyPath, m) {
|
||||
Ember.unwatch = function(obj, _keyPath) {
|
||||
// can't watch length on Array - it is special...
|
||||
if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
|
||||
|
||||
if (isKeyName(_keyPath)) {
|
||||
unwatchKey(obj, _keyPath, m);
|
||||
unwatchKey(obj, _keyPath);
|
||||
} else {
|
||||
unwatchPath(obj, _keyPath, m);
|
||||
unwatchPath(obj, _keyPath);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -4171,7 +4168,7 @@ Ember.unwatch = function(obj, _keyPath, m) {
|
|||
@param obj
|
||||
*/
|
||||
Ember.rewatch = function(obj) {
|
||||
var m = obj[META_KEY], chains = m && m.chains;
|
||||
var m = metaFor(obj, false), chains = m.chains;
|
||||
|
||||
// make sure the object has its own guid.
|
||||
if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
|
||||
|
@ -4297,7 +4294,7 @@ function addDependentKeys(desc, obj, keyName, meta) {
|
|||
// Increment the number of times depKey depends on keyName.
|
||||
keys[keyName] = (keys[keyName] || 0) + 1;
|
||||
// Watch the depKey
|
||||
watch(obj, depKey, meta);
|
||||
watch(obj, depKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4316,7 +4313,7 @@ function removeDependentKeys(desc, obj, keyName, meta) {
|
|||
// Increment the number of times depKey depends on keyName.
|
||||
keys[keyName] = (keys[keyName] || 0) - 1;
|
||||
// Watch the depKey
|
||||
unwatch(obj, depKey, meta);
|
||||
unwatch(obj, depKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4759,8 +4756,7 @@ Ember.computed = function(func) {
|
|||
@return {Object} the cached value
|
||||
*/
|
||||
Ember.cacheFor = function cacheFor(obj, key) {
|
||||
var meta = obj[META_KEY],
|
||||
cache = meta && meta.cache;
|
||||
var cache = metaFor(obj, false).cache;
|
||||
|
||||
if (cache && key in cache) {
|
||||
return cache[key];
|
||||
|
@ -7134,13 +7130,11 @@ var Mixin, REQUIRED, Alias,
|
|||
a_slice = [].slice,
|
||||
o_create = Ember.create,
|
||||
defineProperty = Ember.defineProperty,
|
||||
guidFor = Ember.guidFor,
|
||||
metaFor = Ember.meta,
|
||||
META_KEY = Ember.META_KEY;
|
||||
guidFor = Ember.guidFor;
|
||||
|
||||
|
||||
function mixinsMeta(obj) {
|
||||
var m = metaFor(obj, true), ret = m.mixins;
|
||||
var m = Ember.meta(obj, true), ret = m.mixins;
|
||||
if (!ret) {
|
||||
ret = m.mixins = {};
|
||||
} else if (!m.hasOwnProperty('mixins')) {
|
||||
|
@ -7325,7 +7319,7 @@ function mergeMixins(mixins, m, descs, values, base, keys) {
|
|||
if (props === CONTINUE) { continue; }
|
||||
|
||||
if (props) {
|
||||
meta = metaFor(base);
|
||||
meta = Ember.meta(base);
|
||||
if (base.willMergeMixin) { base.willMergeMixin(props); }
|
||||
concats = concatenatedMixinProperties('concatenatedProperties', props, values, base);
|
||||
mergings = concatenatedMixinProperties('mergedProperties', props, values, base);
|
||||
|
@ -7383,7 +7377,7 @@ function connectBindings(obj, m) {
|
|||
}
|
||||
|
||||
function finishPartial(obj, m) {
|
||||
connectBindings(obj, m || metaFor(obj));
|
||||
connectBindings(obj, m || Ember.meta(obj));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -7430,7 +7424,7 @@ function replaceObserversAndListeners(obj, key, observerOrListener) {
|
|||
}
|
||||
|
||||
function applyMixin(obj, mixins, partial) {
|
||||
var descs = {}, values = {}, m = metaFor(obj),
|
||||
var descs = {}, values = {}, m = Ember.meta(obj),
|
||||
key, value, desc, keys = [];
|
||||
|
||||
// Go through all mixins and hashes passed in, and:
|
||||
|
@ -7642,8 +7636,7 @@ function _detect(curMixin, targetMixin, seen) {
|
|||
MixinPrototype.detect = function(obj) {
|
||||
if (!obj) { return false; }
|
||||
if (obj instanceof Mixin) { return _detect(obj, this, {}); }
|
||||
var m = obj[META_KEY],
|
||||
mixins = m && m.mixins;
|
||||
var mixins = Ember.meta(obj, false).mixins;
|
||||
if (mixins) {
|
||||
return !!mixins[guidFor(this)];
|
||||
}
|
||||
|
@ -7682,8 +7675,7 @@ MixinPrototype.keys = function() {
|
|||
// returns the mixins currently applied to the specified object
|
||||
// TODO: Make Ember.mixin
|
||||
Mixin.mixins = function(obj) {
|
||||
var m = obj[META_KEY],
|
||||
mixins = m && m.mixins, ret = [];
|
||||
var mixins = Ember.meta(obj, false).mixins, ret = [];
|
||||
|
||||
if (!mixins) { return ret; }
|
||||
|
||||
|
@ -9579,7 +9571,7 @@ define("rsvp/promise/all",
|
|||
```
|
||||
|
||||
@method all
|
||||
@for Ember.RSVP.Promise
|
||||
@for RSVP.Promise
|
||||
@param {Array} entries array of promises
|
||||
@param {String} label optional string for labeling the promise.
|
||||
Useful for tooling.
|
||||
|
@ -12215,7 +12207,6 @@ var set = Ember.set, get = Ember.get,
|
|||
guidFor = Ember.guidFor,
|
||||
generateGuid = Ember.generateGuid,
|
||||
meta = Ember.meta,
|
||||
META_KEY = Ember.META_KEY,
|
||||
rewatch = Ember.rewatch,
|
||||
finishChains = Ember.finishChains,
|
||||
sendEvent = Ember.sendEvent,
|
||||
|
@ -12909,8 +12900,7 @@ var ClassMixin = Mixin.create({
|
|||
@param key {String} property name
|
||||
*/
|
||||
metaForProperty: function(key) {
|
||||
var meta = this.proto()[META_KEY],
|
||||
desc = meta && meta.descs[key];
|
||||
var desc = meta(this.proto(), false).descs[key];
|
||||
|
||||
Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
|
||||
return desc._meta || {};
|
||||
|
@ -25403,8 +25393,9 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, {
|
|||
set(this, 'controller', this);
|
||||
},
|
||||
|
||||
defaultLayout: function(context, options){
|
||||
Ember.Handlebars.helpers['yield'].call(context, options);
|
||||
defaultLayout: function(options){
|
||||
options.data = {view: options._context};
|
||||
Ember.Handlebars.helpers['yield'].apply(this, [options]);
|
||||
},
|
||||
|
||||
// during render, isolate keywords
|
||||
|
@ -26510,6 +26501,33 @@ var handlebarsGet = Ember.Handlebars.get = function(root, path, options) {
|
|||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
This method uses `Ember.Handlebars.get` to lookup a value, then ensures
|
||||
that the value is escaped properly.
|
||||
|
||||
If `unescaped` is a truthy value then the escaping will not be performed.
|
||||
|
||||
@method getEscaped
|
||||
@for Ember.Handlebars
|
||||
@param {Object} root The object to look up the property on
|
||||
@param {String} path The path to be lookedup
|
||||
@param {Object} options The template's option hash
|
||||
*/
|
||||
Ember.Handlebars.getEscaped = function(root, path, options) {
|
||||
var result = handlebarsGet(root, path, options);
|
||||
|
||||
if (result === null || result === undefined) {
|
||||
result = "";
|
||||
} else if (!(result instanceof Handlebars.SafeString)) {
|
||||
result = String(result);
|
||||
}
|
||||
if (!options.hash.unescaped){
|
||||
result = Handlebars.Utils.escapeExpression(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Ember.Handlebars.resolveParams = function(context, params, options) {
|
||||
var resolvedParams = [], types = options.types, param, type;
|
||||
|
||||
|
@ -27467,6 +27485,7 @@ Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
|
|||
|
||||
var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
|
||||
var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
|
||||
var handlebarsGetEscaped = Ember.Handlebars.getEscaped;
|
||||
var forEach = Ember.ArrayPolyfills.forEach;
|
||||
var o_create = Ember.create;
|
||||
|
||||
|
@ -27476,20 +27495,6 @@ function exists(value) {
|
|||
return !Ember.isNone(value);
|
||||
}
|
||||
|
||||
function sanitizedHandlebarsGet(currentContext, property, options) {
|
||||
var result = handlebarsGet(currentContext, property, options);
|
||||
if (result === null || result === undefined) {
|
||||
result = "";
|
||||
} else if (!(result instanceof Handlebars.SafeString)) {
|
||||
result = String(result);
|
||||
}
|
||||
if (!options.hash.unescaped){
|
||||
result = Handlebars.Utils.escapeExpression(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Binds a property into the DOM. This will create a hook in DOM that the
|
||||
// KVO system will look for and update if the property changes.
|
||||
function bind(property, options, preserveContext, shouldDisplay, valueNormalizer, childProperties) {
|
||||
|
@ -27560,7 +27565,7 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer
|
|||
} else {
|
||||
// The object is not observable, so just render it out and
|
||||
// be done with it.
|
||||
data.buffer.push(handlebarsGet(currentContext, property, options));
|
||||
data.buffer.push(handlebarsGetEscaped(currentContext, property, options));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27581,7 +27586,7 @@ function simpleBind(currentContext, property, options) {
|
|||
Ember.run.once(view, 'rerender');
|
||||
};
|
||||
|
||||
output = sanitizedHandlebarsGet(currentContext, property, options);
|
||||
output = handlebarsGetEscaped(currentContext, property, options);
|
||||
|
||||
data.buffer.push(output);
|
||||
} else {
|
||||
|
@ -27607,8 +27612,7 @@ function simpleBind(currentContext, property, options) {
|
|||
} else {
|
||||
// The object is not observable, so just render it out and
|
||||
// be done with it.
|
||||
output = sanitizedHandlebarsGet(currentContext, property, options);
|
||||
|
||||
output = handlebarsGetEscaped(currentContext, property, options);
|
||||
data.buffer.push(output);
|
||||
}
|
||||
}
|
||||
|
@ -36178,7 +36182,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
|
|||
if (linkType === 'ID') {
|
||||
options.linkTextPath = linkTitle;
|
||||
options.fn = function() {
|
||||
return Ember.Handlebars.get(context, linkTitle, options);
|
||||
return Ember.Handlebars.getEscaped(context, linkTitle, options);
|
||||
};
|
||||
} else {
|
||||
options.fn = function() {
|
||||
|
|
184
vendor/assets/javascripts/production/ember.js
vendored
184
vendor/assets/javascripts/production/ember.js
vendored
|
@ -5,7 +5,7 @@
|
|||
* Portions Copyright 2008-2011 Apple Inc. All rights reserved.
|
||||
* @license Licensed under MIT license
|
||||
* See https://raw.github.com/emberjs/ember.js/master/LICENSE
|
||||
* @version 1.3.2+pre.773be0ec
|
||||
* @version 1.3.2
|
||||
*/
|
||||
|
||||
|
||||
|
@ -88,7 +88,7 @@ var define, requireModule, require, requirejs;
|
|||
|
||||
@class Ember
|
||||
@static
|
||||
@version 1.3.2+pre.773be0ec
|
||||
@version 1.3.2
|
||||
*/
|
||||
|
||||
if ('undefined' === typeof Ember) {
|
||||
|
@ -115,10 +115,10 @@ Ember.toString = function() { return "Ember"; };
|
|||
/**
|
||||
@property VERSION
|
||||
@type String
|
||||
@default '1.3.2+pre.773be0ec'
|
||||
@default '1.3.2'
|
||||
@static
|
||||
*/
|
||||
Ember.VERSION = '1.3.2+pre.773be0ec';
|
||||
Ember.VERSION = '1.3.2';
|
||||
|
||||
/**
|
||||
Standard environmental variables. You can define these in a global `EmberENV`
|
||||
|
@ -831,7 +831,7 @@ Ember.guidFor = function guidFor(obj) {
|
|||
// META
|
||||
//
|
||||
|
||||
var META_DESC = Ember.META_DESC = {
|
||||
var META_DESC = {
|
||||
writable: true,
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
|
@ -2302,7 +2302,7 @@ ObserverSet.prototype.clear = function() {
|
|||
|
||||
|
||||
(function() {
|
||||
var META_KEY = Ember.META_KEY,
|
||||
var metaFor = Ember.meta,
|
||||
guidFor = Ember.guidFor,
|
||||
tryFinally = Ember.tryFinally,
|
||||
sendEvent = Ember.sendEvent,
|
||||
|
@ -2333,10 +2333,10 @@ var META_KEY = Ember.META_KEY,
|
|||
@return {void}
|
||||
*/
|
||||
function propertyWillChange(obj, keyName) {
|
||||
var m = obj[META_KEY],
|
||||
watching = (m && m.watching[keyName] > 0) || keyName === 'length',
|
||||
proto = m && m.proto,
|
||||
desc = m && m.descs[keyName];
|
||||
var m = metaFor(obj, false),
|
||||
watching = m.watching[keyName] > 0 || keyName === 'length',
|
||||
proto = m.proto,
|
||||
desc = m.descs[keyName];
|
||||
|
||||
if (!watching) { return; }
|
||||
if (proto === obj) { return; }
|
||||
|
@ -2363,10 +2363,10 @@ Ember.propertyWillChange = propertyWillChange;
|
|||
@return {void}
|
||||
*/
|
||||
function propertyDidChange(obj, keyName) {
|
||||
var m = obj[META_KEY],
|
||||
watching = (m && m.watching[keyName] > 0) || keyName === 'length',
|
||||
proto = m && m.proto,
|
||||
desc = m && m.descs[keyName];
|
||||
var m = metaFor(obj, false),
|
||||
watching = m.watching[keyName] > 0 || keyName === 'length',
|
||||
proto = m.proto,
|
||||
desc = m.descs[keyName];
|
||||
|
||||
if (proto === obj) { return; }
|
||||
|
||||
|
@ -2439,7 +2439,7 @@ function chainsWillChange(obj, keyName, m) {
|
|||
}
|
||||
|
||||
function chainsDidChange(obj, keyName, m, suppressEvents) {
|
||||
if (!(m && m.hasOwnProperty('chainWatchers') &&
|
||||
if (!(m.hasOwnProperty('chainWatchers') &&
|
||||
m.chainWatchers[keyName])) {
|
||||
return;
|
||||
}
|
||||
|
@ -3437,11 +3437,11 @@ var metaFor = Ember.meta, // utils.js
|
|||
MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
|
||||
o_defineProperty = Ember.platform.defineProperty;
|
||||
|
||||
Ember.watchKey = function(obj, keyName, meta) {
|
||||
Ember.watchKey = function(obj, keyName) {
|
||||
// can't watch length on Array - it is special...
|
||||
if (keyName === 'length' && typeOf(obj) === 'array') { return; }
|
||||
|
||||
var m = meta || metaFor(obj), watching = m.watching;
|
||||
var m = metaFor(obj), watching = m.watching;
|
||||
|
||||
// activate watching first time
|
||||
if (!watching[keyName]) {
|
||||
|
@ -3466,8 +3466,8 @@ Ember.watchKey = function(obj, keyName, meta) {
|
|||
};
|
||||
|
||||
|
||||
Ember.unwatchKey = function(obj, keyName, meta) {
|
||||
var m = meta || metaFor(obj), watching = m.watching;
|
||||
Ember.unwatchKey = function(obj, keyName) {
|
||||
var m = metaFor(obj), watching = m.watching;
|
||||
|
||||
if (watching[keyName] === 1) {
|
||||
watching[keyName] = 0;
|
||||
|
@ -3510,8 +3510,7 @@ var metaFor = Ember.meta, // utils.js
|
|||
warn = Ember.warn,
|
||||
watchKey = Ember.watchKey,
|
||||
unwatchKey = Ember.unwatchKey,
|
||||
FIRST_KEY = /^([^\.\*]+)/,
|
||||
META_KEY = Ember.META_KEY;
|
||||
FIRST_KEY = /^([^\.\*]+)/;
|
||||
|
||||
function firstKey(path) {
|
||||
return path.match(FIRST_KEY)[0];
|
||||
|
@ -3545,24 +3544,24 @@ function addChainWatcher(obj, keyName, node) {
|
|||
|
||||
if (!nodes[keyName]) { nodes[keyName] = []; }
|
||||
nodes[keyName].push(node);
|
||||
watchKey(obj, keyName, m);
|
||||
watchKey(obj, keyName);
|
||||
}
|
||||
|
||||
var removeChainWatcher = Ember.removeChainWatcher = function(obj, keyName, node) {
|
||||
if (!obj || 'object' !== typeof obj) { return; } // nothing to do
|
||||
|
||||
var m = obj[META_KEY];
|
||||
if (m && !m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
|
||||
var m = metaFor(obj, false);
|
||||
if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
|
||||
|
||||
var nodes = m && m.chainWatchers;
|
||||
var nodes = m.chainWatchers;
|
||||
|
||||
if (nodes && nodes[keyName]) {
|
||||
if (nodes[keyName]) {
|
||||
nodes = nodes[keyName];
|
||||
for (var i = 0, l = nodes.length; i < l; i++) {
|
||||
if (nodes[i] === node) { nodes.splice(i, 1); }
|
||||
}
|
||||
}
|
||||
unwatchKey(obj, keyName, m);
|
||||
unwatchKey(obj, keyName);
|
||||
};
|
||||
|
||||
// A ChainNode watches a single key on an object. If you provide a starting
|
||||
|
@ -3602,14 +3601,14 @@ var ChainNodePrototype = ChainNode.prototype;
|
|||
function lazyGet(obj, key) {
|
||||
if (!obj) return undefined;
|
||||
|
||||
var meta = obj[META_KEY];
|
||||
var meta = metaFor(obj, false);
|
||||
// check if object meant only to be a prototype
|
||||
if (meta && meta.proto === obj) return undefined;
|
||||
if (meta.proto === obj) return undefined;
|
||||
|
||||
if (key === "@each") return get(obj, key);
|
||||
|
||||
// if a CP only return cached value
|
||||
var desc = meta && meta.descs[key];
|
||||
var desc = meta.descs[key];
|
||||
if (desc && desc._cacheable) {
|
||||
if (key in meta.cache) {
|
||||
return meta.cache[key];
|
||||
|
@ -3821,14 +3820,12 @@ ChainNodePrototype.didChange = function(events) {
|
|||
};
|
||||
|
||||
Ember.finishChains = function(obj) {
|
||||
// We only create meta if we really have to
|
||||
var m = obj[META_KEY], chains = m && m.chains;
|
||||
var m = metaFor(obj, false), chains = m.chains;
|
||||
if (chains) {
|
||||
if (chains.value() !== obj) {
|
||||
metaFor(obj).chains = chains = chains.copy(obj);
|
||||
} else {
|
||||
chains.didChange(null);
|
||||
m.chains = chains = chains.copy(obj);
|
||||
}
|
||||
chains.didChange(null);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3850,8 +3847,8 @@ var metaFor = Ember.meta, // utils.js
|
|||
// get the chains for the current object. If the current object has
|
||||
// chains inherited from the proto they will be cloned and reconfigured for
|
||||
// the current object.
|
||||
function chainsFor(obj, meta) {
|
||||
var m = meta || metaFor(obj), ret = m.chains;
|
||||
function chainsFor(obj) {
|
||||
var m = metaFor(obj), ret = m.chains;
|
||||
if (!ret) {
|
||||
ret = m.chains = new ChainNode(null, null, obj);
|
||||
} else if (ret.value() !== obj) {
|
||||
|
@ -3860,26 +3857,26 @@ function chainsFor(obj, meta) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
Ember.watchPath = function(obj, keyPath, meta) {
|
||||
Ember.watchPath = function(obj, keyPath) {
|
||||
// can't watch length on Array - it is special...
|
||||
if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
|
||||
|
||||
var m = meta || metaFor(obj), watching = m.watching;
|
||||
var m = metaFor(obj), watching = m.watching;
|
||||
|
||||
if (!watching[keyPath]) { // activate watching first time
|
||||
watching[keyPath] = 1;
|
||||
chainsFor(obj, m).add(keyPath);
|
||||
chainsFor(obj).add(keyPath);
|
||||
} else {
|
||||
watching[keyPath] = (watching[keyPath] || 0) + 1;
|
||||
}
|
||||
};
|
||||
|
||||
Ember.unwatchPath = function(obj, keyPath, meta) {
|
||||
var m = meta || metaFor(obj), watching = m.watching;
|
||||
Ember.unwatchPath = function(obj, keyPath) {
|
||||
var m = metaFor(obj), watching = m.watching;
|
||||
|
||||
if (watching[keyPath] === 1) {
|
||||
watching[keyPath] = 0;
|
||||
chainsFor(obj, m).remove(keyPath);
|
||||
chainsFor(obj).remove(keyPath);
|
||||
} else if (watching[keyPath] > 1) {
|
||||
watching[keyPath]--;
|
||||
}
|
||||
|
@ -3923,14 +3920,14 @@ function isKeyName(path) {
|
|||
@param obj
|
||||
@param {String} keyName
|
||||
*/
|
||||
Ember.watch = function(obj, _keyPath, m) {
|
||||
Ember.watch = function(obj, _keyPath) {
|
||||
// can't watch length on Array - it is special...
|
||||
if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
|
||||
|
||||
if (isKeyName(_keyPath)) {
|
||||
watchKey(obj, _keyPath, m);
|
||||
watchKey(obj, _keyPath);
|
||||
} else {
|
||||
watchPath(obj, _keyPath, m);
|
||||
watchPath(obj, _keyPath);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3941,14 +3938,14 @@ Ember.isWatching = function isWatching(obj, key) {
|
|||
|
||||
Ember.watch.flushPending = Ember.flushPendingChains;
|
||||
|
||||
Ember.unwatch = function(obj, _keyPath, m) {
|
||||
Ember.unwatch = function(obj, _keyPath) {
|
||||
// can't watch length on Array - it is special...
|
||||
if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
|
||||
|
||||
if (isKeyName(_keyPath)) {
|
||||
unwatchKey(obj, _keyPath, m);
|
||||
unwatchKey(obj, _keyPath);
|
||||
} else {
|
||||
unwatchPath(obj, _keyPath, m);
|
||||
unwatchPath(obj, _keyPath);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3963,7 +3960,7 @@ Ember.unwatch = function(obj, _keyPath, m) {
|
|||
@param obj
|
||||
*/
|
||||
Ember.rewatch = function(obj) {
|
||||
var m = obj[META_KEY], chains = m && m.chains;
|
||||
var m = metaFor(obj, false), chains = m.chains;
|
||||
|
||||
// make sure the object has its own guid.
|
||||
if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
|
||||
|
@ -4088,7 +4085,7 @@ function addDependentKeys(desc, obj, keyName, meta) {
|
|||
// Increment the number of times depKey depends on keyName.
|
||||
keys[keyName] = (keys[keyName] || 0) + 1;
|
||||
// Watch the depKey
|
||||
watch(obj, depKey, meta);
|
||||
watch(obj, depKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4107,7 +4104,7 @@ function removeDependentKeys(desc, obj, keyName, meta) {
|
|||
// Increment the number of times depKey depends on keyName.
|
||||
keys[keyName] = (keys[keyName] || 0) - 1;
|
||||
// Watch the depKey
|
||||
unwatch(obj, depKey, meta);
|
||||
unwatch(obj, depKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4550,8 +4547,7 @@ Ember.computed = function(func) {
|
|||
@return {Object} the cached value
|
||||
*/
|
||||
Ember.cacheFor = function cacheFor(obj, key) {
|
||||
var meta = obj[META_KEY],
|
||||
cache = meta && meta.cache;
|
||||
var cache = metaFor(obj, false).cache;
|
||||
|
||||
if (cache && key in cache) {
|
||||
return cache[key];
|
||||
|
@ -6922,13 +6918,11 @@ var Mixin, REQUIRED, Alias,
|
|||
a_slice = [].slice,
|
||||
o_create = Ember.create,
|
||||
defineProperty = Ember.defineProperty,
|
||||
guidFor = Ember.guidFor,
|
||||
metaFor = Ember.meta,
|
||||
META_KEY = Ember.META_KEY;
|
||||
guidFor = Ember.guidFor;
|
||||
|
||||
|
||||
function mixinsMeta(obj) {
|
||||
var m = metaFor(obj, true), ret = m.mixins;
|
||||
var m = Ember.meta(obj, true), ret = m.mixins;
|
||||
if (!ret) {
|
||||
ret = m.mixins = {};
|
||||
} else if (!m.hasOwnProperty('mixins')) {
|
||||
|
@ -7111,7 +7105,7 @@ function mergeMixins(mixins, m, descs, values, base, keys) {
|
|||
if (props === CONTINUE) { continue; }
|
||||
|
||||
if (props) {
|
||||
meta = metaFor(base);
|
||||
meta = Ember.meta(base);
|
||||
if (base.willMergeMixin) { base.willMergeMixin(props); }
|
||||
concats = concatenatedMixinProperties('concatenatedProperties', props, values, base);
|
||||
mergings = concatenatedMixinProperties('mergedProperties', props, values, base);
|
||||
|
@ -7169,7 +7163,7 @@ function connectBindings(obj, m) {
|
|||
}
|
||||
|
||||
function finishPartial(obj, m) {
|
||||
connectBindings(obj, m || metaFor(obj));
|
||||
connectBindings(obj, m || Ember.meta(obj));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -7216,7 +7210,7 @@ function replaceObserversAndListeners(obj, key, observerOrListener) {
|
|||
}
|
||||
|
||||
function applyMixin(obj, mixins, partial) {
|
||||
var descs = {}, values = {}, m = metaFor(obj),
|
||||
var descs = {}, values = {}, m = Ember.meta(obj),
|
||||
key, value, desc, keys = [];
|
||||
|
||||
// Go through all mixins and hashes passed in, and:
|
||||
|
@ -7426,8 +7420,7 @@ function _detect(curMixin, targetMixin, seen) {
|
|||
MixinPrototype.detect = function(obj) {
|
||||
if (!obj) { return false; }
|
||||
if (obj instanceof Mixin) { return _detect(obj, this, {}); }
|
||||
var m = obj[META_KEY],
|
||||
mixins = m && m.mixins;
|
||||
var mixins = Ember.meta(obj, false).mixins;
|
||||
if (mixins) {
|
||||
return !!mixins[guidFor(this)];
|
||||
}
|
||||
|
@ -7466,8 +7459,7 @@ MixinPrototype.keys = function() {
|
|||
// returns the mixins currently applied to the specified object
|
||||
// TODO: Make Ember.mixin
|
||||
Mixin.mixins = function(obj) {
|
||||
var m = obj[META_KEY],
|
||||
mixins = m && m.mixins, ret = [];
|
||||
var mixins = Ember.meta(obj, false).mixins, ret = [];
|
||||
|
||||
if (!mixins) { return ret; }
|
||||
|
||||
|
@ -9361,7 +9353,7 @@ define("rsvp/promise/all",
|
|||
```
|
||||
|
||||
@method all
|
||||
@for Ember.RSVP.Promise
|
||||
@for RSVP.Promise
|
||||
@param {Array} entries array of promises
|
||||
@param {String} label optional string for labeling the promise.
|
||||
Useful for tooling.
|
||||
|
@ -11994,7 +11986,6 @@ var set = Ember.set, get = Ember.get,
|
|||
guidFor = Ember.guidFor,
|
||||
generateGuid = Ember.generateGuid,
|
||||
meta = Ember.meta,
|
||||
META_KEY = Ember.META_KEY,
|
||||
rewatch = Ember.rewatch,
|
||||
finishChains = Ember.finishChains,
|
||||
sendEvent = Ember.sendEvent,
|
||||
|
@ -12682,8 +12673,7 @@ var ClassMixin = Mixin.create({
|
|||
@param key {String} property name
|
||||
*/
|
||||
metaForProperty: function(key) {
|
||||
var meta = this.proto()[META_KEY],
|
||||
desc = meta && meta.descs[key];
|
||||
var desc = meta(this.proto(), false).descs[key];
|
||||
|
||||
return desc._meta || {};
|
||||
},
|
||||
|
@ -25122,8 +25112,9 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, {
|
|||
set(this, 'controller', this);
|
||||
},
|
||||
|
||||
defaultLayout: function(context, options){
|
||||
Ember.Handlebars.helpers['yield'].call(context, options);
|
||||
defaultLayout: function(options){
|
||||
options.data = {view: options._context};
|
||||
Ember.Handlebars.helpers['yield'].apply(this, [options]);
|
||||
},
|
||||
|
||||
// during render, isolate keywords
|
||||
|
@ -26212,6 +26203,33 @@ var handlebarsGet = Ember.Handlebars.get = function(root, path, options) {
|
|||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
This method uses `Ember.Handlebars.get` to lookup a value, then ensures
|
||||
that the value is escaped properly.
|
||||
|
||||
If `unescaped` is a truthy value then the escaping will not be performed.
|
||||
|
||||
@method getEscaped
|
||||
@for Ember.Handlebars
|
||||
@param {Object} root The object to look up the property on
|
||||
@param {String} path The path to be lookedup
|
||||
@param {Object} options The template's option hash
|
||||
*/
|
||||
Ember.Handlebars.getEscaped = function(root, path, options) {
|
||||
var result = handlebarsGet(root, path, options);
|
||||
|
||||
if (result === null || result === undefined) {
|
||||
result = "";
|
||||
} else if (!(result instanceof Handlebars.SafeString)) {
|
||||
result = String(result);
|
||||
}
|
||||
if (!options.hash.unescaped){
|
||||
result = Handlebars.Utils.escapeExpression(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Ember.Handlebars.resolveParams = function(context, params, options) {
|
||||
var resolvedParams = [], types = options.types, param, type;
|
||||
|
||||
|
@ -27162,6 +27180,7 @@ Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
|
|||
|
||||
var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
|
||||
var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
|
||||
var handlebarsGetEscaped = Ember.Handlebars.getEscaped;
|
||||
var forEach = Ember.ArrayPolyfills.forEach;
|
||||
var o_create = Ember.create;
|
||||
|
||||
|
@ -27171,20 +27190,6 @@ function exists(value) {
|
|||
return !Ember.isNone(value);
|
||||
}
|
||||
|
||||
function sanitizedHandlebarsGet(currentContext, property, options) {
|
||||
var result = handlebarsGet(currentContext, property, options);
|
||||
if (result === null || result === undefined) {
|
||||
result = "";
|
||||
} else if (!(result instanceof Handlebars.SafeString)) {
|
||||
result = String(result);
|
||||
}
|
||||
if (!options.hash.unescaped){
|
||||
result = Handlebars.Utils.escapeExpression(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Binds a property into the DOM. This will create a hook in DOM that the
|
||||
// KVO system will look for and update if the property changes.
|
||||
function bind(property, options, preserveContext, shouldDisplay, valueNormalizer, childProperties) {
|
||||
|
@ -27255,7 +27260,7 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer
|
|||
} else {
|
||||
// The object is not observable, so just render it out and
|
||||
// be done with it.
|
||||
data.buffer.push(handlebarsGet(currentContext, property, options));
|
||||
data.buffer.push(handlebarsGetEscaped(currentContext, property, options));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27276,7 +27281,7 @@ function simpleBind(currentContext, property, options) {
|
|||
Ember.run.once(view, 'rerender');
|
||||
};
|
||||
|
||||
output = sanitizedHandlebarsGet(currentContext, property, options);
|
||||
output = handlebarsGetEscaped(currentContext, property, options);
|
||||
|
||||
data.buffer.push(output);
|
||||
} else {
|
||||
|
@ -27302,8 +27307,7 @@ function simpleBind(currentContext, property, options) {
|
|||
} else {
|
||||
// The object is not observable, so just render it out and
|
||||
// be done with it.
|
||||
output = sanitizedHandlebarsGet(currentContext, property, options);
|
||||
|
||||
output = handlebarsGetEscaped(currentContext, property, options);
|
||||
data.buffer.push(output);
|
||||
}
|
||||
}
|
||||
|
@ -35799,7 +35803,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
|
|||
if (linkType === 'ID') {
|
||||
options.linkTextPath = linkTitle;
|
||||
options.fn = function() {
|
||||
return Ember.Handlebars.get(context, linkTitle, options);
|
||||
return Ember.Handlebars.getEscaped(context, linkTitle, options);
|
||||
};
|
||||
} else {
|
||||
options.fn = function() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user