diff --git a/app/assets/javascripts/admin/components/ace-editor.js.es6 b/app/assets/javascripts/admin/components/ace-editor.js.es6 index f9be6416406..561ebc6d8ab 100644 --- a/app/assets/javascripts/admin/components/ace-editor.js.es6 +++ b/app/assets/javascripts/admin/components/ace-editor.js.es6 @@ -1,8 +1,9 @@ /* global ace:true */ import loadScript from 'discourse/lib/load-script'; import { escapeExpression } from 'discourse/lib/utilities'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; -export default Ember.Component.extend({ +export default Ember.Component.extend(bufferedRender({ mode: 'css', classNames: ['ace-wrapper'], _editor: null, @@ -14,7 +15,7 @@ export default Ember.Component.extend({ } }.observes('content'), - render(buffer) { + buildBuffer(buffer) { buffer.push("
"); if (this.get('content')) { buffer.push(escapeExpression(this.get('content'))); @@ -66,4 +67,4 @@ export default Ember.Component.extend({ }); }.on('didInsertElement') -}); +})); diff --git a/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 b/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 index cb9e39eb603..b36969651d8 100644 --- a/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 +++ b/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 @@ -1,8 +1,9 @@ import debounce from 'discourse/lib/debounce'; import { renderSpinner } from 'discourse/helpers/loading-spinner'; import { escapeExpression } from 'discourse/lib/utilities'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; -export default Ember.Component.extend({ +export default Ember.Component.extend(bufferedRender({ classNames: ["admin-backups-logs"], init() { @@ -29,11 +30,11 @@ export default Ember.Component.extend({ // update the formatted logs & cache index this.setProperties({ formattedLogs: formattedLogs, index: logs.length }); // force rerender - this.rerender(); + this.rerenderBuffer(); } }, 150).observes("logs.[]").on('init'), - render(buffer) { + buildBuffer(buffer) { const formattedLogs = this.get("formattedLogs"); if (formattedLogs && formattedLogs.length > 0) { buffer.push("
");
@@ -53,4 +54,4 @@ export default Ember.Component.extend({
     const $div = this.$()[0];
     $div.scrollTop = $div.scrollHeight;
   },
-});
+}));
diff --git a/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 b/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6
index c8438a65b7f..d176b5a40ab 100644
--- a/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6
+++ b/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6
@@ -1,8 +1,8 @@
 import computed from 'ember-addons/ember-computed-decorators';
-import StringBuffer from 'discourse/mixins/string-buffer';
 import { iconHTML } from 'discourse-common/helpers/fa-icon';
+import { bufferedRender } from 'discourse-common/lib/buffered-render';
 
-export default Ember.Component.extend(StringBuffer, {
+export default Ember.Component.extend(bufferedRender({
   classes: ["text-muted", "text-danger", "text-successful"],
   icons: ["circle-o", "times-circle", "circle"],
 
@@ -21,8 +21,8 @@ export default Ember.Component.extend(StringBuffer, {
     return classes[statusId - 1];
   },
 
-  renderString(buffer) {
+  buildBuffer(buffer) {
     buffer.push(iconHTML(this.get('icon'), { class: this.get('class') }));
     buffer.push(I18n.t(`admin.web_hooks.delivery_status.${this.get('status.name')}`));
   }
-});
+}));
diff --git a/app/assets/javascripts/admin/components/resumable-upload.js.es6 b/app/assets/javascripts/admin/components/resumable-upload.js.es6
index ea2f02cfa05..73a46eded3c 100644
--- a/app/assets/javascripts/admin/components/resumable-upload.js.es6
+++ b/app/assets/javascripts/admin/components/resumable-upload.js.es6
@@ -1,3 +1,5 @@
+import { bufferedRender } from 'discourse-common/lib/buffered-render';
+
 /*global Resumable:true */
 
 /**
@@ -10,7 +12,7 @@
         uploadText="UPLOAD"
     }}
 **/
-const ResumableUploadComponent = Ember.Component.extend(Discourse.StringBuffer, {
+export default Ember.Component.extend(bufferedRender({
   tagName: "button",
   classNames: ["btn", "ru"],
   classNameBindings: ["isUploading"],
@@ -36,9 +38,9 @@ const ResumableUploadComponent = Ember.Component.extend(Discourse.StringBuffer,
     }
   }.property("isUploading", "progress"),
 
-  renderString: function(buffer) {
-    var icon = this.get("isUploading") ? "times" : "upload";
-    buffer.push("");
+  buildBuffer(buffer) {
+    const icon = this.get("isUploading") ? "times" : "upload";
+    buffer.push(``);
     buffer.push("" + this.get("text") + "");
     buffer.push("");
   },
@@ -117,6 +119,4 @@ const ResumableUploadComponent = Ember.Component.extend(Discourse.StringBuffer,
     }
   }.on("willDestroyElement")
 
-});
-
-export default ResumableUploadComponent;
+}));
diff --git a/app/assets/javascripts/discourse-common/components/combo-box.js.es6 b/app/assets/javascripts/discourse-common/components/combo-box.js.es6
index ce222e21145..2325d8b14be 100644
--- a/app/assets/javascripts/discourse-common/components/combo-box.js.es6
+++ b/app/assets/javascripts/discourse-common/components/combo-box.js.es6
@@ -1,13 +1,14 @@
+import { bufferedRender } from 'discourse-common/lib/buffered-render';
 import { on, observes } from 'ember-addons/ember-computed-decorators';
 
-export default Ember.Component.extend({
+export default Ember.Component.extend(bufferedRender({
   tagName: 'select',
   attributeBindings: ['tabindex', 'disabled'],
   classNames: ['combobox'],
   valueAttribute: 'id',
   nameProperty: 'name',
 
-  render(buffer) {
+  buildBuffer(buffer) {
     const nameProperty = this.get('nameProperty');
     const none = this.get('none');
 
@@ -48,11 +49,11 @@ export default Ember.Component.extend({
 
   @observes('content.[]')
   _rerenderOnChange() {
-    this.rerender();
+    this.rerenderBuffer();
   },
 
-  @on('didInsertElement')
-  _initializeCombo() {
+  didInsertElement() {
+    this._super();
 
     // Workaround for https://github.com/emberjs/ember.js/issues/9813
     // Can be removed when fixed. Without it, the wrong option is selected
@@ -60,7 +61,7 @@ export default Ember.Component.extend({
 
     // observer for item names changing (optional)
     if (this.get('nameChanges')) {
-      this.addObserver('content.@each.' + this.get('nameProperty'), this.rerender);
+      this.addObserver('content.@each.' + this.get('nameProperty'), this.rerenderBuffer);
     }
 
     const $elem = this.$();
@@ -88,4 +89,4 @@ export default Ember.Component.extend({
     this.$().select2('destroy');
   }
 
-});
+}));
diff --git a/app/assets/javascripts/discourse-common/lib/buffered-render.js.es6 b/app/assets/javascripts/discourse-common/lib/buffered-render.js.es6
new file mode 100644
index 00000000000..4873bfc7786
--- /dev/null
+++ b/app/assets/javascripts/discourse-common/lib/buffered-render.js.es6
@@ -0,0 +1,53 @@
+// Ember 2.0 removes buffered rendering, but we can still implement it ourselves.
+// In the long term we'll want to remove this.
+
+const Mixin = {
+  __bufferTimeout: null,
+
+  _customRender() {
+    Ember.run.cancel(this.__bufferTimeout);
+    if (!this.element || this.isDestroying || this.isDestroyed) { return; }
+
+    const buffer = [];
+    this.buildBuffer(buffer);
+    this.element.innerHTML = buffer.join('');
+  },
+
+  rerenderBuffer() {
+    Ember.run.scheduleOnce('render', this, this._customRender);
+  }
+};
+
+export function bufferedRender(obj) {
+
+  if (!obj.buildBuffer) {
+    Ember.warn('Missing `buildBuffer` method');
+    return obj;
+  }
+
+  const caller = {};
+
+  // True in 1.13 or greater
+  if (Ember.Helper) {
+    caller.didRender = function() {
+      this._super();
+      this._customRender();
+    };
+  } else {
+    caller.didInsertElement = function() {
+      this._super();
+      this._customRender();
+    };
+  }
+
+  const triggers = obj.rerenderTriggers;
+  if (triggers) {
+    caller.init = function() {
+      this._super();
+      triggers.forEach(k => this.addObserver(k, this.rerenderBuffer));
+    };
+  }
+  delete obj.rerenderTriggers;
+
+  return Ember.Mixin.create(Mixin, caller, obj);
+}
diff --git a/app/assets/javascripts/discourse/components/activity-filter.js.es6 b/app/assets/javascripts/discourse/components/activity-filter.js.es6
deleted file mode 100644
index c0cf5b4e4eb..00000000000
--- a/app/assets/javascripts/discourse/components/activity-filter.js.es6
+++ /dev/null
@@ -1,66 +0,0 @@
-import StringBuffer from 'discourse/mixins/string-buffer';
-import UserAction from "discourse/models/user-action";
-
-export default Ember.Component.extend(StringBuffer, {
-  tagName: 'li',
-  classNameBindings: ['active', 'noGlyph'],
-
-  rerenderTriggers: ['content.count', 'count'],
-  noGlyph: Em.computed.empty('icon'),
-
-  isIndexStream: function() {
-    return !this.get('content');
-  }.property('content.count'),
-
-  active: function() {
-    if (this.get('isIndexStream')) {
-      return !this.get('userActionType');
-    }
-    const content = this.get('content');
-    if (content) {
-      return parseInt(this.get('userActionType'), 10) === parseInt(Em.get(content, 'action_type'), 10);
-    }
-  }.property('userActionType', 'isIndexStream'),
-
-  activityCount: function() {
-    return this.get('content.count') || this.get('count') || 0;
-  }.property('content.count', 'count'),
-
-  typeKey: function() {
-    const actionType = this.get('content.action_type');
-    if (actionType === UserAction.TYPES.messages_received) { return ""; }
-
-    const result = UserAction.TYPES_INVERTED[actionType];
-    if (!result) { return ""; }
-
-    // We like our URLS to have hyphens, not underscores
-    return "/" + result.replace("_", "-");
-  }.property('content.action_type'),
-
-  url: function() {
-    return "/users/" + this.get('user.username_lower') + "/activity" + this.get('typeKey');
-  }.property('typeKey', 'user.username_lower'),
-
-  description: function() {
-    return this.get('content.description') || I18n.t("user.filters.all");
-  }.property('content.description'),
-
-  renderString(buffer) {
-    buffer.push("");
-    const icon = this.get('icon');
-    if (icon) {
-      buffer.push(" ");
-    }
-    buffer.push(this.get('description') + " (" + this.get('activityCount') + ")");
-  },
-
-  icon: function() {
-    switch(parseInt(this.get('content.action_type'), 10)) {
-      case UserAction.TYPES.likes_received: return "heart";
-      case UserAction.TYPES.bookmarks: return "bookmark";
-      case UserAction.TYPES.edits: return "pencil";
-      case UserAction.TYPES.replies: return "reply";
-      case UserAction.TYPES.mentions: return "at";
-    }
-  }.property("content.action_type")
-});
diff --git a/app/assets/javascripts/discourse/components/basic-topic-list.js.es6 b/app/assets/javascripts/discourse/components/basic-topic-list.js.es6
index 75c4d370c0b..6b08494f4f4 100644
--- a/app/assets/javascripts/discourse/components/basic-topic-list.js.es6
+++ b/app/assets/javascripts/discourse/components/basic-topic-list.js.es6
@@ -52,7 +52,5 @@ export default Ember.Component.extend({
       }
       return false;
     }
-
   }
-
 });
diff --git a/app/assets/javascripts/discourse/components/bread-crumbs.js.es6 b/app/assets/javascripts/discourse/components/bread-crumbs.js.es6
index 6841e8e4569..28a06d70004 100644
--- a/app/assets/javascripts/discourse/components/bread-crumbs.js.es6
+++ b/app/assets/javascripts/discourse/components/bread-crumbs.js.es6
@@ -35,9 +35,4 @@ export default Ember.Component.extend({
     });
   }.property('firstCategory', 'hideSubcategories'),
 
-  render(buffer) {
-    if (this.get('hidden')) { return; }
-    this._super(buffer);
-  }
-
 });
diff --git a/app/assets/javascripts/discourse/components/check-mark.js.es6 b/app/assets/javascripts/discourse/components/check-mark.js.es6
index a934a490a45..34585e1a0fa 100644
--- a/app/assets/javascripts/discourse/components/check-mark.js.es6
+++ b/app/assets/javascripts/discourse/components/check-mark.js.es6
@@ -7,10 +7,5 @@ export default Ember.Component.extend({
   @computed('checked')
   status(checked) {
     return checked ? 'status-checked' : 'status-unchecked';
-  },
-
-  render(buffer) {
-    const icon = this.get('checked') ? 'check' : 'times';
-    buffer.push(``);
   }
 });
diff --git a/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6 b/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6
index 49e2eefca14..c248ea5b959 100644
--- a/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6
+++ b/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6
@@ -1,19 +1,10 @@
+import computed from 'ember-addons/ember-computed-decorators';
+
 export default Ember.Component.extend({
   classNameBindings: ['containerClass', 'condition:visible'],
 
-  containerClass: function() {
-    return this.get('size') === 'small' ? 'inline-spinner' : undefined;
-  }.property('size'),
-
-  render: function(buffer) {
-    if (this.get('condition')) {
-      buffer.push('
'); - } else { - return this._super(buffer); - } - }, - - _conditionChanged: function() { - this.rerender(); - }.observes('condition') + @computed('size') + containerClass(size) { + return size === 'small' ? 'inline-spinner' : undefined; + } }); diff --git a/app/assets/javascripts/discourse/components/count-i18n.js.es6 b/app/assets/javascripts/discourse/components/count-i18n.js.es6 index 28c908be236..0197b6bb760 100644 --- a/app/assets/javascripts/discourse/components/count-i18n.js.es6 +++ b/app/assets/javascripts/discourse/components/count-i18n.js.es6 @@ -1,8 +1,10 @@ -export default Ember.Component.extend(Discourse.StringBuffer, { +import { bufferedRender } from 'discourse-common/lib/buffered-render'; + +export default Ember.Component.extend(bufferedRender({ tagName: 'span', rerenderTriggers: ['count', 'suffix'], - renderString: function(buffer) { + buildBuffer(buffer) { buffer.push(I18n.t(this.get('key') + (this.get('suffix') || ''), { count: this.get('count') })); } -}); +})); diff --git a/app/assets/javascripts/discourse/components/d-button.js.es6 b/app/assets/javascripts/discourse/components/d-button.js.es6 index 66e393ba712..c2d7bf1f310 100644 --- a/app/assets/javascripts/discourse/components/d-button.js.es6 +++ b/app/assets/javascripts/discourse/components/d-button.js.es6 @@ -1,5 +1,4 @@ -import { iconHTML } from 'discourse-common/helpers/fa-icon'; -import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; +import { default as computed } from 'ember-addons/ember-computed-decorators'; export default Ember.Component.extend({ tagName: 'button', @@ -18,24 +17,6 @@ export default Ember.Component.extend({ if (label) return I18n.t(label); }, - @observes('icon') - iconChanged() { - this.rerender(); - }, - - render(buffer) { - const label = this.get('translatedLabel'), - icon = this.get('icon'); - - if (label || icon) { - if (icon) { buffer.push(iconHTML(icon) + ' '); } - if (label) { buffer.push(label); } - } else { - // If no label or icon is present, yield - return this._super(buffer); - } - }, - click() { this.sendAction("action", this.get("actionParam")); return false; diff --git a/app/assets/javascripts/discourse/components/d-link.js.es6 b/app/assets/javascripts/discourse/components/d-link.js.es6 deleted file mode 100644 index 1c94bdc8231..00000000000 --- a/app/assets/javascripts/discourse/components/d-link.js.es6 +++ /dev/null @@ -1,69 +0,0 @@ -import computed from 'ember-addons/ember-computed-decorators'; -import { iconHTML } from 'discourse-common/helpers/fa-icon'; -import interceptClick from 'discourse/lib/intercept-click'; - -export default Ember.Component.extend({ - tagName: 'a', - classNames: ['d-link'], - attributeBindings: ['translatedTitle:title', 'translatedTitle:aria-title', 'href'], - - @computed('path') - href(path) { - if (path) { return path; } - - const route = this.get('route'); - if (route) { - const router = this.container.lookup('router:main'); - if (router && router.router) { - const params = [route]; - const model = this.get('model'); - if (model) { - params.push(model); - } - - return Discourse.getURL(router.router.generate.apply(router.router, params)); - } - } - - return ''; - }, - - @computed("title") - translatedTitle(title) { - if (title) return I18n.t(title); - }, - - click(e) { - const action = this.get('action'); - if (action) { - this.sendAction('action'); - return false; - } - - return interceptClick(e); - }, - - render(buffer) { - if (!!this.get('template')) { - return this._super(buffer); - } - - const icon = this.get('icon'); - if (icon) { - buffer.push(iconHTML(icon)); - } - - const label = this.get('label'); - if (label) { - if (icon) { buffer.push(" "); } - - if (this.get('translateLabel') === "false") { - buffer.push(label); - } else { - const count = this.get('count'); - buffer.push(I18n.t(label, { count })); - } - } - } - -}); diff --git a/app/assets/javascripts/discourse/components/directory-toggle.js.es6 b/app/assets/javascripts/discourse/components/directory-toggle.js.es6 index 168db163c58..3f55ec0543a 100644 --- a/app/assets/javascripts/discourse/components/directory-toggle.js.es6 +++ b/app/assets/javascripts/discourse/components/directory-toggle.js.es6 @@ -1,7 +1,7 @@ -import StringBuffer from 'discourse/mixins/string-buffer'; import { iconHTML } from 'discourse-common/helpers/fa-icon'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; -export default Ember.Component.extend(StringBuffer, { +export default Ember.Component.extend(bufferedRender({ tagName: 'th', classNames: ['sortable'], attributeBindings: ['title'], @@ -12,8 +12,7 @@ export default Ember.Component.extend(StringBuffer, { return I18n.t(labelKey + '_long', { defaultValue: I18n.t(labelKey) }); }.property('field'), - renderString(buffer) { - + buildBuffer(buffer) { const icon = this.get('icon'); if (icon) { buffer.push(iconHTML(icon)); @@ -37,4 +36,4 @@ export default Ember.Component.extend(StringBuffer, { this.setProperties({ order: field, asc: null }); } } -}); +})); diff --git a/app/assets/javascripts/discourse/components/discourse-banner.js.es6 b/app/assets/javascripts/discourse/components/discourse-banner.js.es6 index 56374168e6c..40db3dce8ae 100644 --- a/app/assets/javascripts/discourse/components/discourse-banner.js.es6 +++ b/app/assets/javascripts/discourse/components/discourse-banner.js.es6 @@ -1,6 +1,4 @@ -import VisibleComponent from "discourse/components/visible"; - -export default VisibleComponent.extend({ +export default Ember.Component.extend({ visible: function () { var bannerKey = this.get("banner.key"), @@ -14,7 +12,7 @@ export default VisibleComponent.extend({ }.property("user.dismissed_banner_key", "banner.key", "hide"), actions: { - dismiss: function () { + dismiss() { if (this.get("user")) { this.get("user").dismissBanner(this.get("banner.key")); } else { diff --git a/app/assets/javascripts/discourse/components/dropdown-button.js.es6 b/app/assets/javascripts/discourse/components/dropdown-button.js.es6 index 0037ec17f2a..032c80e2aff 100644 --- a/app/assets/javascripts/discourse/components/dropdown-button.js.es6 +++ b/app/assets/javascripts/discourse/components/dropdown-button.js.es6 @@ -1,6 +1,6 @@ -import StringBuffer from 'discourse/mixins/string-buffer'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; -export default Ember.Component.extend(StringBuffer, { +export default Ember.Component.extend(bufferedRender({ classNameBindings: [':btn-group', 'hidden'], rerenderTriggers: ['text', 'longDescription'], @@ -23,7 +23,7 @@ export default Ember.Component.extend(StringBuffer, { this.$().off('click.dropdown-button', 'ul li'); }.on('willDestroyElement'), - renderString(buffer) { + buildBuffer(buffer) { const title = this.get('title'); if (title) { buffer.push("

" + title + "

"); @@ -56,4 +56,4 @@ export default Ember.Component.extend(StringBuffer, { buffer.push("

"); } } -}); +})); diff --git a/app/assets/javascripts/discourse/components/global-notice.js.es6 b/app/assets/javascripts/discourse/components/global-notice.js.es6 index bd45e777a4b..0b291384c6e 100644 --- a/app/assets/javascripts/discourse/components/global-notice.js.es6 +++ b/app/assets/javascripts/discourse/components/global-notice.js.es6 @@ -1,12 +1,12 @@ import { on } from 'ember-addons/ember-computed-decorators'; -import StringBuffer from 'discourse/mixins/string-buffer'; import { iconHTML } from 'discourse-common/helpers/fa-icon'; import LogsNotice from 'discourse/services/logs-notice'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; -export default Ember.Component.extend(StringBuffer, { +export default Ember.Component.extend(bufferedRender({ rerenderTriggers: ['site.isReadOnly'], - renderString: function(buffer) { + buildBuffer(buffer) { let notices = []; if (this.site.get("isReadOnly")) { @@ -51,7 +51,7 @@ export default Ember.Component.extend(StringBuffer, { @on('didInsertElement') _setupLogsNotice() { LogsNotice.current().addObserver('hidden', () => { - this.rerenderString(); + this.rerenderBuffer(); }); this.$().on('click.global-notice', '.alert-logs-notice .close', () => { @@ -63,4 +63,4 @@ export default Ember.Component.extend(StringBuffer, { _teardownLogsNotice() { this.$().off('click.global-notice'); } -}); +})); diff --git a/app/assets/javascripts/discourse/components/input-tip.js.es6 b/app/assets/javascripts/discourse/components/input-tip.js.es6 index cb1bad446ef..0178ad7cac5 100644 --- a/app/assets/javascripts/discourse/components/input-tip.js.es6 +++ b/app/assets/javascripts/discourse/components/input-tip.js.es6 @@ -1,17 +1,17 @@ -import StringBuffer from 'discourse/mixins/string-buffer'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; import { iconHTML } from 'discourse-common/helpers/fa-icon'; -export default Ember.Component.extend(StringBuffer, { +export default Ember.Component.extend(bufferedRender({ classNameBindings: [':tip', 'good', 'bad'], rerenderTriggers: ['validation'], bad: Em.computed.alias('validation.failed'), good: Em.computed.not('bad'), - renderString(buffer) { + buildBuffer(buffer) { const reason = this.get('validation.reason'); if (reason) { buffer.push(iconHTML(this.get('good') ? 'check' : 'times') + ' ' + reason); } } -}); +})); diff --git a/app/assets/javascripts/discourse/components/navigation-item.js.es6 b/app/assets/javascripts/discourse/components/navigation-item.js.es6 index 758e4a385cd..62f6a37fdef 100644 --- a/app/assets/javascripts/discourse/components/navigation-item.js.es6 +++ b/app/assets/javascripts/discourse/components/navigation-item.js.es6 @@ -1,7 +1,7 @@ import computed from "ember-addons/ember-computed-decorators"; -import StringBuffer from 'discourse/mixins/string-buffer'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; -export default Ember.Component.extend(StringBuffer, { +export default Ember.Component.extend(bufferedRender({ tagName: 'li', classNameBindings: ['active', 'content.hasIcon:has-icon'], attributeBindings: ['title'], @@ -26,7 +26,7 @@ export default Ember.Component.extend(StringBuffer, { filterMode.indexOf(contentFilterMode) === 0; }, - renderString(buffer) { + buildBuffer(buffer) { const content = this.get('content'); buffer.push(""); if (content.get('hasIcon')) { @@ -35,4 +35,4 @@ export default Ember.Component.extend(StringBuffer, { buffer.push(this.get('content.displayName')); buffer.push(""); } -}); +})); diff --git a/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 b/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 index 2c164ddf5eb..59e5856db09 100644 --- a/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 +++ b/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 @@ -1,8 +1,8 @@ -import StringBuffer from 'discourse/mixins/string-buffer'; import { iconHTML } from 'discourse-common/helpers/fa-icon'; import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; -export default Ember.Component.extend(StringBuffer, { +export default Ember.Component.extend(bufferedRender({ classNameBindings: [':popup-tip', 'good', 'bad', 'lastShownAt::hide'], animateAttribute: null, bouncePixels: 6, @@ -37,7 +37,7 @@ export default Ember.Component.extend(StringBuffer, { } }, - renderString(buffer) { + buildBuffer(buffer) { const reason = this.get('validation.reason'); if (!reason) { return; } @@ -55,4 +55,4 @@ export default Ember.Component.extend(StringBuffer, { $elem.animate({ right: '-=' + this.bouncePixels }, this.bounceDelay).animate({ right: '+=' + this.bouncePixels }, this.bounceDelay); } } -}); +})); diff --git a/app/assets/javascripts/discourse/components/tag-drop-link.js.es6 b/app/assets/javascripts/discourse/components/tag-drop-link.js.es6 index 8cfad3cd2af..f2f671e4031 100644 --- a/app/assets/javascripts/discourse/components/tag-drop-link.js.es6 +++ b/app/assets/javascripts/discourse/components/tag-drop-link.js.es6 @@ -17,10 +17,6 @@ export default Ember.Component.extend({ return "tag-" + this.get('tagId'); }.property('tagId'), - render(buffer) { - buffer.push(Handlebars.Utils.escapeExpression(this.get('tagId'))); - }, - click(e) { e.preventDefault(); DiscourseURL.routeTo(this.get('href')); diff --git a/app/assets/javascripts/discourse/components/text-overflow.js.es6 b/app/assets/javascripts/discourse/components/text-overflow.js.es6 index 73a4637bfdc..4906ad1a500 100644 --- a/app/assets/javascripts/discourse/components/text-overflow.js.es6 +++ b/app/assets/javascripts/discourse/components/text-overflow.js.es6 @@ -1,12 +1,9 @@ export default Ember.Component.extend({ - _parse: function() { + didInsertElement() { + this._super(); Ember.run.next(null, () => { this.$().find('hr').remove(); this.$().ellipsis(); }); - }.on('didInsertElement'), - - render(buffer) { - buffer.push(this.get('text')); } }); diff --git a/app/assets/javascripts/discourse/components/topic-closing.js.es6 b/app/assets/javascripts/discourse/components/topic-closing.js.es6 index b92f7740358..f4ab5748ce4 100644 --- a/app/assets/javascripts/discourse/components/topic-closing.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-closing.js.es6 @@ -1,6 +1,6 @@ -import StringBuffer from 'discourse/mixins/string-buffer'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; -export default Ember.Component.extend(StringBuffer, { +export default Ember.Component.extend(bufferedRender({ elementId: 'topic-closing-info', delayedRerender: null, @@ -9,7 +9,7 @@ export default Ember.Component.extend(StringBuffer, { 'topic.details.auto_close_based_on_last_post', 'topic.details.auto_close_hours'], - renderString(buffer) { + buildBuffer(buffer) { if (!!Ember.isEmpty(this.get('topic.details.auto_close_at'))) return; if (this.get("topic.closed")) return; @@ -48,4 +48,4 @@ export default Ember.Component.extend(StringBuffer, { Em.run.cancel(this.get('delayedRerender')); } } -}); +})); diff --git a/app/assets/javascripts/discourse/components/topic-list-item.js.es6 b/app/assets/javascripts/discourse/components/topic-list-item.js.es6 index 06f9b4bfa66..1fc7cf53a6e 100644 --- a/app/assets/javascripts/discourse/components/topic-list-item.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-list-item.js.es6 @@ -1,5 +1,5 @@ -import StringBuffer from 'discourse/mixins/string-buffer'; import computed from 'ember-addons/ember-computed-decorators'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; export function showEntrance(e) { let target = $(e.target); @@ -16,17 +16,23 @@ export function showEntrance(e) { } } -export default Ember.Component.extend(StringBuffer, { +export default Ember.Component.extend(bufferedRender({ rerenderTriggers: ['bulkSelectEnabled', 'topic.pinned'], tagName: 'tr', - rawTemplate: 'list/topic-list-item.raw', classNameBindings: [':topic-list-item', 'unboundClassNames'], attributeBindings: ['data-topic-id'], 'data-topic-id': Em.computed.alias('topic.id'), actions: { toggleBookmark() { - this.get('topic').toggleBookmark().finally(() => this.rerender()); + this.get('topic').toggleBookmark().finally(() => this.rerenderBuffer()); + } + }, + + buildBuffer(buffer) { + const template = Discourse.__container__.lookup('template:list/topic-list-item.raw'); + if (template) { + buffer.push(template(this)); } }, @@ -142,4 +148,4 @@ export default Ember.Component.extend(StringBuffer, { } }.on('didInsertElement') -}); +})); diff --git a/app/assets/javascripts/discourse/components/topic-post-badges.js.es6 b/app/assets/javascripts/discourse/components/topic-post-badges.js.es6 index 333967ff5ae..9eb07ade4fb 100644 --- a/app/assets/javascripts/discourse/components/topic-post-badges.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-post-badges.js.es6 @@ -1,4 +1,4 @@ -import StringBuffer from 'discourse/mixins/string-buffer'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; // Creates a link function link(buffer, prop, url, cssClass, i18nKey, text) { @@ -7,15 +7,15 @@ function link(buffer, prop, url, cssClass, i18nKey, text) { buffer.push(`${text || prop}\n`); } -export default Ember.Component.extend(StringBuffer, { +export default Ember.Component.extend(bufferedRender({ tagName: 'span', classNameBindings: [':topic-post-badges'], rerenderTriggers: ['url', 'unread', 'newPosts', 'unseen'], - renderString(buffer) { + buildBuffer(buffer) { const url = this.get('url'); link(buffer, this.get('unread'), url, 'unread', 'unread_posts'); link(buffer, this.get('newPosts'), url, 'new-posts', 'new_posts'); link(buffer, this.get('unseen'), url, 'new-topic', 'new', I18n.t('filters.new.lower_title')); } -}); +})); diff --git a/app/assets/javascripts/discourse/components/topic-status.js.es6 b/app/assets/javascripts/discourse/components/topic-status.js.es6 index 2fa076736fc..5afba3db76d 100644 --- a/app/assets/javascripts/discourse/components/topic-status.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-status.js.es6 @@ -1,8 +1,8 @@ import { iconHTML } from 'discourse-common/helpers/fa-icon'; -import StringBuffer from 'discourse/mixins/string-buffer'; +import { bufferedRender } from 'discourse-common/lib/buffered-render'; import { escapeExpression } from 'discourse/lib/utilities'; -export default Ember.Component.extend(StringBuffer, { +export default Ember.Component.extend(bufferedRender({ classNames: ['topic-statuses'], rerenderTriggers: ['topic.archived', 'topic.closed', 'topic.pinned', 'topic.visible', 'topic.unpinned', 'topic.is_warning'], @@ -26,7 +26,7 @@ export default Ember.Component.extend(StringBuffer, { return Discourse.User.current() && !this.get('disableActions'); }.property('disableActions'), - renderString(buffer) { + buildBuffer(buffer) { const self = this; const renderIcon = function(name, key, actionable) { @@ -57,4 +57,4 @@ export default Ember.Component.extend(StringBuffer, { renderIconIf('topic.unpinned', 'thumb-tack', 'unpinned', this.get("canAct")); renderIconIf('topic.invisible', 'eye-slash', 'invisible'); } -}); +})); diff --git a/app/assets/javascripts/discourse/components/visible.js.es6 b/app/assets/javascripts/discourse/components/visible.js.es6 deleted file mode 100644 index f9570e412ea..00000000000 --- a/app/assets/javascripts/discourse/components/visible.js.es6 +++ /dev/null @@ -1,12 +0,0 @@ -export default Ember.Component.extend({ - visibleChanged: function(){ - this.rerender(); - }.observes("visible"), - - render: function(buffer){ - if (this._state !== 'inDOM' && this._state !== 'preRender' && this._state !== 'inBuffer') { return; } - if (!this.get("visible")) { return; } - - return this._super(buffer); - } -}); diff --git a/app/assets/javascripts/discourse/mixins/string-buffer.js.es6 b/app/assets/javascripts/discourse/mixins/string-buffer.js.es6 deleted file mode 100644 index 943a972c6e4..00000000000 --- a/app/assets/javascripts/discourse/mixins/string-buffer.js.es6 +++ /dev/null @@ -1,45 +0,0 @@ -export default Ember.Mixin.create({ - - _watchProps: function() { - const args = this.get('rerenderTriggers'); - if (!Ember.isNone(args)) { - args.forEach(k => this.addObserver(k, this.rerenderString)); - } - }.on('init'), - - render(buffer) { - this.renderString(buffer); - }, - - renderString(buffer){ - const template = Discourse.__container__.lookup('template:' + this.rawTemplate); - if (template) { - buffer.push(template(this)); - } - }, - - _rerenderString() { - const $sel = this.$(); - if (!$sel) { return; } - - const buffer = []; - this.renderString(buffer); - - // Chrome likes scrolling after HTML is set - // This happens if you navigate back and forth a few times - // Before removing this code confirm that this does not cause scrolling - // 1. Sort by views - // 2. Go to last post on one of the topics - // 3. Hit back - // 4. Go to last post on same topic - // 5. Expand likes - const scrollTop = $(window).scrollTop(); - $sel.html(buffer.join('')); - $(window).scrollTop(scrollTop); - }, - - rerenderString() { - Ember.run.once(this, '_rerenderString'); - } - -}); diff --git a/app/assets/javascripts/discourse/templates/components/check-mark.hbs b/app/assets/javascripts/discourse/templates/components/check-mark.hbs new file mode 100644 index 00000000000..85c38d59ac1 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/check-mark.hbs @@ -0,0 +1,5 @@ +{{#if checked}} + {{fa-icon "check"}} +{{else}} + {{fa-icon "times"}} +{{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/conditional-loading-spinner.hbs b/app/assets/javascripts/discourse/templates/components/conditional-loading-spinner.hbs new file mode 100644 index 00000000000..e18527be3a8 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/conditional-loading-spinner.hbs @@ -0,0 +1,5 @@ +{{#if condition}} +
+{{else}} + {{yield}} +{{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/d-button.hbs b/app/assets/javascripts/discourse/templates/components/d-button.hbs new file mode 100644 index 00000000000..14364c51ff3 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/d-button.hbs @@ -0,0 +1,3 @@ +{{fa-icon icon}} +{{translatedLabel}} +{{yield}} diff --git a/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs b/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs index 1a647c5ea82..306f2924b44 100644 --- a/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs +++ b/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs @@ -1,11 +1,13 @@ -
-