A Simple Discourse Plugin

tutorial

#1

That's "simple" as in "doesn't do much".
The main intent of this tutorial is to help those unfamiliar with writing a Discourse plugin.

Background
Some Discourse template files now have "plugin outlets" to make things easier for plugin authors.
This tutorial will show how to write a plugin that allows Admins to add content to the hamburger's list of site links.
The core template file is located at
app/assets/javascripts/discourse/templates/site-map.hbs
and has the outlet
{{plugin-outlet "site-map-links"}}
inside of UL tags, but not inside of LI tags.

Files
There are 2 required files.
The plugin.rb file which is needed to let Discourse know where to get needed assets.
The handlebars template file which is the point of this plugin.

There are also 4 "good to have" files.
3 config files that allow plugin users to enable / disable the plugin, and alter the inserted content without needing to hack the template file with hard-coded content.
A readme that hopefully will help any that may have problems.

Comments
Files are not required to have comments in them, but they are important.

Naming
It is a good idea to have descriptive names. Except for when required for Discourse to do it's "magic" eg. "plugin.rb", I prefer to use dashes in folder and file names, and underscores in variable and function names.
But there is usually a lot of leeway here if you prefer otherwise.
For simplicity and lack of imagination, I named this plugin "below-sitemap" even though technically it should probably be "in-sitemap-links".

File and Folder Location
As mentioned the core template is located at
app/assets/javascripts/discourse/templates/site-map.hbs
For the plugin to work, this path is somewhat replicated within the plugin's folder except there needs to be a "connectors" folder inside of the templates folder with a "site-map-links" (the plugin-outlet name) inside of that where the plugin's handlebars file will go.

The structure is as so

below-sitemap
  plugin.rb			// main plugin file
  /assets
    /javascripts
      /discourse
        /templates
          /connectors
            /site-map-links 	// same name as the plugin-outlet
              site-map-links.hbs	// inserted into site links
  /config
    settings.yml		// variable objects
    /locales
      client.en.yml		// adds inputs to ACP UI
      server.en.yml		// variable values
  README.md			// helpful information

plugin.rb
Not much here, but the path needs to be correct

# name: below-sitemap
# about: plugin to insert content into hamburger menu link list
# version: 0.1
# authors: Mittineague


register_asset "javascripts/discourse/templates/connectors/site-map-links/site-map-links.hbs"

* YAML files are extremely sensitive to whitespace (spaces, indentation, newlines) so it's a good idea to run them through a validator

settings.yml
Adds two Admin inputs, a boolean and a text

# below-sitemap

plugins:
  below_sitemap_active:
    default: false
    client: true
  below_sitemap_content:
    default: ''
    client: ''

client.en.yml
Lets Discourse know where to put the site setting inputs.
In this case, Admin -> Settings -> Plugins and Admin -> Plugins -> Settings

# below-sitemap

en:
  admin:
    site_settings:
      categories:
         plugins: "Below Sitemap"

server.en.yml
Text to show next to the inputs

# below-sitemap

en:
  site_settings:
    below_sitemap_active: "Show Content in Hambuger Menu"
    below_sitemap_content: "Content to show in Hambuger Menu. Individual items need to be enclosed in LI tags."

site-map-links.hbs
If below_sitemap_active is checked, show below_sitemap_content

{{#if Discourse.SiteSettings.below_sitemap_active}}
	{{{Discourse.SiteSettings.below_sitemap_content}}}
{{/if}}

A not so simple Discourse Plugin - Handlebars.registerHelper
#2

As an example, these Settings

result in this (hover on Home link)


#3

I would alter one thing. Name your ReadMe, README.md and place it in the root folder. That way if you ever import your plugin to GitHub, your repo has a README file that it will display by default when visiting the repo page. smile

Also, how did you discover the need to have a "connectors" folder?


#4

It's old (started May 2014) so I don't know what or how much is likely to break soon, but I've been installing old plugins and reading topics and if they work studying them and trying bits and pieces.

One thing I need to do is rework the client.en.yml file. as I copied that from an older plugin that works, but isn't quite how it should be. I'm working on that now. EDIT now improved

Excellent point. Edited

I've also taken a look at how to put it into a GEM, but that looks like it might take me a while.


#5

Also, I'm a bit torn on "Activate Settings". If you install it, obviously you want it. If you don't want it, uninstall it. Seems silly to have to install it and then go activate it.

Of course there is the "Well what if you additional data is needed for the plugin to work properly?", to which I would respond, use that data to activate the plugin.

Example: (https://github.com/cpradio/composer-help-button)

The plugin requires a URL to associate with the new toolbar button, so once the URL is entered, the button becomes available on the composer window toolbar.


#6

For the default inactive, I tried default active - but - with no content the menu had a blank space and with default content that showed in the list.

So I figured since it was necessary to go to the settings to enter content it could be activated while there.

It is possible that I've made changes since I tested that, I'll look into it ASAP

AFAIK the "reopen" is needed if there is no plugin outlet and Sam has been adding those into templates when asked on an as-needed basis, which I take hasn't been all that often yet.

I do want to see if I can create a "new" page and / or modal, and I'm fairly certain that will need a reopen


#7

Although the intent of this topic is to illustrate a way to create a plugin to add content to the hamburger sitemap links, I should add for the benefit of those that are not looking to write a plugin to do this, the "easy" way to do this is to add something like

<script type='text/x-handlebars' data-template-name='/connectors/site-map-links/shop'>
<li><a href="http://www.example.com/shop/" title="Some Amazing Club Shop" class="shop-link" target="_blank">Shop</a></li>
</script>

to Admin -> Customize -> CSS/HTML -> </head>


#8

Continued


#9

I have since discovered that if
# name:
# version:
are not included, the plugin will break the Admin Plugins page resulting in a nasty "Problem" message.

If
# url:
is specified, a link to the plugin's "home" page will be on the Admin Plugins page

AFAIK, without further testing, not having
# about:
# authors:
will not break anything


#10

I'm not sure what happened (Discourse has been making frequent changes. eg. upgrading Ember versions, changing from JS to ES6, Handlebars to HTMLbars, etc.) but this plugin stopped working and was only inserting comments.

To get "Home" to show again I needed to change

to

register_asset "javascripts/discourse/templates/connectors/site-map-links/site-map-links.hbs", :server_side

But Chrome Inspector is showing broken mark-up

<ul class="location-links">
        <li>
<a id="ember1956" class="ember-view admin-link" href="/admin">
            <i class="fa fa-wrench"></i> Admin
</a>        </li>
        <li>
<a id="ember1957" class="ember-view flagged-posts-link" href="/admin/flags">
            <i class="fa fa-flag"></i> Flags
<!----></a>        </li>
      <li>
<a id="ember1948" class="ember-view latest-topics-link active" href="/">          Latest
</a>      </li>
        <li>
          <a class="badge-link" href="/badges">Badges</a>
        </li>
  
        <li><a id="ember2004" class="ember-view" href="/users">Users</a></li>
  
<!---->  
      <div id="ember1953" class="ember-view site-map-links-outlet site-map-links">
  	<li><a href="http://localhost:4000">Home</a></li>
</div>
  
        <li><a href="" class="keyboard-shortcuts-link" data-ember-action="2022">Keyboard Shortcuts</a></li>
      <li>
        <a class="faq-link" href="/faq">FAQ</a>
      </li>
      <li>
        <a id="ember1954" class="ember-view" href="/about">About</a>
      </li>
<!---->    </ul>

#11

Complete source code for this - slightly modified - (and more) is available at

https://github.com/Mittineague/discourse-plugin-outlet-locations


#13

That's a safety feature so that in case someone mistakenly installs it in other than a development server they can quickly disable it.

True. it should be obvious that it's for development only, but experience has shown that someone will eventually miss the obvious.

Imagine installing it and having every visitor seeing this

IMHO it isn't that difficult to check one checkbox, and it's a lot easier than checking / unchecking the ~40 individual ones.


#14

I know why it exists, but it has very limited use. Most plugins aren't going to try and write something to every outlet. They are going to add some sort of enhancement that the user is expecting. The big issue I have with activate/deactivate is it can technically still break your Discourse instance even if they deactivate it again. As everything is still loaded and executed. So if your code isn't up-to-date with the latest changes in Discourse, you are still going to potentially break that instance.

It should really behave as "loaded/unloaded", where unloaded doesn't execute the code at all.


#15

Yes, I brought this up at meta. It's something they have "thought about".
I guess it hasn't become a big enough problem to devote much time to yet.

WordPress plugins can be

  • Installed
  • Activated
  • Deactivated
  • Uninstalled

When an attempt to activate a buggy WordPress plugin is made, the page will display an error message indicating the problem and the activation is aborted.

It would be nice if Discourse did similar. Not to check logic errors, but at least for code that could break the forum.

On a positive note, I am very pleased to see how the plugin_store_rows table can be used to hold plugin data separate from the other Discourse tables.

This will allow plugins to provide a way to delete records associated with them prior to uninstalling them. eg.
... WHERE plugin_store_rows.plugin_name LIKE 'whatever'
and maybe even a way for Discourse to check if a plugin is not installed and the records are stale and likely orphaned by using the created_at date stored in plugin_store_rows.value


#16

Yeah, the plugin store is nice. I haven't found a use for utilizing it yet, but one day, maybe I will.


closed #17

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.