Update Polls plugin to work with new Plugin API

This commit is contained in:
Robin Ward
2016-02-18 15:17:53 -05:00
parent f6aa1ac37a
commit 6935925f10
5 changed files with 136 additions and 95 deletions

View File

@ -2,6 +2,12 @@ import { diff, patch } from 'virtual-dom';
import { WidgetClickHook } from 'discourse/widgets/click-hook'; import { WidgetClickHook } from 'discourse/widgets/click-hook';
import { renderedKey } from 'discourse/widgets/widget'; import { renderedKey } from 'discourse/widgets/widget';
const _cleanCallbacks = {};
export function addWidgetCleanCallback(widgetName, fn) {
_cleanCallbacks[widgetName] = _cleanCallbacks[widgetName] || [];
_cleanCallbacks[widgetName].push(fn);
}
export default Ember.Component.extend({ export default Ember.Component.extend({
_tree: null, _tree: null,
_rootNode: null, _rootNode: null,
@ -22,6 +28,13 @@ export default Ember.Component.extend({
this._timeout = Ember.run.scheduleOnce('render', this, this.rerenderWidget); this._timeout = Ember.run.scheduleOnce('render', this, this.rerenderWidget);
}, },
willClearRender() {
const callbacks = _cleanCallbacks[this.get('widget')];
if (callbacks) {
callbacks.forEach(cb => cb());
}
},
willDestroyElement() { willDestroyElement() {
Ember.run.cancel(this._timeout); Ember.run.cancel(this._timeout);
}, },

View File

@ -4,6 +4,7 @@ import { addPosterIcon } from 'discourse/widgets/poster-name';
import { addButton } from 'discourse/widgets/post-menu'; import { addButton } from 'discourse/widgets/post-menu';
import { includeAttributes } from 'discourse/lib/transform-post'; import { includeAttributes } from 'discourse/lib/transform-post';
import { addToolbarCallback } from 'discourse/components/d-editor'; import { addToolbarCallback } from 'discourse/components/d-editor';
import { addWidgetCleanCallback } from 'discourse/components/mount-widget';
let _decorateId = 0; let _decorateId = 0;
function decorate(klass, evt, cb) { function decorate(klass, evt, cb) {
@ -29,23 +30,31 @@ class PluginApi {
} }
/** /**
* decorateCooked(callback) * decorateCooked(callback, options)
* *
* Used for decorating the `cooked` content of a post after it is rendered using * Used for decorating the `cooked` content of a post after it is rendered using
* jQuery. * jQuery.
* *
* `callback` will be called when it is time to decorate with a jQuery selector. * `callback` will be called when it is time to decorate with a jQuery selector.
* *
* Use `options.onlyStream` if you only want to decorate posts within a topic,
* and not in other places like the user stream.
*
* For example, to add a yellow background to all posts you could do this: * For example, to add a yellow background to all posts you could do this:
* *
* ``` * ```
* api.decorateCooked($elem => $elem.css({ backgroundColor: 'yellow' })); * api.decorateCooked($elem => $elem.css({ backgroundColor: 'yellow' }));
* ``` * ```
**/ **/
decorateCooked(cb) { decorateCooked(callback, opts) {
addDecorator(cb); opts = opts || {};
decorate(ComposerEditor, 'previewRefreshed', cb);
decorate(this.container.lookupFactory('view:user-stream'), 'didInsertElement', cb); addDecorator(callback);
if (!opts.onlyStream) {
decorate(ComposerEditor, 'previewRefreshed', callback);
decorate(this.container.lookupFactory('view:user-stream'), 'didInsertElement', callback);
}
} }
/** /**
@ -91,6 +100,10 @@ class PluginApi {
addToolbarCallback(callback); addToolbarCallback(callback);
} }
cleanupStream(fn) {
addWidgetCleanCallback('post-stream', fn);
}
} }
let _pluginv01; let _pluginv01;

View File

@ -10,10 +10,11 @@ export function addDecorator(cb) {
export default class PostCooked { export default class PostCooked {
constructor(attrs) { constructor(attrs, getModel) {
this.attrs = attrs; this.attrs = attrs;
this.expanding = false; this.expanding = false;
this._highlighted = false; this._highlighted = false;
this.getModel = getModel;
} }
update(prev) { update(prev) {
@ -29,7 +30,7 @@ export default class PostCooked {
this._fixImageSizes($html); this._fixImageSizes($html);
this._applySearchHighlight($html); this._applySearchHighlight($html);
_decorators.forEach(cb => cb($html)); _decorators.forEach(cb => cb($html, this.getModel));
return $html[0]; return $html[0];
} }

View File

@ -221,6 +221,17 @@ createWidget('expand-post-button', {
} }
}); });
class DecoratorHelper {
constructor(widget) {
this.container = widget.container;
this._widget = widget;
}
getModel() {
return this._widget.findAncestorModel();
}
}
createWidget('post-contents', { createWidget('post-contents', {
buildKey: attrs => `post-contents-${attrs.id}`, buildKey: attrs => `post-contents-${attrs.id}`,
@ -240,7 +251,7 @@ createWidget('post-contents', {
}, },
html(attrs, state) { html(attrs, state) {
const result = [new PostCooked(attrs)]; const result = [new PostCooked(attrs, new DecoratorHelper(this))];
if (attrs.cooked_hidden) { if (attrs.cooked_hidden) {
result.push(this.attach('expand-hidden', attrs)); result.push(this.attach('expand-hidden', attrs));

View File

@ -1,12 +1,8 @@
import PostView from "discourse/views/post"; import { withPluginApi } from 'discourse/lib/plugin-api';
import TopicController from "discourse/controllers/topic";
import Post from "discourse/models/post";
import { on } from "ember-addons/ember-computed-decorators";
function createPollView(container, post, poll, vote) { function createPollView(container, post, poll, vote) {
const controller = container.lookup("controller:poll", { singleton: false }), const controller = container.lookup("controller:poll", { singleton: false });
view = container.lookup("view:poll"); const view = container.lookup("view:poll");
controller.set("vote", vote); controller.set("vote", vote);
controller.setProperties({ model: poll, post }); controller.setProperties({ model: poll, post });
@ -15,12 +11,32 @@ function createPollView(container, post, poll, vote) {
return view; return view;
} }
export default { let _pollViews;
name: "extend-for-poll",
initialize(container) { function initializePolls(api) {
const TopicController = api.container.lookupFactory('controller:topic');
TopicController.reopen({
subscribe(){
this._super();
this.messageBus.subscribe("/polls/" + this.get("model.id"), msg => {
const post = this.get('model.postStream').findLoadedPost(msg.post_id);
if (post) {
post.set('polls', msg.polls);
}
});
},
unsubscribe(){
this.messageBus.unsubscribe('/polls/*');
this._super();
}
});
const Post = api.container.lookupFactory('model:post');
Post.reopen({ Post.reopen({
_polls: null,
pollsObject: null,
// we need a proper ember object so it is bindable // we need a proper ember object so it is bindable
pollsChanged: function(){ pollsChanged: function(){
const polls = this.get("polls"); const polls = this.get("polls");
@ -39,64 +55,51 @@ export default {
}.observes("polls") }.observes("polls")
}); });
TopicController.reopen({ function cleanUpPollViews() {
subscribe(){ if (_pollViews) {
this._super(); Object.keys(_pollViews).forEach(pollName => _pollViews[pollName].destroy());
this.messageBus.subscribe("/polls/" + this.get("model.id"), msg => {
const post = this.get('model.postStream').findLoadedPost(msg.post_id);
if (post) {
post.set('polls', msg.polls);
} }
}); _pollViews = null;
},
unsubscribe(){
this.messageBus.unsubscribe('/polls/*');
this._super();
} }
});
// overwrite polls function createPollViews($elem, helper) {
PostView.reopen({ const $polls = $('.poll', $elem);
if (!$polls.length) { return; }
@on("postViewInserted", "postViewUpdated") const post = helper.getModel();
_createPollViews($post) { const votes = post.get('polls_votes') || {};
const post = this.get("post"),
votes = post.get("polls_votes") || {};
post.pollsChanged(); post.pollsChanged();
const polls = post.get("pollsObject");
// don't even bother when there's no poll const polls = post.get("pollsObject");
if (!polls) { return; } if (!polls) { return; }
// TODO inject cleanly into cleanUpPollViews();
const postPollViews = {};
// clean-up if needed $polls.each((idx, pollElem) => {
this._cleanUpPollViews(); const $div = $("<div>");
const $poll = $(pollElem);
const pollViews = {}; const pollName = $poll.data("poll-name");
const pollView = createPollView(helper.container, post, polls[pollName], votes[pollName]);
// iterate over all polls
$(".poll", $post).each(function() {
const $div = $("<div>"),
$poll = $(this),
pollName = $poll.data("poll-name"),
pollView = createPollView(container, post, polls[pollName], votes[pollName]);
$poll.replaceWith($div); $poll.replaceWith($div);
Em.run.next(() => pollView.renderer.replaceIn(pollView, $div[0])); Em.run.next(() => pollView.renderer.replaceIn(pollView, $div[0]));
pollViews[pollName] = pollView; postPollViews[pollName] = pollView;
}); });
this.set("pollViews", pollViews); _pollViews = postPollViews;
}, }
@on("willClearRender") api.decorateCooked(createPollViews, { onlyStream: true });
_cleanUpPollViews() { api.cleanupStream(cleanUpPollViews);
if (this.get("pollViews")) {
_.forEach(this.get("pollViews"), v => v.destroy());
} }
}
}); export default {
name: "extend-for-poll",
initialize() {
withPluginApi('0.1', initializePolls);
} }
}; };