From 1006b1ba94d01be0de67893319745c23dd6e2431 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 11 Mar 2016 15:52:18 -0500 Subject: [PATCH] Various Plugin Enhancements and Extension Points --- .../discourse/helpers/plugin-outlet.js.es6 | 7 +++-- .../discourse/lib/plugin-api.js.es6 | 15 +++++++++-- .../discourse/templates/user/user.hbs | 3 +++ .../discourse/widgets/decorator-helper.js.es6 | 14 ++++++++++ .../discourse/widgets/emoji.js.es6 | 9 +++++++ app/models/user.rb | 27 +++++++++++++++++++ app/serializers/user_serializer.rb | 9 ++----- lib/plugin/instance.rb | 4 +++ lib/topic_view.rb | 12 ++++----- spec/models/user_spec.rb | 1 - 10 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 app/assets/javascripts/discourse/widgets/emoji.js.es6 diff --git a/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 index 3df8b18c56b..f7a85a460e8 100644 --- a/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 +++ b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 @@ -110,7 +110,7 @@ function buildConnectorCache() { _connectorCache[outletName].removeObject(viewClass); } else { if (!/\.raw$/.test(uniqueName)) { - viewClass = Em.View.extend({ classNames: [outletName + '-outlet', uniqueName] }); + viewClass = Ember.View.extend({ classNames: [outletName + '-outlet', uniqueName] }); } } @@ -172,8 +172,11 @@ Ember.HTMLBars._registerHelper('plugin-outlet', function(params, hash, options, // just shove it in. const viewClass = (childViews.length > 1) ? Ember.ContainerView : childViews[0]; + const newHash = viewInjections(env.data.view.container); + if (hash.tagName) { newHash.tagName = hash.tagName; } + delete options.fn; // we don't need the default template since we have a connector - env.helpers.view.helperFunction.call(this, [viewClass], viewInjections(env.data.view.container), options, env); + env.helpers.view.helperFunction.call(this, [viewClass], newHash, options, env); const cvs = env.data.view._childViews; if (childViews.length > 1 && cvs && cvs.length) { diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 index 947fae6f73a..b8eeb0fe7fc 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 +++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 @@ -5,7 +5,7 @@ import { addButton } from 'discourse/widgets/post-menu'; import { includeAttributes } from 'discourse/lib/transform-post'; import { addToolbarCallback } from 'discourse/components/d-editor'; import { addWidgetCleanCallback } from 'discourse/components/mount-widget'; -import { decorateWidget, changeSetting } from 'discourse/widgets/widget'; +import { createWidget, decorateWidget, changeSetting } from 'discourse/widgets/widget'; import { onPageChange } from 'discourse/lib/page-tracker'; import { preventCloak } from 'discourse/widgets/post-stream'; @@ -89,6 +89,8 @@ class PluginApi { const src = Discourse.Emoji.urlFor(emoji); return dec.h('img', { className: 'emoji', attributes: { src } }); }); + + iconBody = result.emoji.split('|').map(name => dec.attach('emoji', { name })); } if (result.text) { @@ -268,11 +270,20 @@ class PluginApi { preventCloak(postId) { preventCloak(postId); } + + /** + * Exposes the widget creating ability to plugins. Plugins can + * register their own plugins and attach them with decorators. + * See `createWidget` in `discourse/widgets/widget` for more info. + **/ + createWidget(name, args) { + return createWidget(name, args); + } } let _pluginv01; function getPluginApi(version) { - if (version === "0.1") { + if (version === "0.1" || version === "0.2") { if (!_pluginv01) { _pluginv01 = new PluginApi(version, Discourse.__container__); } diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs index 39718247396..9c3b28877fb 100644 --- a/app/assets/javascripts/discourse/templates/user/user.hbs +++ b/app/assets/javascripts/discourse/templates/user/user.hbs @@ -46,11 +46,14 @@ {{#if currentUser.staff}}
  • {{fa-icon "wrench"}}{{i18n 'admin.user.show_admin_profile'}}
  • {{/if}} + {{plugin-outlet "user-profile-controls" tagName="li"}} + {{#if collapsedInfo}} {{#if viewingSelf}}
  • {{fa-icon "angle-double-down"}}{{i18n 'user.expand_profile'}}
  • {{/if}} {{/if}} + diff --git a/app/assets/javascripts/discourse/widgets/decorator-helper.js.es6 b/app/assets/javascripts/discourse/widgets/decorator-helper.js.es6 index 6ea4eb7d2bc..13db7ce8364 100644 --- a/app/assets/javascripts/discourse/widgets/decorator-helper.js.es6 +++ b/app/assets/javascripts/discourse/widgets/decorator-helper.js.es6 @@ -22,6 +22,20 @@ class DecoratorHelper { **/ // h() is attached via `prototype` below + /** + * Attach another widget inside this one. + * + * ``` + * return helper.attach('widget-name'); + * ``` + */ + attach(name, attrs, state) { + attrs = attrs || this.widget.attrs; + state = state || this.widget.state; + + return this.widget.attach(name, attrs, state); + } + /** * Returns the model associated with this widget. When decorating * posts this will normally be the post. diff --git a/app/assets/javascripts/discourse/widgets/emoji.js.es6 b/app/assets/javascripts/discourse/widgets/emoji.js.es6 new file mode 100644 index 00000000000..6ab36c4692a --- /dev/null +++ b/app/assets/javascripts/discourse/widgets/emoji.js.es6 @@ -0,0 +1,9 @@ +import { createWidget } from 'discourse/widgets/widget'; + +export default createWidget('emoji', { + tagName: 'img.emoji', + + buildAttributes(attrs) { + return { src: Discourse.Emoji.urlFor(attrs.name) }; + }, +}); diff --git a/app/models/user.rb b/app/models/user.rb index fb865b213c4..e0663db08e1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -148,6 +148,33 @@ class User < ActiveRecord::Base User.where(username_lower: lower).blank? && !SiteSetting.reserved_usernames.split("|").include?(username) end + def self.plugin_staff_user_custom_fields + @plugin_staff_user_custom_fields ||= {} + end + + def self.register_plugin_staff_custom_field(custom_field_name, plugin) + plugin_staff_user_custom_fields[custom_field_name] = plugin + end + + def self.whitelisted_user_custom_fields(guardian) + fields = [] + + if SiteSetting.public_user_custom_fields.present? + fields += SiteSetting.public_user_custom_fields.split('|') + end + + if guardian.is_staff? + if SiteSetting.staff_user_custom_fields.present? + fields += SiteSetting.staff_user_custom_fields.split('|') + end + plugin_staff_user_custom_fields.each do |k, v| + fields << k if v.enabled? + end + end + + fields.uniq + end + def effective_locale if SiteSetting.allow_user_locale && self.locale.present? self.locale diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 730a23da741..b1acb42fe8a 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -318,15 +318,10 @@ class UserSerializer < BasicUserSerializer end def custom_fields - fields = nil + fields = User.whitelisted_user_custom_fields(scope) if scope.can_edit?(object) - fields = DiscoursePluginRegistry.serialized_current_user_fields.to_a - end - - if SiteSetting.public_user_custom_fields.present? - fields ||= [] - fields += SiteSetting.public_user_custom_fields.split('|') + fields += DiscoursePluginRegistry.serialized_current_user_fields.to_a end if fields.present? diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index f13fd226a48..7fb3735de1d 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -67,6 +67,10 @@ class Plugin::Instance klass.send(:define_method, "include_#{attr}?") { plugin.enabled? } end + def whitelist_staff_user_custom_field(field) + User.register_plugin_staff_custom_field(field, self) + end + # Extend a class but check that the plugin is enabled # for class methods use `add_class_method` def add_to_class(klass, attr, &block) diff --git a/lib/topic_view.rb b/lib/topic_view.rb index 97b903ec502..9f84dd8063b 100644 --- a/lib/topic_view.rb +++ b/lib/topic_view.rb @@ -54,13 +54,11 @@ class TopicView filter_posts(options) - if SiteSetting.public_user_custom_fields.present? && @posts - @user_custom_fields = User.custom_fields_for_ids(@posts.map(&:user_id), SiteSetting.public_user_custom_fields.split('|')) - end - - if @guardian.is_staff? && SiteSetting.staff_user_custom_fields.present? && @posts - @user_custom_fields ||= {} - @user_custom_fields.deep_merge!(User.custom_fields_for_ids(@posts.map(&:user_id), SiteSetting.staff_user_custom_fields.split('|'))) + if @posts + added_fields = User.whitelisted_user_custom_fields(@guardian) + if added_fields.present? + @user_custom_fields = User.custom_fields_for_ids(@posts.map(&:user_id), added_fields) + end end whitelisted_fields = TopicView.whitelisted_post_custom_fields(@user) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 35a9e72725f..84a992ae942 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -38,7 +38,6 @@ describe User do Jobs.expects(:enqueue).with(:send_system_message, user_id: user.id, message_type: 'welcome_user').never user.enqueue_welcome_message('welcome_user') end - end describe '.approve' do