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:
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:
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.
<!-- ***************************************** * 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>
#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; }
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 }); } };
User Macro List
User Macro Template Editor
Davin Studer
Business Intelligence Engineer
Vancouver, WA
480 accepted answers
13 comments