diff --git a/Gemfile b/Gemfile index c314af4463b..abf697bb3b9 100644 --- a/Gemfile +++ b/Gemfile @@ -48,7 +48,7 @@ gem 'onebox' gem 'http_accept_language', '~>2.0.5', require: false gem 'ember-rails', '0.18.5' -gem 'ember-source', '2.10.0' +gem 'ember-source', '2.4.6' gem 'barber' gem 'babel-transpiler' diff --git a/Gemfile.lock b/Gemfile.lock index bebf768f42f..f48e3ff72eb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -93,7 +93,7 @@ GEM ember-source (>= 1.1.0) jquery-rails (>= 1.0.17) railties (>= 3.1) - ember-source (2.10.0) + ember-source (2.4.6) erubis (2.7.0) eventmachine (1.2.0.1) excon (0.53.0) @@ -413,7 +413,7 @@ DEPENDENCIES discourse_fastimage (= 2.0.3) email_reply_trimmer (= 0.1.6) ember-rails (= 0.18.5) - ember-source (= 2.10.0) + ember-source (= 2.4.6) excon execjs fabrication (= 2.9.8) diff --git a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 index 7ddc6f3ac4e..7d11fc86226 100644 --- a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 @@ -68,7 +68,7 @@ export default Ember.Controller.extend(BufferedContent, { this.get('model').save(data).then(() => { if (newBadge) { const adminBadges = this.get('adminBadges.model'); - if (!adminBadges.includes(model)) { + if (!adminBadges.contains(model)) { adminBadges.pushObject(model); } this.transitionToRoute('adminBadges.show', model.get('id')); diff --git a/app/assets/javascripts/admin/templates/admin.hbs b/app/assets/javascripts/admin/templates/admin.hbs index 7f34c00b772..77904edc48b 100644 --- a/app/assets/javascripts/admin/templates/admin.hbs +++ b/app/assets/javascripts/admin/templates/admin.hbs @@ -23,7 +23,7 @@ {{nav-item route='admin.backups' label='admin.backups.title'}} {{/if}} {{nav-item route='adminPlugins' label='admin.plugins.title'}} - {{plugin-outlet name="admin-menu" connectorTagName="li"}} + {{plugin-outlet "admin-menu" tagName="li"}}
{{i18n 'admin.dashboard.installed_version'}} | diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 96a2066c414..2a4085f785e 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -89,6 +89,7 @@ //= require_tree ./discourse/models //= require_tree ./discourse/components //= require_tree ./discourse/raw-views +//= require_tree ./discourse/views //= require_tree ./discourse/helpers //= require_tree ./discourse/templates //= require_tree ./discourse/routes diff --git a/app/assets/javascripts/discourse-common/resolver.js.es6 b/app/assets/javascripts/discourse-common/resolver.js.es6 index 27b80c40c00..a69efc4e30b 100644 --- a/app/assets/javascripts/discourse-common/resolver.js.es6 +++ b/app/assets/javascripts/discourse-common/resolver.js.es6 @@ -11,18 +11,18 @@ export function setResolverOption(name, value) { } function parseName(fullName) { - const nameParts = fullName.split(":"); - const type = nameParts[0]; - let fullNameWithoutType = nameParts[1]; - const namespace = get(this, 'namespace'); - const root = namespace; + const nameParts = fullName.split(":"), + type = nameParts[0], fullNameWithoutType = nameParts[1], + name = fullNameWithoutType, + namespace = get(this, 'namespace'), + root = namespace; return { - fullName, - type, - fullNameWithoutType, - name: fullNameWithoutType, - root, + fullName: fullName, + type: type, + fullNameWithoutType: fullNameWithoutType, + name: name, + root: root, resolveMethodName: "resolve" + classify(type) }; } @@ -125,21 +125,12 @@ export function buildResolver(baseName) { } }, - findConnectorTemplate(parsedName) { - const full = parsedName.fullNameWithoutType.replace('components/', ''); - if (full.indexOf('connectors') === 0) { - return Ember.TEMPLATES[`javascripts/${full}`]; - } - - }, - resolveTemplate(parsedName) { return this.findPluginMobileTemplate(parsedName) || this.findPluginTemplate(parsedName) || this.findMobileTemplate(parsedName) || this.findTemplate(parsedName) || this.findLoadingTemplate(parsedName) || - this.findConnectorTemplate(parsedName) || Ember.TEMPLATES.not_found; }, diff --git a/app/assets/javascripts/discourse.js.es6 b/app/assets/javascripts/discourse.js.es6 index 213ef89ceba..9012e8301b2 100644 --- a/app/assets/javascripts/discourse.js.es6 +++ b/app/assets/javascripts/discourse.js.es6 @@ -7,7 +7,6 @@ const Discourse = Ember.Application.extend({ rootElement: '#main', _docTitle: document.title, __TAGS_INCLUDED__: true, - RAW_TEMPLATES: {}, getURL(url) { if (!url) return url; diff --git a/app/assets/javascripts/discourse/components/badge-selector.js.es6 b/app/assets/javascripts/discourse/components/badge-selector.js.es6 index 047ad4ba568..315f7398a78 100644 --- a/app/assets/javascripts/discourse/components/badge-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/badge-selector.js.es6 @@ -1,4 +1,5 @@ import { on, observes, default as computed } from 'ember-addons/ember-computed-decorators'; +import { getOwner } from 'discourse-common/lib/get-owner'; export default Ember.Component.extend({ @computed('placeholderKey') @@ -17,7 +18,7 @@ export default Ember.Component.extend({ var self = this; var selectedBadges; - var template = Discourse.RAW_TEMPLATES['badge-selector-autocomplete']; + var template = getOwner(this).lookup('template:badge-selector-autocomplete.raw'); self.$('input').autocomplete({ allowAny: false, items: _.isArray(this.get('badgeNames')) ? this.get('badgeNames') : [this.get('badgeNames')], diff --git a/app/assets/javascripts/discourse/components/category-selector.js.es6 b/app/assets/javascripts/discourse/components/category-selector.js.es6 index 70cdafdd729..460a1dfdd65 100644 --- a/app/assets/javascripts/discourse/components/category-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/category-selector.js.es6 @@ -1,6 +1,7 @@ import { categoryBadgeHTML } from 'discourse/helpers/category-link'; import Category from 'discourse/models/category'; import { on, observes } from 'ember-addons/ember-computed-decorators'; +import { getOwner } from 'discourse-common/lib/get-owner'; export default Ember.Component.extend({ @observes('categories') @@ -12,7 +13,7 @@ export default Ember.Component.extend({ @on('didInsertElement') _initializeAutocomplete(opts) { const self = this, - template = Discourse.RAW_TEMPLATES['category-selector-autocomplete'], + template = getOwner(this).lookup('template:category-selector-autocomplete.raw'), regexp = new RegExp(`href=['\"]${Discourse.getURL('/c/')}([^'\"]+)`); this.$('input').autocomplete({ diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6 index 88ca121c3ab..55b69c64a1c 100644 --- a/app/assets/javascripts/discourse/components/composer-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6 @@ -6,6 +6,7 @@ import { linkSeenTagHashtags, fetchUnseenTagHashtags } from 'discourse/lib/link- import { load } from 'pretty-text/oneboxer'; import { ajax } from 'discourse/lib/ajax'; import InputValidation from 'discourse/models/input-validation'; +import { getOwner } from 'discourse-common/lib/get-owner'; import { tinyAvatar, displayErrorForUpload, getUploadMarkdown, @@ -61,7 +62,7 @@ export default Ember.Component.extend({ @on('didInsertElement') _composerEditorInit() { const topicId = this.get('topic.id'); - const template = Discourse.RAW_TEMPLATES['user-selector-autocomplete']; + const template = getOwner(this).lookup('template:user-selector-autocomplete.raw'); const $input = this.$('.d-editor-input'); $input.autocomplete({ template, diff --git a/app/assets/javascripts/discourse/components/composer-title.js.es6 b/app/assets/javascripts/discourse/components/composer-title.js.es6 index 31d13d3f281..1e0d6b75601 100644 --- a/app/assets/javascripts/discourse/components/composer-title.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-title.js.es6 @@ -51,8 +51,6 @@ export default Ember.Component.extend({ }, _checkForUrl() { - if (!this.element || this.isDestroying || this.isDestroyed) { return; } - if (this.get('isAbsoluteUrl') && (this.get('composer.reply')||"").length === 0) { // Try to onebox. If success, update post body and title. diff --git a/app/assets/javascripts/discourse/components/custom-html.js.es6 b/app/assets/javascripts/discourse/components/custom-html.js.es6 deleted file mode 100644 index c5df7fb51ed..00000000000 --- a/app/assets/javascripts/discourse/components/custom-html.js.es6 +++ /dev/null @@ -1,20 +0,0 @@ -import { getCustomHTML } from 'discourse/helpers/custom-html'; -import { getOwner } from 'discourse-common/lib/get-owner'; - -export default Ember.Component.extend({ - init() { - this._super(); - const name = this.get('name'); - const html = getCustomHTML(name); - - if (html) { - this.set('html', html); - this.set('layoutName', 'components/custom-html-container'); - } else { - const template = getOwner(this).lookup(`template:${name}`); - if (template) { - this.set('layoutName', name); - } - } - } -}); diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 0995fd6438d..6e43dfde817 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -297,7 +297,7 @@ export default Ember.Component.extend({ }, _applyCategoryHashtagAutocomplete() { - const template = Discourse.RAW_TEMPLATES['category-tag-autocomplete']; + const template = this.register.lookup('template:category-tag-autocomplete.raw'); const siteSettings = this.siteSettings; this.$('.d-editor-input').autocomplete({ @@ -323,7 +323,7 @@ export default Ember.Component.extend({ if (!this.siteSettings.enable_emoji) { return; } const register = this.register; - const template = Discourse.RAW_TEMPLATES['emoji-selector-autocomplete']; + const template = this.register.lookup('template:emoji-selector-autocomplete.raw'); const self = this; $editorInput.autocomplete({ diff --git a/app/assets/javascripts/discourse/components/group-selector.js.es6 b/app/assets/javascripts/discourse/components/group-selector.js.es6 index e8cfbbc8938..4f4fa0f6a5e 100644 --- a/app/assets/javascripts/discourse/components/group-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/group-selector.js.es6 @@ -1,4 +1,6 @@ import { on, observes, default as computed } from 'ember-addons/ember-computed-decorators'; +import { getOwner } from 'discourse-common/lib/get-owner'; + export default Ember.Component.extend({ @computed('placeholderKey') placeholder(placeholderKey) { @@ -17,7 +19,7 @@ export default Ember.Component.extend({ var selectedGroups; var groupNames = this.get('groupNames'); - var template = Discourse.RAW_TEMPLATES['group-selector-autocomplete']; + var template = getOwner(this).lookup('template:group-selector-autocomplete.raw'); self.$('input').autocomplete({ allowAny: false, items: _.isArray(groupNames) ? groupNames : (Ember.isEmpty(groupNames)) ? [] : [groupNames], diff --git a/app/assets/javascripts/discourse/components/mount-widget.js.es6 b/app/assets/javascripts/discourse/components/mount-widget.js.es6 index 3c41721fe63..3c56961ff8c 100644 --- a/app/assets/javascripts/discourse/components/mount-widget.js.es6 +++ b/app/assets/javascripts/discourse/components/mount-widget.js.es6 @@ -23,6 +23,8 @@ export default Ember.Component.extend({ this._super(); const name = this.get('widget'); + (this.get('delegated') || []).forEach(m => this.set(m, m)); + this.register = getRegister(this); this._widgetClass = queryRegistry(name) || this.register.lookupFactory(`widget:${name}`); @@ -31,6 +33,7 @@ export default Ember.Component.extend({ console.error(`Error: Could not find widget: ${name}`); } + this._childEvents = []; this._connected = []; this._dispatched = []; diff --git a/app/assets/javascripts/discourse/components/plugin-connector.js.es6 b/app/assets/javascripts/discourse/components/plugin-connector.js.es6 deleted file mode 100644 index 21be7a7622c..00000000000 --- a/app/assets/javascripts/discourse/components/plugin-connector.js.es6 +++ /dev/null @@ -1,22 +0,0 @@ -export default Ember.Component.extend({ - - init() { - this._super(); - - const connector = this.get('connector'); - this.set('layoutName', connector.templateName); - - const args = this.get('args') || {}; - Object.keys(args).forEach(key => this.set(key, args[key])); - - const connectorClass = this.get('connector.connectorClass'); - connectorClass.setupComponent.call(this, args, this); - }, - - send(name, ...args) { - const connectorClass = this.get('connector.connectorClass'); - const action = connectorClass.actions[name]; - return action ? action.call(this, ...args) : this._super(name, ...args); - } - -}); diff --git a/app/assets/javascripts/discourse/components/plugin-outlet.js.es6 b/app/assets/javascripts/discourse/components/plugin-outlet.js.es6 deleted file mode 100644 index 91bf45560e9..00000000000 --- a/app/assets/javascripts/discourse/components/plugin-outlet.js.es6 +++ /dev/null @@ -1,51 +0,0 @@ -/** - A plugin outlet is an extension point for templates where other templates can - be inserted by plugins. - - ## Usage - - If your handlebars template has: - - ```handlebars - {{plugin-outlet name="evil-trout"}} - ``` - - Then any handlebars files you create in the `connectors/evil-trout` directory - will automatically be appended. For example: - - plugins/hello/assets/javascripts/discourse/templates/connectors/evil-trout/hello.hbs - - With the contents: - - ```handlebars - Hello World - ``` - - Will insert Hello World at that point in the template. - - ## Disabling - - If a plugin returns a disabled status, the outlets will not be wired up for it. - The list of disabled plugins is returned via the `Site` singleton. - -**/ -import { connectorsFor } from 'discourse/lib/plugin-connectors'; - -export default Ember.Component.extend({ - tagName: 'span', - connectors: null, - - init() { - this._super(); - const name = this.get('name'); - - if (name) { - const args = this.get('args'); - const connectors = connectorsFor(name).filter(con => { - return con.connectorClass.shouldRender(args, this); - }); - - this.set('connectors', connectors); - } - } -}); diff --git a/app/assets/javascripts/discourse/components/search-advanced-options.js.es6 b/app/assets/javascripts/discourse/components/search-advanced-options.js.es6 index 23bdac794b4..51ec9b4483b 100644 --- a/app/assets/javascripts/discourse/components/search-advanced-options.js.es6 +++ b/app/assets/javascripts/discourse/components/search-advanced-options.js.es6 @@ -48,15 +48,12 @@ export default Em.Component.extend({ init() { this._super(); - Ember.run.scheduleOnce('afterRender', () => { - this._init(); - this._update(); - }); + this._init(); + this._update(); }, @observes('searchTerm') _updateOptions() { - this._update(); Ember.run.debounce(this, this._update, 250); }, diff --git a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 index 13473c0e0f0..aeb9db8baab 100644 --- a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 @@ -1,11 +1,17 @@ import computed from 'ember-addons/ember-computed-decorators'; +import DelegatedActions from 'discourse/mixins/delegated-actions'; -export default Ember.Component.extend({ +export default Ember.Component.extend(DelegatedActions, { elementId: 'topic-footer-buttons', // Allow us to extend it layoutName: 'components/topic-footer-buttons', + init() { + this._super(); + this.delegateAll(this.get('topicDelegated')); + }, + @computed('topic.details.can_invite_to') canInviteTo(result) { return !this.site.mobileView && result; 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 c82c2f97f9d..a38dd9c8f4c 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,7 @@ import computed from 'ember-addons/ember-computed-decorators'; import { bufferedRender } from 'discourse-common/lib/buffered-render'; +import { getOwner } from 'discourse-common/lib/get-owner'; + export function showEntrance(e) { let target = $(e.target); @@ -30,7 +32,7 @@ export default Ember.Component.extend(bufferedRender({ }, buildBuffer(buffer) { - const template = Discourse.RAW_TEMPLATES['list/topic-list-item']; + const template = getOwner(this).lookup('template:list/topic-list-item.raw'); if (template) { buffer.push(template(this)); } diff --git a/app/assets/javascripts/discourse/components/topic-progress.js.es6 b/app/assets/javascripts/discourse/components/topic-progress.js.es6 index 938c7085494..85cb5209552 100644 --- a/app/assets/javascripts/discourse/components/topic-progress.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-progress.js.es6 @@ -3,11 +3,17 @@ import { default as computed, observes } from 'ember-addons/ember-computed-decor export default Ember.Component.extend({ elementId: 'topic-progress-wrapper', classNameBindings: ['docked'], + expanded: false, docked: false, progressPosition: null, postStream: Ember.computed.alias('topic.postStream'), _streamPercentage: null, + init() { + this._super(); + (this.get('delegated') || []).forEach(m => this.set(m, m)); + }, + @computed('progressPosition') jumpTopDisabled(progressPosition) { return progressPosition <= 3; diff --git a/app/assets/javascripts/discourse/components/user-selector.js.es6 b/app/assets/javascripts/discourse/components/user-selector.js.es6 index 1ca42af110d..94d6db9bfe4 100644 --- a/app/assets/javascripts/discourse/components/user-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/user-selector.js.es6 @@ -1,6 +1,8 @@ import { observes } from 'ember-addons/ember-computed-decorators'; import TextField from 'discourse/components/text-field'; import userSearch from 'discourse/lib/user-search'; +import { getOwner } from 'discourse-common/lib/get-owner'; + export default TextField.extend({ @observes('usernames') _update() { @@ -29,7 +31,7 @@ export default TextField.extend({ } this.$().val(this.get('usernames')).autocomplete({ - template: Discourse.RAW_TEMPLATES['user-selector-autocomplete'], + template: getOwner(this).lookup('template:user-selector-autocomplete.raw'), disabled: this.get('disabled'), single: this.get('single'), allowAny: this.get('allowAny'), diff --git a/app/assets/javascripts/discourse/controllers/application.js.es6 b/app/assets/javascripts/discourse/controllers/application.js.es6 index 78402acd259..dc9f45d3220 100644 --- a/app/assets/javascripts/discourse/controllers/application.js.es6 +++ b/app/assets/javascripts/discourse/controllers/application.js.es6 @@ -15,10 +15,4 @@ export default Ember.Controller.extend({ loginRequired() { return Discourse.SiteSettings.login_required && !Discourse.User.current(); }, - - actions: { - appRouteAction(name) { - return this.send(name); - } - } }); diff --git a/app/assets/javascripts/discourse/controllers/create-account.js.es6 b/app/assets/javascripts/discourse/controllers/create-account.js.es6 index 50bd0188021..ea7dee44093 100644 --- a/app/assets/javascripts/discourse/controllers/create-account.js.es6 +++ b/app/assets/javascripts/discourse/controllers/create-account.js.es6 @@ -114,7 +114,7 @@ export default Ember.Controller.extend(ModalFunctionality, { email = this.get("accountEmail"); - if (this.get('rejectedEmails').includes(email)) { + if (this.get('rejectedEmails').contains(email)) { return InputValidation.create({ failed: true, reason: I18n.t('user.email.invalid') @@ -314,7 +314,7 @@ export default Ember.Controller.extend(ModalFunctionality, { }); } - if (this.get('rejectedPasswords').includes(password)) { + if (this.get('rejectedPasswords').contains(password)) { return InputValidation.create({ failed: true, reason: I18n.t('user.password.common') diff --git a/app/assets/javascripts/discourse/controllers/tags-show.js.es6 b/app/assets/javascripts/discourse/controllers/tags-show.js.es6 index 1e8e0c2fa0b..0e8e15db508 100644 --- a/app/assets/javascripts/discourse/controllers/tags-show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/tags-show.js.es6 @@ -16,7 +16,7 @@ if (customNavItemHref) { if (navItem.get('tagId')) { var name = navItem.get('name'); - if ( !Discourse.Site.currentProp('filters').includes(name) ) { + if ( !Discourse.Site.currentProp('filters').contains(name) ) { return null; } diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 9638a25671f..4b9650fd98c 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -32,6 +32,30 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { filter: null, quoteState: null, + topicDelegated: [ + 'toggleMultiSelect', + 'deleteTopic', + 'recoverTopic', + 'toggleClosed', + 'showAutoClose', + 'showFeatureTopic', + 'showChangeTimestamp', + 'toggleArchived', + 'toggleVisibility', + 'convertToPublicTopic', + 'convertToPrivateMessage', + 'jumpTop', + 'jumpToPost', + 'jumpToPostPrompt', + 'jumpToIndex', + 'jumpBottom', + 'replyToPost', + 'toggleArchiveMessage', + 'showInvite', + 'toggleBookmark', + 'showFlagTopic' + ], + updateQueryParams() { const postStream = this.get('model.postStream'); this.setProperties(postStream.get('streamFilters')); @@ -151,22 +175,6 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { actions: { - showPostFlags(post) { - return this.send('showFlags', post); - }, - - topicRouteAction(name) { - return this.send(name); - }, - - openAutoClose() { - this.send('showAutoClose'); - }, - - openFeatureTopic() { - this.send('showFeatureTopic'); - }, - deselectText() { this.get('quoteState').setProperties({ buffer: null, postId: null }); }, @@ -810,7 +818,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { postSelected(post) { if (this.get('allPostsSelected')) { return true; } - if (this.get('selectedPosts').includes(post)) { return true; } + if (this.get('selectedPosts').contains(post)) { return true; } if (this.get('selectedReplies').findBy('post_number', post.get('reply_to_post_number'))) { return true; } return false; diff --git a/app/assets/javascripts/discourse/helpers/category-badge.js.es6 b/app/assets/javascripts/discourse/helpers/category-badge.js.es6 index 41aad35a597..74d28b745db 100644 --- a/app/assets/javascripts/discourse/helpers/category-badge.js.es6 +++ b/app/assets/javascripts/discourse/helpers/category-badge.js.es6 @@ -2,9 +2,6 @@ import { categoryLinkHTML } from 'discourse/helpers/category-link'; import { registerUnbound } from 'discourse-common/lib/helpers'; registerUnbound('category-badge', function(cat, options) { - return categoryLinkHTML(cat, { - hideParent: options.hideParent, - allowUncategorized: options.allowUncategorized, - link: false - }); + options.link = false; + return categoryLinkHTML(cat, options); }); diff --git a/app/assets/javascripts/discourse/helpers/custom-html.js.es6 b/app/assets/javascripts/discourse/helpers/custom-html.js.es6 index 1fbc94eb832..29b98c46cf6 100644 --- a/app/assets/javascripts/discourse/helpers/custom-html.js.es6 +++ b/app/assets/javascripts/discourse/helpers/custom-html.js.es6 @@ -1,3 +1,5 @@ +const { registerKeyword } = Ember.__loader.require("ember-htmlbars/keywords"); +const { internal } = Ember.__loader.require('htmlbars-runtime'); import PreloadStore from 'preload-store'; let _customizations = {}; @@ -22,3 +24,36 @@ export function clearHTMLCache() { export function setCustomHTML(key, html) { _customizations[key] = html; } + +registerKeyword('custom-html', { + setupState(state, env, scope, params) { + return { htmlKey: env.hooks.getValue(params[0]) }; + }, + + render(renderNode, env, scope, params, hash, template, inverse, visitor) { + let state = renderNode.getState(); + if (!state.htmlKey) { return true; } + + const html = getCustomHTML(state.htmlKey); + if (html) { + const htmlHash = { html }; + env.hooks.component(renderNode, + env, + scope, + 'custom-html-container', + params, + htmlHash, + { default: template, inverse }, + visitor); + return true; + } + + template = env.owner.lookup(`template:${state.htmlKey}`); + if (template) { + internal.hostBlock(renderNode, env, scope, template.raw, null, null, visitor, function(options) { + options.templates.template.yield(); + }); + } + return true; + } +}); diff --git a/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 new file mode 100644 index 00000000000..32fd7d8bae9 --- /dev/null +++ b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 @@ -0,0 +1,138 @@ +/** + A plugin outlet is an extension point for templates where other templates can + be inserted by plugins. + + ## Usage + + If your handlebars template has: + + ```handlebars + {{plugin-outlet "evil-trout"}} + ``` + + Then any handlebars files you create in the `connectors/evil-trout` directory + will automatically be appended. For example: + + plugins/hello/assets/javascripts/discourse/templates/connectors/evil-trout/hello.hbs + + With the contents: + + ```handlebars + Hello World + ``` + + Will insert Hello World at that point in the template. + + ## Disabling + + If a plugin returns a disabled status, the outlets will not be wired up for it. + The list of disabled plugins is returned via the `Site` singleton. + +**/ +let _connectorCache, _templateCache; + +function findOutlets(collection, callback) { + const disabledPlugins = Discourse.Site.currentProp('disabled_plugins') || []; + + Object.keys(collection).forEach(function(res) { + if (res.indexOf("/connectors/") !== -1) { + // Skip any disabled plugins + for (let i=0; i|
---|---|