FUME – A Better Confluence User Macro Editor Experience

One of the best and most overlooked aspects of Confluence is user macros. There are so many useful scenarios for user macros. Here are some:

  • Templated snippets
  • Overriding built in macros example with task list report
  • Quickly creating your own macros
  • Inserting arbitrary html/css/javascript into a page without having to enabled the html macro

However, there are some big usability issues with the user macro editor. First it’s super easy to accidentally delete one. The delete link is right next to the edit link and seriously, there is no confirmation on the delete link. It’s just gone. Ack!

Second, the link to create new user macros is at the bottom of the page. If you have more that what can fit on a screen you have to scroll down to get to the link to create a new one … this just gets worse over time as you create more.

Third, the template box in the editor is just a plain old text area … no line numbers, not syntax highlighting, it’s not even a mono-spaced font! Grr!

Fourth, the cancel button doesn’t ask you to confirm canceling the edit if you have made changes to the user macro and since it sits right next to the save button it’s easy to miss. Hope you can recreate your work quickly.

Finally, every time you save it kicks you back to the list page. So, if you want to make some changes and try it out on a page you have to click back into the editor every time you save and whoops you accidentally just clicked delete instead of edit! There goes all that work.

So, without further ado … FUME. Fantastic user macro editor. The fantastic part is really just because I needed a word that ended in “ume” and that was the only word I could think of. Really it’s not all that fantastic … maybe just great, but gume isn’t even a word. Then I thought “How about  great looking user macro editor”, but that would be glume and … well … yeah, that kinda defeats the purpose. So, FUME it is. All in all I think it is a much better editing experience than the default setup. Here are some of the features:

  • Copy that “Create a User Macro” link to the top of the list page … no more scrolling
  • Delete confirmation on the list page
  • Template box changed to a source code editor with (Ace editor):
    • monospaced font
    • line numbers
    • syntax highlighting
    • find and replace
    • code folding
    • column select
  • Confirmation on cancelling edits of the user macro if the template has been changed
  • Asynchronous user macro saves
  • It will do your dishes and laundry … ok, not quite yet

Update 4/9/2018:

Ignore the "How to Setup" section below. I'll leave it there, however, for the sake of continuity. After a good kick in the pants from @Boris Berenberg - Atlas Authority I packaged this up as an add-on. I named it Enhanced User Macro Editor (EUME ... pronounced you-me ... it's a stretch I know). It seemed a bit more humble of a name and is more descriptive of what it is. I hope it is as useful for you as it has been for me. Marketplace link below.

Enhanced User Macro Editor

How to Setup

  • Copy the below CSS and Javascript into files on your machine fume.css and fume.js respectively.
  • Place them on a web server where they will be web accessible to your user macro editors.
  • Add this to the end of Confluence Admin -> Custom HTML -> At end of the BODY
    • <!--
      *****************************************
      * Fantastic User Macro Editor           *
      *****************************************
      -->
      <link rel="stylesheet" type="text/css" href="http(s)://{your server}/path/to/fume.css">
      <script src="http(s)://{your server}/path/to/fume.js" type="text/javascript"></script>
  • Enjoy editing your user macros.  :-)

CSS

#savespinner{
    margin-right: 5px;
}

#savedstatus{
    margin-left: 5px;
    font-weight: bold;
}

.ace_editor {
    font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace !important;
    border: 1px solid gray;
    border-radius: 3px;
    color: #333 !important;
}

.invisible {
    visibility: hidden;
}

.saved {
    color: #59DB4D;
}

.error {
    color: #FF0000;
}

JS

AJS.toInit(function() {
    FUME.init();
});

//FUME = Fantastic User Macro Editor
var FUME = {
    editorJQuery: 'userMacro\\.template',
    nameJQuery: 'userMacro\\.name',
    titleJQuery: 'userMacro\\.title',
    editorDiv: 'userMacroEditorDiv',
    isNew: function(){
        return AJS.$('#user-macro-form').prop('name') == 'addusermacro' ? true : false;
    },
    editor: null,
    isDirty: false,
    init: function() {
        var t = this;
        //What page are we on?
        var url = window.location.pathname;

        //If it is the user macro list page do this stuff
        if(url.indexOf('usermacros.action') != -1) {
            //Put a copy of the create user macro link at the top of the page.
            AJS.$('#user-macros-admin').before(AJS.$('a[href="addusermacro-start.action"]').clone());
            
            //Verify that you really want to delete the user macro
            AJS.$('a.remove').click(function(event){
                var macroKey = AJS.$(this).parent().parent().attr('data-macro-key');

                if (!confirm('Do you really want to delete the "' + macroKey + '" user macro?')) { 
                    event.preventDefault();
                }
            });
        }
        
        //If it is the user macro edit page do this stuff
        if(url.indexOf('addusermacro-start.action') != -1 || url.indexOf('updateusermacro-start.action') != -1) {
            AJS.$('#' + t.editorJQuery).css('visibility', 'hidden');
            AJS.$('#' + t.editorJQuery).css('height', '800px');

            //Load ACE editor
            AJS.$.getScript('//cdn.jsdelivr.net/gh/ajaxorg/ace-builds@latest/src-min-noconflict/ace.js', function () {
                //Get editor textarea
                var textarea = AJS.$('#' + t.editorJQuery);

                //New editor div
                var editDiv = AJS.$('<div id="' + t.editorDiv + '">').insertBefore(textarea);
                AJS.$('#' + t.editorDiv).css({
                    'position': 'absolute',
                    'width': textarea.width(),
                    'height': textarea.height(),
                });

                t.editor = ace.edit(editDiv[0]);
                t.editor.setShowPrintMargin(false);
                t.editor.setTheme('ace/theme/textmate');
                t.editor.getSession().setMode('ace/mode/velocity');
                t.editor.getSession().setValue(textarea.val());
                t.editor.getSession().on('change', function () {
                    textarea.val(t.editor.getSession().getValue());
                    t.isDirty = true;
                });
            });

            //Change up the cancel button a bit
            AJS.$('#cancel').removeClass('aui-button-link');
            AJS.$('#cancel').addClass('submit');
            AJS.$('#cancel').val('Close')

            //Add save spinner
            AJS.$('#confirm').before('<span id="savespinner" class="aui-icon aui-icon-wait invisible">Saving...</span> ');

            //Add ctrl+s keyboard shortcut for save
            AJS.whenIType('ctrl+s').click('#confirm');

            //Add clicked attribute to submit buttons when they are clicked so that I can tell which was clicked in the form submission event
            AJS.$('form input[type=submit]').click(function(){
                AJS.$('input[type=submit]', AJS.$(this).parents('form')).removeAttr('clicked');
                AJS.$(this).attr('clicked', 'true');
            });

            //Set up the save to be asynchronous
            AJS.$('#user-macro-form').submit(function(event) {
                var clicked = AJS.$('input[type=submit][clicked=true]').val();
                var err = "";
                //If Save button was clicked then do asynchronous stuff.
                if(clicked === 'Save'){
                    event.preventDefault();

                    //Validation
                    if(AJS.$('#' + t.nameJQuery).val() === "") {
                        err += '* User macro name cannot be empty<br>';
                    }
                    if(AJS.$('#' + t.titleJQuery).val() === "") {
                        err += '* User macro title cannot be empty<br>';
                    }
                    if(AJS.$('#' + t.editorJQuery).val() === "") {
                        err += '* User template cannot be empty<br>';
                    }
                    
                    if(err === "") {
                        //disable save button and show spinner
                        AJS.$('#confirm').attr('disabled', true);
                        AJS.$('#savespinner').removeClass('invisible');

                        if(AJS.$('#savedstatus').length > 0) {
                            AJS.$('#savedstatus').remove();
                        }

                        //Do the asynchronous save
                        AJS.$.ajax({
                            type: 'POST',
                            data: AJS.$(this).serialize(),
                            url: AJS.$(this).attr('action'),
                            success: function(data) {
                                //hide the spinner, re-enable the save button, display status, and clear dirty flag
                                AJS.$('#savespinner').addClass('invisible');
                                AJS.$('#confirm').attr('disabled', false);
                                AJS.$('#cancel').after('<span id="savedstatus" class="saved">Saved</span>')
                                t.isDirty = false;
                                
                                //if this is a new user macro redirect to new url
                                if(t.isNew()) {
                                    location.href = AJS.Meta.get('base-url') + '/admin/updateusermacro-start.action?macro=' + AJS.$('#' + t.nameJQuery).val();
                                }
                            },
                            error: function() {
                                //hide the spinner, re-enable the save button, and display status
                                AJS.$('#savespinner').addClass('invisible');
                                AJS.$('#confirm').attr('disabled', false);
                                AJS.$('#cancel').after('<span id="savedstatus" class="error">Error saving</span>')
                            }
                        });
                    } else {
                        t.postErrorMessage('Error', err);
                    }
                } else {
                    if(t.isDirty) {
                        if (!confirm('Changes have been made. Do you really want to close and lose those changes?')) { 
                            event.preventDefault();
                        }
                    }
                }
            });
        }
    },
    postErrorMessage: function (title, body) {
        var errorFlag = AJS.flag({
            type: 'error',
            title: title,
            body: body
        });
    }
};

Screenshots

User Macro ListUserMacroListPage.png

User Macro Template EditorUserMacroTemplateEditor.png

13 comments

Comment

Log in or Sign up to comment
Boris Berenberg - Atlas Authority
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 29, 2018

Have you considered just putting this into a plugin? Shouldn't be too hard, and then it doesn't require self hosting of the assets. Plus users would get updates as you evolve the tool.

Davin Studer
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 29, 2018

I've totally thought about it, but then I would have to learn how to create a Confluence add-on. :) I fear that if I try to cram another programming environment into my head it might actually explode. But maybe this is a good place to start with actually learning the Confluence SDK. Well shoot, now you have me thinking about learning something new! :)

Heth Siemer
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 29, 2018

Voluptuous User Macro Editor? Volume!

There's also assume... Okay I'm done ;)

This is kinda amazing though... I gave up on a project to create a user macro 'cause I deleted it on accident and didn't have a backup.

Bill Bailey
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 29, 2018

I started using Notepad++ with Velocity highlighting. Works really well.

Patrick Cartier [Candylio]
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
April 1, 2018

Incredibly useful.

Ditto on putting this into a plugin, if you need help, don't hesitate to reach out.

Boris Berenberg - Atlas Authority
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
April 2, 2018

One other note, if you post a license for this code, then someone else could build the plugin for you 😉

Davin Studer
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
April 6, 2018

So, @Boris Berenberg - Atlas Authoritythrew down the gauntlet and I took it up. Thanks for the push. It took me a few days to figure out how create an add-on and how to bundle it up (I'm a .NET guy not a Java guy). But anyway ... success!! I submitted it to the marketplace a few days ago and once it is approved I will edit the above article and post a link to the add-on.

Davin Studer
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
April 6, 2018

In fact I've already got v 1.1.0 waiting to push to the marketplace once v1.0.0 gets the go ahead. So good call @Boris Berenberg - Atlas Authority.

Heth Siemer
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
April 6, 2018

Okay THAT is cool. Good job :)

Boris Berenberg - Atlas Authority
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
April 6, 2018

@Davin Studer thats great to hear. 

Davin Studer
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
April 9, 2018

Alrighty it's up. Here's the link.

Enhanced User Macro Editor

Davin Studer
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
August 22, 2019

As of today version 1.2.0 is up on the marketplace. This version adds the ability to have user macro changes saved as historical versions.

Boris Berenberg - Atlas Authority
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
August 22, 2019

youre-killing-it-9fb13.png

TAGS
AUG Leaders

Atlassian Community Events