Plugin Help -- Making a custom bbcode tag


(Jacob Bridges) #1

Hi Guys!

So I am trying (failing) to make a plugin for discourse which transforms a custom bbcode tag into an anchor element with a “data-id” attribute. For example:

[mtg]stuffy doll[/mtg]  -->  <a data-id="279711">stuffy doll</a>

The data-id attribute is coming from MtG’s API here: http://api.mtgdb.info/. I have written a function for getting the ID of a card based on the name–but I’m just not certain how the replaceBlock function works. (Really wish there was a ReadTheDocs or something for Discourse…)

This is what I have so far: https://github.com/jacobbridges/discourse-gatherer
And this is the code in my assets/javascripts which is failing:

function Find_MtG_Image( query ) {
	get_card_uri = "http://api.mtgdb.info/cards/";
	// URL encode the query
	encoded_query = encodeURIComponent( query );

	// Query MtG API for the card with name like "query"
	$.getJSON( get_card_uri + encoded_query, function( data ) {
  		var card_ids = [];
         	$.each( data, function( key, obj ) {
			card_ids.push( obj.id );
	        });

	       // Get the last card id in the list
	       // (Most recent version of the card they are searching for.)
	        var card_id = card_ids.pop()
         });

         return card_id;
}

Discourse.Dialect.replaceBlock({
	start: /(\[mtg\])([\s\S]*)/igm,
	stop: '[/mtg]',

	emitter: function(blockContents) {
		console.log(blockContents);
		var response = '<a data="'+ Find_MtG_Image(blockContents) +'">'+ blockContents +'</a>';
		return response;
	}
});

Any help would be appreciated! I think I am using the Discourse.Dialect.replaceBlock function incorrectly–but I couldn’t find any documentation on it so that would be expected.


(Kane York) #2

This is wrong, I think.

Try this:

Discourse.Dialect.inlineBetween({
  start: '[mtg]',
  stop: '[/mtg]',
  rawContents: true,

  emitter: function(mdtext) {
    // don't process Markdown inside of [mtg]
    return ['a', {"data-mtg-id": Find_MtG_Image(mdtext)}, mdtext];
    // do process Markdown inside of [mtg]
    //return ['a', {"data-mtg-id": Find_MtG_Image(mdtext)}, Discourse.Dialect.processInline(mdtext)];
  }
});

Also:

Discourse.PostView.reopen({
  processGathererLinks: function() {
    var $post = this.$();
    debugger;
  }.on('postViewInserted')
});

(Jacob Bridges) #3

Thanks for the response riking. I really was trying to solve this myself but the code is a little confusing.

Could you explain the difference between Discourse.Dialect.replaceBlock and Discourse.Dialect.inlineBetween? When should I use one over the other?

In the meantime I will try this new code!


(Kane York) #4

Discourse.Dialect.inlineBetween is what is used for stuff like markdown bold and italics as well as the inline bbcode, like [b][/b]. You can generally nest them (see comments about ‘don’t process markdown’), which is the main point.

The replaceBlock ones must take up their entire line (and then some), inlineBetween can be pretty much anywhere.


(Jacob Bridges) #5

Ah, that makes sense. Thank you.

Well, I added your code and changed my Find_MtG_Image function (it had an error with card_id not being defined yet). Now I am having the following issues:

In the preview panel, it works! The link is created as you type the text [mtg]something[/mtg], but that link is removed once you submit the reply and the text [mtg]something[/mtg] reappears.

Also, I discovered when editing a post with the mtg code in it ([mtg]..[/mtg]) every letter you type after that will call the code and makes an AJAX request to the MTG API. It makes editing a post very slow and of course my IP will probably be blacklisted from the MTG API soon enough for “spamming”. Any thoughts?

I’ll keep workin’ on this. Here is my current code (for those interested):

function Find_MtG_Image( query ) {
    var get_card_uri = "http://api.mtgdb.info/cards/";
    // URL encode the query
    encoded_query = encodeURIComponent( query );
    console.log(get_card_uri + encoded_query);
    var card_id = null;

    // Query MtG API for the card with name like "query"
    var request = $.ajax({
        url: get_card_uri + encoded_query,
        async: false,
        type: "GET",
        dataType: "json"
    });
  
    request.done(function( data ) {
        var card_ids = [];
        $.each( data, function( key, obj ) {
	   card_ids.push( obj.id );
        });

        // Get the last card id in the list
        // (Most recent version of the card they are searching for.)
        console.log(card_ids);
        card_id = card_ids.pop()
    });
 
    request.fail(function( ) {
        console.error("Could not get the MTG card image ID!");
    });

     return card_id != null ? card_id : 0;
}

Discourse.Dialect.inlineBetween({
  start: '[mtg]',
  stop: '[/mtg]',
  rawContents: true,

  emitter: function(mdtext) {
    // don't process Markdown inside of [mtg]
    var response = ['a', {"data-mtg-id": Find_MtG_Image(mdtext)}, mdtext];
    console.log(response);
    return response;
    // do process Markdown inside of [mtg]
    //return ['a', {"data-mtg-id": Find_MtG_Image(mdtext)}, Discourse.Dialect.processInline(mdtext)];
  }
});

Discourse.PostView.reopen({
  processGathererLinks: function() {
    var $post = this.$();
    debugger;
  }.on('postViewInserted')
});

(Kane York) #6

You need to specify :server in the register_asset line in plugin.rb for all Dialect processing. (This means that the JS will run when processing posts! Have a separate JS file for the processGatherLinks stuff.)

Here’s an example:


(Jacob Bridges) #7

Ahh, perhaps that is my problem. I didn’t have the “:server_side” line in the plugin.rb.

Now…what separate file should I have for processGathererLinks? Oh, and I installed that plugin (bbcode-color) to my forum and it didn’t work. Created a span, but didn’t create the inline styling. So I decided not to use it as an example.


(Kane York) #8

Ah, that would be because it’s missing the required CSS HTML attribute whitelisting.

Add this as well:

Discourse.Markdown.whiteListTag('a', 'data-mtg-id', /^\d+$/);

This makes the data-mtg-id attribute not be stripped out by the HTML sanitizer - but only if it is strictly a number. (It’s very strict, which is good!)

Name it anything you want. Here’s an example:

register_asset "javascripts/mtg_dialect.js", :server_side
register_asset "javascripts/mtg_rendering.js"

(Jacob Bridges) #9

Gotcha! So that’s why it only accepted ‘href’ for the anchor element.

In your example: register_asset "javacripts/mtg_rendering.js", what part of the code should I put in this file? All of the rendering is done in mtg_dialect.js.


(Kane York) #10

Anything that runs on the client, and not in the Markdown pipeline, should go in a non-:server_side JS file.