From 05a0308663e831d22ddd7c169d1c4688c3cd0954 Mon Sep 17 00:00:00 2001
From: Robin Ward <robin.ward@gmail.com>
Date: Tue, 23 Feb 2016 12:44:52 -0500
Subject: [PATCH] Finish documenting new PluginAPI

---
 .../discourse/lib/plugin-api.js.es6           | 122 ++++++++++++++++--
 .../discourse/widgets/decorator-helper.js.es6 |  68 +++++++++-
 2 files changed, 174 insertions(+), 16 deletions(-)

diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6
index 3f1ec4b5f..1e74ae397 100644
--- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6
+++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6
@@ -8,17 +8,6 @@ import { addWidgetCleanCallback } from 'discourse/components/mount-widget';
 import { decorateWidget } from 'discourse/widgets/widget';
 import { onPageChange } from 'discourse/lib/page-tracker';
 
-let _decorateId = 0;
-function decorate(klass, evt, cb) {
-  const mixin = {};
-  mixin["_decorate_" + (_decorateId++)] = function($elem) { cb($elem); }.on(evt);
-  klass.reopen(mixin);
-}
-
-export function decorateCooked() {
-  console.warn('`decorateCooked` has been removed. Use `getPluginApi(version).decorateCooked` instead');
-}
-
 class PluginApi {
   constructor(version, container) {
     this.version = version;
@@ -26,13 +15,15 @@ class PluginApi {
     this._currentUser = container.lookup('current-user:main');
   }
 
+  /**
+   * Use this function to retrieve the currently logged in user within your plugin.
+   * If the user is not logged in, it will be `null`.
+  **/
   getCurrentUser() {
     return this._currentUser;
   }
 
   /**
-   * decorateCooked(callback, options)
-   *
    * Used for decorating the `cooked` content of a post after it is rendered using
    * jQuery.
    *
@@ -115,27 +106,121 @@ class PluginApi {
     });
   }
 
+  /**
+   * The main interface for extending widgets with additional HTML.
+   *
+   * The `name` you pass it should be the name of the widget and a type
+   * for the decorator. All widgets support `before` and `after` types.
+   *
+   * Example:
+   *
+   * ```
+   * api.decorateWidget('post:after', () => {
+   *   return "I am displayed after every post!";
+   * });
+   * ```
+   *
+   * Your decorator will be called with an instance of a `DecoratorHelper`
+   * object, which provides methods you can use to build more interesting
+   * formatting.
+   *
+   * ```
+   * api.decorateWidget('post:after', helper => {
+   *   return helper.h('p.fancy', `I'm an HTML paragraph on post with id ${helper.attrs.id}`);
+   * });
+   *
+   * (View the source for `DecoratorHelper` for more helper methods you
+   * can use in your plugin decorators.)
+   *
+   **/
   decorateWidget(name, fn) {
     decorateWidget(name, fn);
   }
 
+  /**
+   * Adds a new action to a widget that already exists. You can use this to
+   * add additional functionality from your plugin.
+   *
+   * Example:
+   *
+   * ```
+   * api.attachWidgetAction('post', 'annoyMe', () => {
+   *  alert('ANNOYED!');
+   * });
+   * ```
+   **/
   attachWidgetAction(widget, actionName, fn) {
     const widgetClass = this.container.lookupFactory(`widget:${widget}`);
     widgetClass.prototype[actionName] = fn;
   }
 
+  /**
+   * Add more attributes to the Post's `attrs` object passed through to widgets.
+   * You'll need to do this if you've added attributes to the serializer for a
+   * Post and want to use them when you're rendering.
+   *
+   * Example:
+   *
+   * ```
+   * // attrs.poster_age and attrs.poster_height will be present
+   * api.includePostAttributes('poster_age', 'poster_height');
+   * ```
+   *
+   **/
   includePostAttributes(...attributes) {
     includeAttributes(...attributes);
   }
 
+  /**
+   * Add a new button below a post with your plugin.
+   *
+   * The `callback` function will be called whenever the post menu is rendered,
+   * and if you return an object with the button details it will be rendered.
+   *
+   * Example:
+   *
+   * ```
+   * api.addPostMenuButton('coffee', () => {
+   *   return {
+   *     action: 'drinkCoffee',
+   *     icon: 'coffee',
+   *     className: 'hot-coffee',
+   *     title: 'coffee.title',
+   *     position: 'first'  // can be `first`, `last` or `second-last-hidden`
+   *   };
+   * });
+   **/
   addPostMenuButton(name, callback) {
     addButton(name, callback);
   }
 
+  /**
+   * A hook that is called when the editor toolbar is created. You can
+   * use this to add custom editor buttons.
+   *
+   * Example:
+   *
+   * ```
+   * api.onToolbarCreate(toolbar => {
+   *   toolbar.addButton({
+   *     id: 'pop-text',
+   *     group: 'extras',
+   *     icon: 'bolt',
+   *     action: 'makeItPop',
+   *     title: 'pop_format.title'
+   *   });
+   * });
+   **/
   onToolbarCreate(callback) {
     addToolbarCallback(callback);
   }
 
+  /**
+   * A hook that is called when the post stream is removed from the DOM.
+   * This advanced hook should be used if you end up wiring up any
+   * events that need to be torn down when the user leaves the topic
+   * page.
+   **/
   cleanupStream(fn) {
     addWidgetCleanCallback('post-stream', fn);
   }
@@ -185,3 +270,14 @@ export function withPluginApi(version, apiCodeCallback, opts) {
     return apiCodeCallback(api);
   }
 }
+
+let _decorateId = 0;
+function decorate(klass, evt, cb) {
+  const mixin = {};
+  mixin["_decorate_" + (_decorateId++)] = function($elem) { cb($elem); }.on(evt);
+  klass.reopen(mixin);
+}
+
+export function decorateCooked() {
+  console.warn('`decorateCooked` has been removed. Use `getPluginApi(version).decorateCooked` instead');
+}
diff --git a/app/assets/javascripts/discourse/widgets/decorator-helper.js.es6 b/app/assets/javascripts/discourse/widgets/decorator-helper.js.es6
index 3448a660f..9410bc4d8 100644
--- a/app/assets/javascripts/discourse/widgets/decorator-helper.js.es6
+++ b/app/assets/javascripts/discourse/widgets/decorator-helper.js.es6
@@ -10,21 +10,83 @@ class DecoratorHelper {
     this.state = state;
   }
 
-  connect(details) {
-    return new Connector(this.widget, details);
-  }
+  /**
+   * The `h` helper allows you to build up a virtual dom easily.
+   *
+   * Example:
+   *
+   * ```
+   * // renders `<div class='some-class'><p>paragraph</p></div>`
+   * return helper.h('div.some-class', helper.h('p', 'paragraph'));
+   * ```
+   **/
+  // h() is attached via `prototype` below
 
+  /**
+   * Returns the model associated with this widget. When decorating
+   * posts this will normally be the post.
+   *
+   * Example:
+   *
+   * ```
+   * const post = helper.getModel();
+   * console.log(post.get('id'));
+   * ```
   getModel() {
     return this.widget.findAncestorModel();
   }
 
+  /**
+   * If your decorator must produce raw HTML, you can use this helper
+   * to display it. It is preferred to use the `h` helper and create
+   * the HTML yourself whenever possible.
+   *
+   * Example:
+   *
+   * ```
+   * return helper.rawHtml(`<p>I will be displayed</p`);
+   * ```
+   **/
   rawHtml(html) {
     return new RawHtml({ html });
   }
 
+  /**
+   * Renders `cooked` content using all the helpers and decorators that
+   * are attached to that. This is useful if you want to render a post's
+   * content or a different version of it.
+   *
+   * Example:
+   *
+   * ```
+   * return helper.cooked(`<p>Cook me</p>`);
+   * ```
+   **/
   cooked(cooked) {
     return new PostCooked({ cooked });
   }
+
+  /**
+   * You can use this bridge to mount an Ember View inside the virtual
+   * DOM post stream. Note that this is a bit bizarre, as our core app
+   * is rendered in Ember, then we switch to a virtual dom, and this
+   * allows part of that virtual dom to use Ember again!
+   *
+   * It really only exists as backwards compatibility for some old
+   * plugins that would be difficult to update otherwise. There are
+   * performance reasons not to use this, so be careful and avoid
+   * using it whenever possible.
+   *
+   * Example:
+   *
+   * ```
+   * helper.connect({ templateName: 'my-handlebars-template' });
+   * ```
+   **/
+  connect(details) {
+    return new Connector(this.widget, details);
+  }
+
 }
 DecoratorHelper.prototype.h = h;