diff --git a/app/assets/javascripts/admin/templates/email_logs.js.handlebars b/app/assets/javascripts/admin/templates/email_logs.js.handlebars
index b61e4f6fc88..f937ee6bc53 100644
--- a/app/assets/javascripts/admin/templates/email_logs.js.handlebars
+++ b/app/assets/javascripts/admin/templates/email_logs.js.handlebars
@@ -10,22 +10,22 @@
{{#if model.length}}
- {{#group}}
- {{#collection contentBinding="model" tagName="tbody" itemTagName="tr"}}
-
{{date created_at}} |
-
- {{#if user}}
- {{#linkTo 'adminUser' user}}{{avatar user imageSize="tiny"}}{{/linkTo}}
- {{#linkTo 'adminUser' user}}{{user.username}}{{/linkTo}}
- {{else}}
- —
- {{/if}}
- |
- {{to_address}} |
- {{email_type}} |
- {{reply_key}} |
- {{/collection}}
- {{/group}}
+ {{#groupedEach model}}
+
+ {{date created_at}} |
+
+ {{#if user}}
+ {{#linkTo 'adminUser' user}}{{avatar user imageSize="tiny"}}{{/linkTo}}
+ {{#linkTo 'adminUser' user}}{{user.username}}{{/linkTo}}
+ {{else}}
+ —
+ {{/if}}
+ |
+ {{to_address}} |
+ {{email_type}} |
+ {{reply_key}} |
+
+ {{/groupedEach}}
{{/if}}
diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb
index dc1dadfea83..35acdbfeefb 100644
--- a/app/assets/javascripts/application.js.erb
+++ b/app/assets/javascripts/application.js.erb
@@ -23,10 +23,8 @@ require_asset ("./external/handlebars.js")
if Rails.env.development?
require_asset ("./external_development/ember.js")
- require_asset ("./external_development/group-helper.js")
else
require_asset ("./external_production/ember.js")
- require_asset ("./external_production/group-helper.js")
end
require_asset ("./main_include.js")
diff --git a/app/assets/javascripts/discourse/helpers/grouped_each.js b/app/assets/javascripts/discourse/helpers/grouped_each.js
new file mode 100644
index 00000000000..d59409117e4
--- /dev/null
+++ b/app/assets/javascripts/discourse/helpers/grouped_each.js
@@ -0,0 +1,111 @@
+var DiscourseGroupedEach = function(context, path, options) {
+ var self = this,
+ normalized = Ember.Handlebars.normalizePath(context, path, options.data);
+
+ this.context = context;
+ this.path = path;
+ this.options = options;
+ this.template = options.fn;
+ this.containingView = options.data.view;
+ this.normalizedRoot = normalized.root;
+ this.normalizedPath = normalized.path;
+ this.content = this.lookupContent();
+
+ this.addContentObservers();
+ this.addArrayObservers();
+
+ this.containingView.on('willClearRender', function() {
+ self.destroy();
+ });
+};
+
+DiscourseGroupedEach.prototype = {
+ contentWillChange: function() {
+ this.removeArrayObservers();
+ },
+
+ contentDidChange: function() {
+ this.content = this.lookupContent();
+ this.addArrayObservers();
+ this.rerenderContainingView();
+ },
+
+ contentArrayWillChange: Ember.K,
+
+ contentArrayDidChange: function() {
+ this.rerenderContainingView();
+ },
+
+ lookupContent: function() {
+ return Ember.Handlebars.get(this.normalizedRoot, this.normalizedPath, this.options);
+ },
+
+ addArrayObservers: function() {
+ this.content.addArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ },
+
+ removeArrayObservers: function() {
+ this.content.removeArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ },
+
+ addContentObservers: function() {
+ Ember.addBeforeObserver(this.normalizedRoot, this.normalizedPath, this, this.contentWillChange);
+ Ember.addObserver(this.normalizedRoot, this.normalizedPath, this, this.contentDidChange);
+ },
+
+ removeContentObservers: function() {
+ Ember.removeBeforeObserver(this.normalizedRoot, this.normalizedPath, this.contentWillChange);
+ Ember.removeObserver(this.normalizedRoot, this.normalizedPath, this.contentDidChange);
+ },
+
+ render: function() {
+ var content = this.content,
+ contentLength = Em.get(content, 'length'),
+ data = this.options.data,
+ template = this.template;
+
+ data.insideEach = true;
+ data.insideGroup = true;
+ for (var i = 0; i < contentLength; i++) {
+ template(content.objectAt(i), { data: data });
+ }
+ },
+
+ rerenderContainingView: function() {
+ Ember.run.scheduleOnce('render', this.containingView, 'rerender');
+ },
+
+ destroy: function() {
+ this.removeContentObservers();
+ this.removeArrayObservers();
+ }
+};
+
+
+Ember.Handlebars.registerHelper('groupedEach', function(path, options) {
+ if (arguments.length === 4) {
+ Ember.assert("If you pass more than one argument to the groupedEach helper, it must be in the form #groupedEach foo in bar", arguments[1] === "in");
+
+ var keywordName = arguments[0];
+
+ options = arguments[3];
+ path = arguments[2];
+ if (path === '') { path = "this"; }
+
+ options.hash.keyword = keywordName;
+ }
+
+ if (arguments.length === 1) {
+ options = path;
+ path = 'this';
+ }
+
+ options.hash.dataSourceBinding = path;
+ new DiscourseGroupedEach(this, path, options).render();
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/templates/list/basic_topic_list.js.handlebars b/app/assets/javascripts/discourse/templates/list/basic_topic_list.js.handlebars
index 60b0846a800..9672cffa5a8 100644
--- a/app/assets/javascripts/discourse/templates/list/basic_topic_list.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/list/basic_topic_list.js.handlebars
@@ -10,8 +10,8 @@
{{i18n activity}} |
- {{#group}}
- {{#collection contentBinding="view.topics" tagName="tbody" itemTagName="tr"}}
+ {{#groupedEach view.topics}}
+
{{{unbound fancy_title}}}
{{#if unread}}
@@ -21,35 +21,35 @@
{{unbound new_posts}}
{{/if}}
{{#if unseen}}
-
+
{{/if}}
|
{{categoryLink category}}
|
- {{number posts_count numberKey="posts_long"}} |
+ {{number posts_count numberKey="posts_long"}} |
{{#if like_count}}
- {{like_count}}
+ {{unbound like_count}}
{{/if}}
|
{{number views numberKey="views_long"}} |
{{#if bumped}}
- {{unboundAge created_at}}
+ {{unboundAge created_at}}
|
- {{unboundAge bumped_at}}
+ {{unboundAge bumped_at}}
|
{{else}}
- {{unboundAge created_at}}
+ {{unboundAge created_at}}
|
|
{{/if}}
- {{/collection}}
- {{/group}}
+
+ {{/groupedEach}}
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars b/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars
index 0925d81e044..ccf18fcb2ed 100644
--- a/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars
@@ -1,76 +1,76 @@
- {{#if controller.currentUser.id}}
-
-
- |
+{{#if controller.currentUser.id}}
+
+
+ |
+{{/if}}
+
+
+
+ {{#if controller.rankDetailsVisible}}
+
+ {{rank_details.hot_topic_type}}
+
+ ({{float rank_details.random_bias}} * {{float rank_details.random_multiplier}}) + ({{float rank_details.days_ago_bias}} * {{float rank_details.days_ago_multiplier}}) = {{float rank_details.ranking_score}}
+
+
+
{{/if}}
- |
-
- {{#if controller.rankDetailsVisible}}
-
- {{rank_details.hot_topic_type}}
-
- ({{float rank_details.random_bias}} * {{float rank_details.random_multiplier}}) + ({{float rank_details.days_ago_bias}} * {{float rank_details.days_ago_multiplier}}) = {{float rank_details.ranking_score}}
-
-
-
- {{/if}}
-
- {{topicStatus topic=this}}
- {{{topicLink this}}}
- {{#if unread}}
- {{unread}}
- {{/if}}
- {{#if displayNewPosts}}
- {{displayNewPosts}}
- {{/if}}
- {{#if unseen}}
-
- {{/if}}
-
- {{#if hasExcerpt}}
-
- {{/if}}
- |
-
-
- {{categoryLink category}}
- |
-
-
- {{#each posters}}
-
- {{/each}}
- |
-
- {{number posts_count numberKey="posts_long"}} |
-
-
- {{#if like_count}}
- {{number like_count numberKey="likes_long"}}
- {{/if}}
- |
-
- {{number views numberKey="views_long"}} |
-
- {{#if bumped}}
-
- {{unboundAge created_at}}
- |
-
- {{unboundAge bumped_at}}
- |
- {{else}}
-
- {{unboundAge created_at}}
- |
- |
+ {{topicStatus topic=this}}
+ {{{topicLink this}}}
+ {{#if unread}}
+ {{unread}}
{{/if}}
+ {{#if displayNewPosts}}
+ {{displayNewPosts}}
+ {{/if}}
+ {{#if unseen}}
+
+ {{/if}}
+
+ {{#if hasExcerpt}}
+
+ {{/if}}
+
+
+
+ {{categoryLink category}}
+ |
+
+
+ {{#each posters}}
+
+ {{/each}}
+ |
+
+{{number posts_count numberKey="posts_long"}} |
+
+
+ {{#if like_count}}
+ {{number like_count numberKey="likes_long"}}
+ {{/if}}
+ |
+
+{{number views numberKey="views_long"}} |
+
+{{#if bumped}}
+
+ {{unboundAge created_at}}
+ |
+
+ {{unboundAge bumped_at}}
+ |
+{{else}}
+
+ {{unboundAge created_at}}
+ |
+ |
+{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/list/topics.js.handlebars b/app/assets/javascripts/discourse/templates/list/topics.js.handlebars
index 23c6b850b6a..e9a25630774 100644
--- a/app/assets/javascripts/discourse/templates/list/topics.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/list/topics.js.handlebars
@@ -41,9 +41,7 @@
{{/if}}
- {{#group}}
- {{collection contentBinding="topics" tagName="tbody" itemViewClass="Discourse.TopicListItemView"}}
- {{/group}}
+ {{collection contentBinding="topics" tagName="tbody" itemViewClass="Discourse.TopicListItemView"}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/post.js.handlebars b/app/assets/javascripts/discourse/templates/post.js.handlebars
index cd719a2f3d9..3c3cbab395a 100644
--- a/app/assets/javascripts/discourse/templates/post.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/post.js.handlebars
@@ -47,7 +47,7 @@
{{/unless}}
- {{view Discourse.RawDivView class="cooked" contentBinding="cooked"}}
+ {{{cooked}}}
{{view Discourse.PostMenuView postBinding="this" postViewBinding="view"}}
{{view Discourse.RepliesView contentBinding="replies" postViewBinding="view"}}
diff --git a/app/assets/javascripts/discourse/views/embedded_post_view.js b/app/assets/javascripts/discourse/views/embedded_post_view.js
index aff7e9e2cb4..2a9c0082d04 100644
--- a/app/assets/javascripts/discourse/views/embedded_post_view.js
+++ b/app/assets/javascripts/discourse/views/embedded_post_view.js
@@ -6,15 +6,10 @@
@namespace Discourse
@module Discourse
**/
-Discourse.EmbeddedPostView = Discourse.View.extend({
+Discourse.EmbeddedPostView = Discourse.GroupedView.extend({
templateName: 'embedded_post',
classNames: ['reply'],
- init: function() {
- this._super();
- this.set('context', this.get('content'));
- },
-
didInsertElement: function() {
Discourse.ScreenTrack.instance().track(this.get('elementId'), this.get('post.post_number'));
},
diff --git a/app/assets/javascripts/discourse/views/list/topic_list_item_view.js b/app/assets/javascripts/discourse/views/list/topic_list_item_view.js
index 5a69bd02281..2d1f6d71456 100644
--- a/app/assets/javascripts/discourse/views/list/topic_list_item_view.js
+++ b/app/assets/javascripts/discourse/views/list/topic_list_item_view.js
@@ -2,11 +2,11 @@
This view handles the rendering of a topic in a list
@class TopicListItemView
- @extends Discourse.View
+ @extends Discourse.GroupedView
@namespace Discourse
@module Discourse
**/
-Discourse.TopicListItemView = Discourse.View.extend({
+Discourse.TopicListItemView = Discourse.GroupedView.extend({
tagName: 'tr',
templateName: 'list/topic_list_item',
classNameBindings: ['content.archived', ':topic-list-item', 'content.hasExcerpt:has-excerpt'],
@@ -14,11 +14,6 @@ Discourse.TopicListItemView = Discourse.View.extend({
'data-topic-id': Em.computed.alias('content.id'),
- init: function() {
- this._super();
- this.set('context', this.get('content'));
- },
-
highlight: function() {
var $topic = this.$();
var originalCol = $topic.css('backgroundColor');
diff --git a/app/assets/javascripts/discourse/views/modal/option_boolean_view.js b/app/assets/javascripts/discourse/views/modal/option_boolean_view.js
index 51b5b8e6eb7..5536a204f4e 100644
--- a/app/assets/javascripts/discourse/views/modal/option_boolean_view.js
+++ b/app/assets/javascripts/discourse/views/modal/option_boolean_view.js
@@ -6,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
-Discourse.OptionBooleanView = Discourse.View.extend({
+Discourse.OptionBooleanView = Discourse.GroupedView.extend({
classNames: ['archetype-option'],
composerControllerBinding: 'Discourse.router.composerController',
templateName: "modal/option_boolean",
@@ -16,12 +16,7 @@ Discourse.OptionBooleanView = Discourse.View.extend({
metaData = this.get('parentView.metaData');
metaData.set(this.get('content.key'), this.get('checked') ? 'true' : 'false');
return this.get('controller.controllers.composer').saveDraft();
- }).observes('checked'),
-
- init: function() {
- this._super();
- return this.set('context', this.get('content'));
- }
+ }).observes('checked')
});
diff --git a/app/assets/javascripts/discourse/views/post_view.js b/app/assets/javascripts/discourse/views/post_view.js
index 961d5fad116..f0dc293dc52 100644
--- a/app/assets/javascripts/discourse/views/post_view.js
+++ b/app/assets/javascripts/discourse/views/post_view.js
@@ -2,11 +2,11 @@
This view renders a post.
@class PostView
- @extends Discourse.View
+ @extends Discourse.GroupedView
@namespace Discourse
@module Discourse
**/
-Discourse.PostView = Discourse.View.extend({
+Discourse.PostView = Discourse.GroupedView.extend({
classNames: ['topic-post', 'clearfix'],
templateName: 'post',
classNameBindings: ['postTypeClass',
@@ -28,18 +28,12 @@ Discourse.PostView = Discourse.View.extend({
});
}.observes('post.cooked'),
- init: function() {
- this._super();
- this.set('context', this.get('content'));
- },
-
mouseUp: function(e) {
if (this.get('controller.multiSelect') && (e.metaKey || e.ctrlKey)) {
this.get('controller').selectPost(this.get('post'));
}
},
-
selected: function() {
var selectedPosts = this.get('controller.selectedPosts');
if (!selectedPosts) return false;
diff --git a/app/assets/javascripts/discourse/views/raw_div_view.js b/app/assets/javascripts/discourse/views/raw_div_view.js
deleted file mode 100644
index 28ef39e8ff7..00000000000
--- a/app/assets/javascripts/discourse/views/raw_div_view.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// used to render a div with unescaped contents
-
-Discourse.RawDivView = Ember.View.extend({
-
- shouldRerender: Discourse.View.renderIfChanged('content'),
-
- render: function(buffer) {
- buffer.push(this.get('content'));
- }
-
-});
diff --git a/app/assets/javascripts/discourse/views/search/search_results_type_view.js b/app/assets/javascripts/discourse/views/search/search_results_type_view.js
index 2a5248a142f..2c63dd328d4 100644
--- a/app/assets/javascripts/discourse/views/search/search_results_type_view.js
+++ b/app/assets/javascripts/discourse/views/search/search_results_type_view.js
@@ -8,17 +8,11 @@
**/
Discourse.SearchResultsTypeView = Ember.CollectionView.extend({
tagName: 'ul',
- itemViewClass: Ember.View.extend({
+ itemViewClass: Discourse.GroupedView.extend({
tagName: 'li',
classNameBindings: ['selected'],
templateName: Discourse.computed.fmt('parentView.type', "search/%@_result"),
- selected: Discourse.computed.propertyEqual('content.index', 'controller.selectedIndex'),
-
- init: function() {
- this._super();
- this.set('context', this.get('content'));
- }
-
+ selected: Discourse.computed.propertyEqual('content.index', 'controller.selectedIndex')
})
});
diff --git a/app/assets/javascripts/discourse/views/user/user_stream_view.js b/app/assets/javascripts/discourse/views/user/user_stream_view.js
index 60de6246b4f..c04271f1fe7 100644
--- a/app/assets/javascripts/discourse/views/user/user_stream_view.js
+++ b/app/assets/javascripts/discourse/views/user/user_stream_view.js
@@ -13,13 +13,7 @@ Discourse.UserStreamView = Ember.CollectionView.extend(Discourse.LoadMore, {
eyelineSelector: '#user-activity .user-stream .item',
classNames: ['user-stream'],
- itemViewClass: Ember.View.extend({
- templateName: 'user/stream_item',
- init: function() {
- this._super();
- this.set('context', this.get('content'));
- }
- }),
+ itemViewClass: Discourse.GroupedView.extend({ templateName: 'user/stream_item' }),
loadMore: function() {
var userStreamView = this;
diff --git a/app/assets/javascripts/discourse/views/view.js b/app/assets/javascripts/discourse/views/view.js
index 0be210a50a5..44f8ba5f14a 100644
--- a/app/assets/javascripts/discourse/views/view.js
+++ b/app/assets/javascripts/discourse/views/view.js
@@ -9,6 +9,20 @@
**/
Discourse.View = Ember.View.extend(Discourse.Presence, {});
+Discourse.GroupedView = Ember.View.extend(Discourse.Presence, {
+ init: function() {
+ this._super();
+ this.set('context', this.get('content'));
+
+ var templateData = this.get('templateData');
+ if (templateData) {
+ this.set('templateData.insideGroup', true);
+ }
+ }
+});
+
+
+
Discourse.View.reopenClass({
/**
diff --git a/app/assets/javascripts/external_development/ember.js b/app/assets/javascripts/external_development/ember.js
index eb28f82d2cc..3acfb3ef40d 100755
--- a/app/assets/javascripts/external_development/ember.js
+++ b/app/assets/javascripts/external_development/ember.js
@@ -1999,7 +1999,7 @@ function suspendListener(obj, eventName, target, method, callback) {
Suspends multiple listeners during a callback.
-
+
@method suspendListeners
@for Ember
@param obj
@@ -2066,7 +2066,7 @@ function watchedEvents(obj) {
is skipped, and once listeners are removed. A listener without
a target is executed on the passed object. If an array of actions
is not passed, the actions stored on the passed object are invoked.
-
+
@method sendEvent
@for Ember
@param obj
@@ -2794,14 +2794,14 @@ Map.create = function() {
Map.prototype = {
/**
This property will change as the number of objects in the map changes.
-
+
@property length
@type number
@default 0
*/
length: 0,
-
-
+
+
/**
Retrieve the value associated with a given key.
@@ -4395,7 +4395,7 @@ Ember.computed.alias = function(dependentKey) {
@return {Ember.ComputedProperty} computed property which creates an
one way computed property to the original value for property.
- Where `computed.alias` aliases `get` and `set`, and allows for bidirectional
+ Where `computed.alias` aliases `get` and `set`, and allows for bidirectional
data flow, `computed.oneWay` only provides an aliased `get`. The `set` will
not mutate the upstream property, rather causes the current property to
become the value set. This causes the downstream property to permentantly
@@ -23878,7 +23878,7 @@ helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {};
var buffer = '', stack1, hashTypes, hashContexts, escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) {
-
+
var buffer = '', hashTypes, hashContexts;
data.buffer.push("