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 { 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({
_tree: null,
_rootNode: null,
@ -22,6 +28,13 @@ export default Ember.Component.extend({
this._timeout = Ember.run.scheduleOnce('render', this, this.rerenderWidget);
},
willClearRender() {
const callbacks = _cleanCallbacks[this.get('widget')];
if (callbacks) {
callbacks.forEach(cb => cb());
}
},
willDestroyElement() {
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 { includeAttributes } from 'discourse/lib/transform-post';
import { addToolbarCallback } from 'discourse/components/d-editor';
import { addWidgetCleanCallback } from 'discourse/components/mount-widget';
let _decorateId = 0;
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
* jQuery.
*
* `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:
*
* ```
* api.decorateCooked($elem => $elem.css({ backgroundColor: 'yellow' }));
* ```
**/
decorateCooked(cb) {
addDecorator(cb);
decorate(ComposerEditor, 'previewRefreshed', cb);
decorate(this.container.lookupFactory('view:user-stream'), 'didInsertElement', cb);
decorateCooked(callback, opts) {
opts = opts || {};
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);
}
cleanupStream(fn) {
addWidgetCleanCallback('post-stream', fn);
}
}
let _pluginv01;

View File

@ -10,10 +10,11 @@ export function addDecorator(cb) {
export default class PostCooked {
constructor(attrs) {
constructor(attrs, getModel) {
this.attrs = attrs;
this.expanding = false;
this._highlighted = false;
this.getModel = getModel;
}
update(prev) {
@ -29,7 +30,7 @@ export default class PostCooked {
this._fixImageSizes($html);
this._applySearchHighlight($html);
_decorators.forEach(cb => cb($html));
_decorators.forEach(cb => cb($html, this.getModel));
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', {
buildKey: attrs => `post-contents-${attrs.id}`,
@ -240,7 +251,7 @@ createWidget('post-contents', {
},
html(attrs, state) {
const result = [new PostCooked(attrs)];
const result = [new PostCooked(attrs, new DecoratorHelper(this))];
if (attrs.cooked_hidden) {
result.push(this.attach('expand-hidden', attrs));

View File

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