Making Discourse Geo-Aware

A story about how adding a banner takes a full day of developer time

Making Discourse Geo-Aware

A client whose community I started managing a while ago has locations in two countries. He wanted a banner at the top that linked to his main site, where he makes money by selling stuff. No problem! The custom header links theme component makes it easy to insert custom header links. Oh, but it works only for text links, not images. So I forked that theme component, and added the ability to add a class to the link as well as a URL for an image that is displayed rather than just text. That got us an image in the header with a link to the primary site. Hooray!

Next, though, I needed to be able to add two images and links to their respective sites and have only one of them display depending on the location of the user viewing the site. Discourse comes with support for Maxmind reverse IP Lookups, but those lookups are available only for admins. There is also a Geo Blocking Plugin, but that’s just for . . . blocking. Wait!! The Locations Plugin!. . . No, it’s just for adding locations to topics, not users.

Taking matters into my own hands

Since none of the off-the-shelf solutions sufficed, I created the Discourse Geo Customization Plugin. This plugin adds the Maxmind IP lookup record to the currentUser serializer (that is, it sends it out from Rails to the Ember front end) and then optionally adds the country-code and city as classes to the <body> tag. With this plugin you can use body.country-US to display or hide elements anywhere on the site! It even has a setting that you can use to override the actual IP address of admin users with another address so you can test how things will look in different countries without getting on a plane or using a VPN.

A problem arose when the image they wanted to use was too big for mobile. I had initially used the Header Links to have the link displayed only on Desktop, which worked fine. Sadly, when using different images for Desktop and Mobile, they both displayed because the logic that un-hides the element for Desktop or Mobile, also un-hides it for all of the locations. I imagine that someone who is really clever with CSS or has access to an AI could figure out a way to solve that problem, but this project was already significantly over budget and there was still another part left to go. The client settled on using just one smaller image link for both Desktop and Mobile.

Adding a Banner

The other thing that they really wanted was a geo-specific banner and corresponding link to display under the first post of every topic. My solution was to use the House Ads feature of the Discourse Ad Plugin to display the ad. It didn’t make it possible to put them exactly under the first post, but instead every N posts. This might have been good enough. I provided an example ad that would display for the proper location that needed just an <a href...> with an <img> under the right <div>. I asked for the banner, expecting a jpg or png but instead got a zip file with a few images and an index.html file. (In retrospect, this is when this task went from what I expected to something much, much harder.) When I opened the file on my browser, it was a garbled mess. I managed to solve the garbled mess issue by disabling Privacy Badger, allowing their HTML to load a remote script that animated the banner. But this still wasn’t something that was going to be easy to apply to Discourse.

The html file included a bunch of javascript, css, and a bit of regular old html. I tried for over an hour to coerce it into a Ember Glimmer component (see this meta.discourse.org topic for an example), but wasn’t getting very far. I reached out to a user on Discourse Meta who had been very helpful with another similar problem just to ask how long he thought such a task might take him. He estimated an hour and a half, and then, to my surprise, offered to do it because “It’s a good exercise for me.” Three and a half hours later, he came back with a solution. This was fantastic! He had converted the mess into a theme component that would display the banner. He said it had taken him about 2.5 hours. I guess he stopped for lunch.

Adding a geo-aware banner!

The zip file I had been given didn’t include just one animated banner that linked to the sales site, but two banners, customized for the appropriate country. With the hard part of making it into a component done, I could then go about modifying the code to display the correct images (the brand logo was an SVG file embedded into the HTML), words, and links depending on the location. Another couple hours later, I’d figured out a few tricks I didn’t know and managed to get the javascript to look at the currentUser, pull some text and the links out of some theme settings, and insert the banner. Now when I change my IP address with the Geo Customization Plugin, I get the animated right banner, linking to the right web site!

Also adding the banner under the first post

Having the banner display under the first post of a topic seems like a reasonable request. Discourse has plugin outlets, that make it easy to insert stuff in a template. There’s a clever theme component that will show you where they are. As it turns out, there isn’t one between posts in a topic. I asked on Meta for possible solutions. As a proof of concept I had managed, for example, to insert some text in the <article>’s innerHTML, so that seemed like a possibility, and quite an accomplishment for me. That works only for some text, though, not my Glimmer component. About ten minutes later, one of the Discourse developers had responded that they could just add a plugin outlet there, so my problem would be solved. Half an hour after that, a pull request was created! That wasn’t the end of the story, however. Two days, 3 commits, and a bunch of changes to 7 files later, I was still waiting. It turned out that the change wasn’t as easy as the Discourse developer had thought, and they gave up providing me with that solution.

Fortunately, I found another way to attack the problem. I was able to call api.decorateWidget("post:after") and target just the first post and attach my banner widget, like this:

    registerWidgetShim(
      "geo-banner-widget",
      "div.widget-connector",
      hbs`<Banner/>`
    );

This little project ended up taking much, much longer than I would have imagined. Often when this happens, I can look back and think “well, had I known x, y, and z, which should be pretty obvious to a Competent Developer, it would have taken about as long as I expected. This time, however, it still seems pretty hard even in hindsight.

Lessons Learned

Sometimes, an estimate is really just a wild guess. I generally err on the side of doing all of the work before I give them an estimate—that way I know for sure that I can do the job for the amount I’ve given. Sometimes, I find things that would make things much better, like last week’s Affiliate Linker theme component. Of course, the problem with that is doing thousands of dollars of work only to find out that they don’t really want it done, or don’t really have the budget that they thought, or they just stop responding to email. That has not happened that many times, but also, I am now good enough at this front end stuff that I should be able to provide an estimate before doing all of the work.

Do you have ideas for using Geo-location to make your Discourse site more attractive to your community? Got a wild idea for a plugin or theme component? If so, send me an email,a PM from our dashboard, or use the contact page here. I’d love to hear about it!

You can see the code for the Geo Customization Plugin over on github.

Cookies
essential