From 54904133a0e9b1c53d7870d11445f387aa835b11 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 22 Feb 2016 12:10:34 -0500 Subject: [PATCH] Ability to mount ember views into vdom It's slow but can be used as a last resort for old plugins. --- .../discourse/components/mount-widget.js.es6 | 4 ++ .../discourse/lib/plugin-api.js.es6 | 14 ++++--- .../discourse/widgets/connector.js.es6 | 39 +++++++++++++++++++ .../discourse/widgets/widget.js.es6 | 30 +++++++++++--- 4 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 app/assets/javascripts/discourse/widgets/connector.js.es6 diff --git a/app/assets/javascripts/discourse/components/mount-widget.js.es6 b/app/assets/javascripts/discourse/components/mount-widget.js.es6 index 3b0c22f891b..b64625d033d 100644 --- a/app/assets/javascripts/discourse/components/mount-widget.js.es6 +++ b/app/assets/javascripts/discourse/components/mount-widget.js.es6 @@ -18,6 +18,7 @@ export default Ember.Component.extend({ init() { this._super(); this._widgetClass = this.container.lookupFactory(`widget:${this.get('widget')}`); + this._connected = []; }, didInsertElement() { @@ -33,6 +34,9 @@ export default Ember.Component.extend({ if (callbacks) { callbacks.forEach(cb => cb()); } + + this._connected.forEach(v => v.destroy()); + this._connected.length = 0; }, willDestroyElement() { diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 index a012eb55e55..49db0a29892 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 +++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 @@ -84,7 +84,9 @@ class PluginApi { * ``` **/ addPosterIcon(cb) { - decorateWidget('poster-name:after', (h, attrs) => { + decorateWidget('poster-name:after', dec => { + const attrs = dec.attrs; + const result = cb(attrs.userCustomFields || {}, attrs); if (result) { let iconBody; @@ -94,7 +96,7 @@ class PluginApi { } else if (result.emoji) { iconBody = result.emoji.split('|').map(emoji => { const src = Discourse.Emoji.urlFor(emoji); - return h('img', { className: 'emoji', attributes: { src } }); + return dec.h('img', { className: 'emoji', attributes: { src } }); }); } @@ -103,13 +105,13 @@ class PluginApi { } if (result.url) { - iconBody = h('a', { attributes: { href: result.url } }, iconBody); + iconBody = dec.h('a', { attributes: { href: result.url } }, iconBody); } - return h('span', - { className: result.className, attributes: { title: result.title } }, - iconBody); + return dec.h('span', + { className: result.className, attributes: { title: result.title } }, + iconBody); } }); } diff --git a/app/assets/javascripts/discourse/widgets/connector.js.es6 b/app/assets/javascripts/discourse/widgets/connector.js.es6 new file mode 100644 index 00000000000..c132666a412 --- /dev/null +++ b/app/assets/javascripts/discourse/widgets/connector.js.es6 @@ -0,0 +1,39 @@ +export default class Connector { + + constructor(widget, opts) { + this.widget = widget; + this.opts = opts; + } + + init() { + const $elem = $(`
`); + const elem = $elem[0]; + + const { opts, widget } = this; + Ember.run.next(() => { + + const mounted = widget._findView(); + + let context; + if (opts.context === 'model') { + const model = widget.findAncestorModel(); + context = model; + } + + const view = Ember.View.create({ + container: widget.container, + templateName: opts.templateName, + context + }); + mounted._connected.push(view); + + view.renderer.replaceIn(view, $elem[0]); + }); + + return elem; + } + + update() { } +} + +Connector.prototype.type = 'Widget'; diff --git a/app/assets/javascripts/discourse/widgets/widget.js.es6 b/app/assets/javascripts/discourse/widgets/widget.js.es6 index 56c025fde50..680a7b59458 100644 --- a/app/assets/javascripts/discourse/widgets/widget.js.es6 +++ b/app/assets/javascripts/discourse/widgets/widget.js.es6 @@ -1,5 +1,6 @@ import { WidgetClickHook, WidgetClickOutsideHook } from 'discourse/widgets/click-hook'; import { h } from 'virtual-dom'; +import Connector from 'discourse/widgets/connector'; function emptyContent() { } @@ -16,14 +17,33 @@ export function renderedKey(key) { const _decorators = {}; +class DecoratorHelper { + constructor(container, attrs, state) { + this.container = container; + this.attrs = attrs; + this.state = state; + } + + connect(details) { + return new Connector(this.container, details); + } +} +DecoratorHelper.prototype.h = h; + export function decorateWidget(widgetName, cb) { _decorators[widgetName] = _decorators[widgetName] || []; _decorators[widgetName].push(cb); } -function applyDecorators(name, type, attrs, state) { - const decorators = _decorators[`${name}:${type}`] || []; - return decorators.map(d => d(h, attrs, state)); +function applyDecorators(widget, type, attrs, state) { + const decorators = _decorators[`${widget.name}:${type}`] || []; + + if (decorators.length) { + const helper = new DecoratorHelper(widget, attrs, state); + return decorators.map(d => d(helper)); + } + + return []; } function drawWidget(builder, attrs, state) { @@ -58,8 +78,8 @@ function drawWidget(builder, attrs, state) { let contents = this.html(attrs, state); if (this.name) { - const beforeContents = applyDecorators(this.name, 'before', attrs, state) || []; - const afterContents = applyDecorators(this.name, 'after', attrs, state) || []; + const beforeContents = applyDecorators(this, 'before', attrs, state) || []; + const afterContents = applyDecorators(this, 'after', attrs, state) || []; contents = beforeContents.concat(contents).concat(afterContents); }