mirror of
				https://github.com/discourse/discourse.git
				synced 2025-02-25 18:55:32 -06:00 
			
		
		
		
	Refactor selector components for extensibility
This commit is contained in:
		@@ -1,29 +1,13 @@
 | 
			
		||||
var compiled;
 | 
			
		||||
 | 
			
		||||
function templateFunction() {
 | 
			
		||||
  compiled = compiled || Handlebars.compile(
 | 
			
		||||
    "<div class='autocomplete'>" +
 | 
			
		||||
      "<ul>" +
 | 
			
		||||
      "{{#each options}}" +
 | 
			
		||||
        "<li>" +
 | 
			
		||||
            "<a href=''>{{this.name}}</a>" +
 | 
			
		||||
        "</li>" +
 | 
			
		||||
      "{{/each}}" +
 | 
			
		||||
      "</ul>" +
 | 
			
		||||
      "</div>"
 | 
			
		||||
  );
 | 
			
		||||
  return compiled;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Em.Component.extend({
 | 
			
		||||
export default Ember.Component.extend({
 | 
			
		||||
  placeholder: function(){
 | 
			
		||||
    return I18n.t(this.get("placeholderKey"));
 | 
			
		||||
  }.property("placeholderKey"),
 | 
			
		||||
 | 
			
		||||
  didInsertElement: function() {
 | 
			
		||||
  _initializeAutocomplete: function() {
 | 
			
		||||
    var self = this;
 | 
			
		||||
    var selectedGroups;
 | 
			
		||||
 | 
			
		||||
    var template = this.container.lookup('template:group-selector-autocomplete.raw');
 | 
			
		||||
    self.$('input').autocomplete({
 | 
			
		||||
      allowAny: false,
 | 
			
		||||
      onChangeItems: function(items){
 | 
			
		||||
@@ -45,7 +29,7 @@ export default Em.Component.extend({
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
      template: templateFunction()
 | 
			
		||||
      template: template
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  }.on('didInsertElement')
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,62 @@
 | 
			
		||||
import TextField from 'discourse/components/text-field';
 | 
			
		||||
import userSearch from 'discourse/lib/user-search';
 | 
			
		||||
 | 
			
		||||
export default TextField.extend({
 | 
			
		||||
 | 
			
		||||
  _initializeAutocomplete: function() {
 | 
			
		||||
    var self = this,
 | 
			
		||||
        selected = [],
 | 
			
		||||
        currentUser = this.currentUser,
 | 
			
		||||
        includeGroups = this.get('includeGroups') === 'true';
 | 
			
		||||
 | 
			
		||||
    function excludedUsernames() {
 | 
			
		||||
      if (currentUser && self.get('excludeCurrentUser')) {
 | 
			
		||||
        return selected.concat([currentUser.get('username')]);
 | 
			
		||||
      }
 | 
			
		||||
      return selected;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var template = this.container.lookup('template:user-selector-autocomplete.raw');
 | 
			
		||||
    $(this.get('element')).val(this.get('usernames')).autocomplete({
 | 
			
		||||
      template: template,
 | 
			
		||||
 | 
			
		||||
      disabled: this.get('disabled'),
 | 
			
		||||
      single: this.get('single'),
 | 
			
		||||
      allowAny: this.get('allowAny'),
 | 
			
		||||
 | 
			
		||||
      dataSource: function(term) {
 | 
			
		||||
        return userSearch({
 | 
			
		||||
          term: term,
 | 
			
		||||
          topicId: self.get('topicId'),
 | 
			
		||||
          exclude: excludedUsernames(),
 | 
			
		||||
          includeGroups: includeGroups
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      transformComplete: function(v) {
 | 
			
		||||
        if (v.username) {
 | 
			
		||||
          return v.username;
 | 
			
		||||
        } else {
 | 
			
		||||
          var excludes = excludedUsernames();
 | 
			
		||||
          return v.usernames.filter(function(item){
 | 
			
		||||
            return excludes.indexOf(item) === -1;
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      onChangeItems: function(items) {
 | 
			
		||||
        items = items.map(function(i) {
 | 
			
		||||
          return i.username ? i.username : i;
 | 
			
		||||
        });
 | 
			
		||||
        self.set('usernames', items.join(","));
 | 
			
		||||
        selected = items;
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      reverseTransform: function(i) {
 | 
			
		||||
        return { username: i };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
  }.on('didInsertElement')
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
import registerUnbound from 'discourse/helpers/register-unbound';
 | 
			
		||||
 | 
			
		||||
registerUnbound('max-usernames', function(usernames, params) {
 | 
			
		||||
  var maxLength = parseInt(params.max) || 3;
 | 
			
		||||
  if (usernames.length > maxLength){
 | 
			
		||||
    return usernames.slice(0, maxLength).join(", ") + ", +" + (usernames.length - maxLength);
 | 
			
		||||
  } else {
 | 
			
		||||
    return usernames.join(", ");
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
@@ -1,43 +0,0 @@
 | 
			
		||||
var deprecatedViewHelpers = {
 | 
			
		||||
  inputTip: 'input-tip',
 | 
			
		||||
  pagedown: 'pagedown-editor',
 | 
			
		||||
  textField: 'text-field',
 | 
			
		||||
  userSelector: 'user-selector',
 | 
			
		||||
  combobox: 'combo-box',
 | 
			
		||||
  categoryChooser: 'category-chooser',
 | 
			
		||||
  chooseTopic: 'choose-topic',
 | 
			
		||||
  'discourse-activity-filter': 'activity-filter'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
var renamedHelpers = {
 | 
			
		||||
  icon: "fa-icon",
 | 
			
		||||
  date: "format-date",
 | 
			
		||||
  age: "format-age"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'deprecations',
 | 
			
		||||
  initialize: function(container) {
 | 
			
		||||
    Ember.keys(deprecatedViewHelpers).forEach(function(old) {
 | 
			
		||||
      var newName = deprecatedViewHelpers[old];
 | 
			
		||||
      Ember.Handlebars.registerHelper(old, function(options) {
 | 
			
		||||
        Em.warn("The `" + old +"` helper is deprecated. Use `" + newName + "` instead.");
 | 
			
		||||
        var helper = container.lookupFactory('view:' + newName) || container.lookupFactory('component:' + newName);
 | 
			
		||||
        var hash = options.hash,
 | 
			
		||||
            types = options.hashTypes;
 | 
			
		||||
 | 
			
		||||
        Discourse.Utilities.normalizeHash(hash, types);
 | 
			
		||||
        return Ember.Handlebars.helpers.view.call(this, helper, options);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Ember.keys(renamedHelpers).forEach(function(old) {
 | 
			
		||||
      var newName = renamedHelpers[old];
 | 
			
		||||
      Ember.Handlebars.registerHelper(old, function() {
 | 
			
		||||
        Em.warn("The `" + old +"` helper is deprecated. Use `" + newName + "` instead.");
 | 
			
		||||
        var newHelper = container.lookupFactory('helper:' + newName);
 | 
			
		||||
        return newHelper.apply(this, Array.prototype.slice.call(arguments));
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@@ -39,5 +39,9 @@ export default {
 | 
			
		||||
    application.inject('route', 'session', 'session:main');
 | 
			
		||||
    application.inject('view', 'session', 'session:main');
 | 
			
		||||
    application.inject('model', 'session', 'session:main');
 | 
			
		||||
 | 
			
		||||
    // Inject currentUser. Components only for now to prevent any breakage
 | 
			
		||||
    application.register('current-user:main', Discourse.User.current(), { instantiate: false });
 | 
			
		||||
    application.inject('component', 'currentUser', 'current-user:main');
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
var helpers = ['input-tip',
 | 
			
		||||
               'pagedown-editor',
 | 
			
		||||
               'user-selector',
 | 
			
		||||
               'category-chooser',
 | 
			
		||||
               'combo-box',
 | 
			
		||||
               'choose-topic',
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ function organizeResults(r, options) {
 | 
			
		||||
 | 
			
		||||
export default function userSearch(options) {
 | 
			
		||||
  var term = options.term || "",
 | 
			
		||||
      includeGroups = !!options.include_groups,
 | 
			
		||||
      includeGroups = options.includeGroups,
 | 
			
		||||
      topicId = options.topicId;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
  {{render "composer-messages"}}
 | 
			
		||||
 | 
			
		||||
  <div class='control'>
 | 
			
		||||
    <a href='#' class='toggler' {{action "toggle" bubbles=false}} title='{{i18n 'composer.toggler'}}'></a>
 | 
			
		||||
    <a href class='toggler' {{action "toggle" bubbles=false}} title='{{i18n 'composer.toggler'}}'></a>
 | 
			
		||||
 | 
			
		||||
    {{#if model.viewOpen}}
 | 
			
		||||
      <div class='control-row reply-area'>
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
              {{user-selector topicId=controller.controllers.topic.model.id
 | 
			
		||||
                              excludeCurrentUser="true"
 | 
			
		||||
                              id="private-message-users"
 | 
			
		||||
                              include_groups="true"
 | 
			
		||||
                              includeGroups="true"
 | 
			
		||||
                              class="span8"
 | 
			
		||||
                              placeholderKey="composer.users_placeholder"
 | 
			
		||||
                              tabindex="1"
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
<div class='autocomplete'>
 | 
			
		||||
  <ul>
 | 
			
		||||
    {{#each options}}
 | 
			
		||||
      <li><a href>{{this.name}}</a></li>
 | 
			
		||||
    {{/each}}
 | 
			
		||||
  </ul>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
 | 
			
		||||
  <form>
 | 
			
		||||
      <label>{{i18n 'topic.change_owner.label'}}</label>
 | 
			
		||||
    {{user-selector single=true usernames=new_user include_groups="false" placeholderKey="topic.change_owner.placeholder"}}
 | 
			
		||||
    {{user-selector single="true" usernames=new_user placeholderKey="topic.change_owner.placeholder"}}
 | 
			
		||||
  </form>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 | 
			
		||||
    {{#if isAdmin}}
 | 
			
		||||
      <label>{{{groupInstructions}}}</label>
 | 
			
		||||
      {{group-selector includeAuto=false groupFinder=groupFinder groupNames=groupNames placeholderKey="topic.invite_private.group_name"}}
 | 
			
		||||
      {{group-selector groupFinder=groupFinder groupNames=groupNames placeholderKey="topic.invite_private.group_name"}}
 | 
			
		||||
    {{/if}}
 | 
			
		||||
  {{/if}}
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
    {{i18n 'topic.invite_private.success'}}
 | 
			
		||||
  {{else}}
 | 
			
		||||
    <label>{{i18n 'topic.invite_private.email_or_username'}}</label>
 | 
			
		||||
    {{user-selector single=true allowAny=true usernames=emailOrUsername include_groups="true" placeholderKey="topic.invite_private.email_or_username_placeholder"}}
 | 
			
		||||
    {{user-selector single="true" allowAny=true usernames=emailOrUsername includeGroups="true" placeholderKey="topic.invite_private.email_or_username_placeholder"}}
 | 
			
		||||
  {{/if}}
 | 
			
		||||
</div>
 | 
			
		||||
<div class="modal-footer">
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,22 @@
 | 
			
		||||
<div class='autocomplete'>
 | 
			
		||||
  <ul>
 | 
			
		||||
    {{#each options.users}}
 | 
			
		||||
      <li>
 | 
			
		||||
      <a href='#'>{{avatar this imageSize="tiny"}}
 | 
			
		||||
        <span class='username'>{{this.username}}</span>
 | 
			
		||||
        <span class='name'>{{this.name}}</span></a>
 | 
			
		||||
      </li>
 | 
			
		||||
    {{/each}}
 | 
			
		||||
    {{#if options.groups}}
 | 
			
		||||
      {{#if options.users}}<hr>{{/if}}
 | 
			
		||||
      {{#each options.groups}}
 | 
			
		||||
        <li>
 | 
			
		||||
        <a href=''><i class='icon-group'></i>
 | 
			
		||||
          <span class='username'>{{this.name}}</span>
 | 
			
		||||
          <span class='name'>{{max-usernames usernames max="3"}}</span>
 | 
			
		||||
        </a>
 | 
			
		||||
        </li>
 | 
			
		||||
      {{/each}}
 | 
			
		||||
    {{/if}}
 | 
			
		||||
  </ul>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -173,7 +173,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
 | 
			
		||||
    $LAB.script(assetPath('defer/html-sanitizer-bundle'));
 | 
			
		||||
    ComposerView.trigger("initWmdEditor");
 | 
			
		||||
 | 
			
		||||
    var template = this.container.lookupFactory('view:user-selector').templateFunction();
 | 
			
		||||
    var template = this.container.lookup('template:user-selector-autocomplete.raw');
 | 
			
		||||
    $wmdInput.data('init', true);
 | 
			
		||||
    $wmdInput.autocomplete({
 | 
			
		||||
      template: template,
 | 
			
		||||
@@ -181,7 +181,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
 | 
			
		||||
        return userSearch({
 | 
			
		||||
          term: term,
 | 
			
		||||
          topicId: self.get('controller.controllers.topic.model.id'),
 | 
			
		||||
          include_groups: true
 | 
			
		||||
          includeGroups: true
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
      key: "@",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,109 +0,0 @@
 | 
			
		||||
import TextField from 'discourse/components/text-field';
 | 
			
		||||
import userSearch from 'discourse/lib/user-search';
 | 
			
		||||
 | 
			
		||||
var compiled;
 | 
			
		||||
function templateFunction() {
 | 
			
		||||
  if (!compiled) {
 | 
			
		||||
    Handlebars.registerHelper("showMax", function(context, block) {
 | 
			
		||||
      var maxLength = parseInt(block.hash.max) || 3;
 | 
			
		||||
      if (context.length > maxLength){
 | 
			
		||||
        return context.slice(0, maxLength).join(", ") + ", +" + (context.length - maxLength);
 | 
			
		||||
      } else {
 | 
			
		||||
        return context.join(", ");
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    compiled = Handlebars.compile(
 | 
			
		||||
      "<div class='autocomplete'>" +
 | 
			
		||||
        "<ul>" +
 | 
			
		||||
        "{{#each options.users}}" +
 | 
			
		||||
          "<li>" +
 | 
			
		||||
              "<a href='#'>{{avatar this imageSize=\"tiny\"}} " +
 | 
			
		||||
              "<span class='username'>{{this.username}}</span> " +
 | 
			
		||||
              "<span class='name'>{{this.name}}</span></a>" +
 | 
			
		||||
          "</li>" +
 | 
			
		||||
        "{{/each}}" +
 | 
			
		||||
        "{{#if options.groups}}" +
 | 
			
		||||
          "{{#if options.users}}<hr>{{/if}}"+
 | 
			
		||||
            "{{#each options.groups}}" +
 | 
			
		||||
              "<li>" +
 | 
			
		||||
                "<a href=''><i class='icon-group'></i>" +
 | 
			
		||||
                  "<span class='username'>{{this.name}}</span> " +
 | 
			
		||||
                  "<span class='name'>{{showMax this.usernames max=3}}</span>" +
 | 
			
		||||
                "</a>" +
 | 
			
		||||
              "</li>" +
 | 
			
		||||
            "{{/each}}" +
 | 
			
		||||
          "{{/if}}" +
 | 
			
		||||
        "</ul>" +
 | 
			
		||||
      "</div>");
 | 
			
		||||
  }
 | 
			
		||||
  return compiled;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var UserSelector = TextField.extend({
 | 
			
		||||
 | 
			
		||||
  didInsertElement: function() {
 | 
			
		||||
    var userSelectorView = this,
 | 
			
		||||
        selected = [];
 | 
			
		||||
 | 
			
		||||
    function excludedUsernames() {
 | 
			
		||||
      var exclude = selected;
 | 
			
		||||
      if (userSelectorView.get('excludeCurrentUser')) {
 | 
			
		||||
        exclude = exclude.concat([Discourse.User.currentProp('username')]);
 | 
			
		||||
      }
 | 
			
		||||
      return exclude;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $(this.get('element')).val(this.get('usernames')).autocomplete({
 | 
			
		||||
      template: templateFunction(),
 | 
			
		||||
 | 
			
		||||
      disabled: this.get('disabled'),
 | 
			
		||||
      single: this.get('single'),
 | 
			
		||||
      allowAny: this.get('allowAny'),
 | 
			
		||||
 | 
			
		||||
      dataSource: function(term) {
 | 
			
		||||
        return userSearch({
 | 
			
		||||
          term: term,
 | 
			
		||||
          topicId: userSelectorView.get('topicId'),
 | 
			
		||||
          exclude: excludedUsernames(),
 | 
			
		||||
          include_groups: userSelectorView.get('include_groups')
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      transformComplete: function(v) {
 | 
			
		||||
        if (v.username) {
 | 
			
		||||
          return v.username;
 | 
			
		||||
        } else {
 | 
			
		||||
          var excludes = excludedUsernames();
 | 
			
		||||
          return v.usernames.filter(function(item){
 | 
			
		||||
                // include only, those not found in the exclude list
 | 
			
		||||
                return excludes.indexOf(item) === -1;
 | 
			
		||||
              });
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      onChangeItems: function(items) {
 | 
			
		||||
        items = _.map(items, function(i) {
 | 
			
		||||
          if (i.username) {
 | 
			
		||||
            return i.username;
 | 
			
		||||
          } else {
 | 
			
		||||
            return i;
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
        userSelectorView.set('usernames', items.join(","));
 | 
			
		||||
        selected = items;
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      reverseTransform: function(i) {
 | 
			
		||||
        return { username: i };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
UserSelector.reopenClass({ templateFunction: templateFunction });
 | 
			
		||||
 | 
			
		||||
export default UserSelector;
 | 
			
		||||
		Reference in New Issue
	
	Block a user