Simplify Confluence breadcrumbs

Breadcrumbs are a pain in the ass in Confluence. Not just because there’s a default mode where Confluence inserts an ellipses (…), but also because the default Velocity template that draws the crumbs is overly complicated.

Plus, I am getting pretty tired of adding jQuery every time I want to customize something on my site: for example, I had a line in my Custom HTML that simulates a “click” event on the ellipses just so all the crumbs would appear.

Before, my Custom HTML included the following:

<!-- Automatically expand the "..." in the breadcrumbs when the page loads -->
AJS.$('#ellipsis strong').click();
<!-- Remove the redundant breadcrumb node -->
<!-- if  there are exactly 3 breadcrumbs (for example: DevCenter > Documentation > Documentation), remove the third one (#2) to avoid redundancy -->
if (AJS.$('#breadcrumbs li').length == 3) {
  AJS.$('#breadcrumbs li').eq(2).remove();
} else {
  AJS.$('#breadcrumbs li').eq(3).remove();
}

What a PITA.

So, I decided to remove the jQuery calls, and simplify the Velocity template instead, customizing the crumbs to meet my site’s needs. Here’s my commented version of the breadcrumbs.vm file. This file is located in the /confluence directory.

Note: The file looks long, but half the lines are comments:

#requireResource("confluence.web.resources:breadcrumbs")
## breadcrumbs is a List of AbstractBreadcrumb objects
## http://docs.atlassian.com/atlassian-confluence/latest/com/atlassian/confluence/util/breadcrumbs/AbstractBreadcrumb.html
#set ($breadcrumbs = $helper.breadcrumbs)
<content tag="breadcrumbs">
  #set ($counter = 0)
  <ol id="breadcrumbs">
    ## iterate over each breadcrumb
    #foreach( $breadcrumb in $breadcrumbs )
      ## skip the first two breadcrumbs (0 and 1) because they are redundant and unnecessary
      #if ($counter > 1)
        ## if it's the second breadcrumb, then set the LI class to "first" so Confluence does not draw the separator
        #if ($counter == 2) #set($crumbString = "first") #else #set($crumbString = "") #end

        ## get the displayTitle property to set the text of the crumb
        #set ($displayTitle = $breadcrumb.displayTitle)

        ## draw the breadcrumb with the contentPath and target as the link, displayTitle as the text
        <li class="$crumbString"><span><a href="$req.contextPath$breadcrumb.target">$displayTitle</a></span></li>
      #else
        ## do nothing
      #end ## end counter > 1
      ## increment the counter
      #set($counter = $counter + 1)
    #end ## end foreach
  </ol>
</content>

Yeah, yeah, I know. It will break when I upgrade. It lacks much error checking. It doesn’t support tooltips. Whatever, it suits my needs.

To customize the separator, I just replace the images/decoration/white_breadcrumbs_indicator.png to an image that I want. I didn’t bother trying to track down where that image is set (I’m almost certain it’s hardcoded in the WAR file somewhere, and I just don’t have the heart to find it).

Posted in Uncategorized | Leave a comment

Protect static HTML pages using Crowd and Confluence

Overview

Recently, we had a requirement to add several sets of API documentation to our Confluence website. While the API docs are static, they have some funky characteristics that make them difficult to include in Confluence:

  • Source is generated from multiple tools — Our API docs will be generated form Doxygen and JSDoc. Neither of these can be imported as Confluence pages
  • Source is generated at regular intervals — As the APIs change, so will the docs. As a result, adding the API docs to Confluence would not be a one-time event. It would have to happen every time the API changes.
  • Source docs should be developer friendly — The format for API docs used by Doxygen and JSDoc are known and loved by developers. We don’t want the Confluence styles to be applied to these docs.

Because of these reasons, we decided to add the API docs as static HTML in their own subdirectories of our Confluence-based documentation. Basically, we want users to be able to navigate to them, but NOT view them within Confluence.

However, we also wanted to use the existing Confluence/Crowd authentication scheme on these docs. If a user navigates to the API docs, we wanted them to first have to log in, just as they would if they navigated to the Confluence-based technical documentation.

Set up

Seraph-based authentication ties into Crowd’s groups. You can identify a group that has rights to access assets that match any path.

This technique piggybacks on the Seraph HTTP authentication scheme. This scheme is used to protect the “admin” application in Confluence. However, it can be expanded to include any number of arbitrary directories within the Confluence installation.

Copy HTML files to Confluence

Copy the HTML files to the confluence/confluence directory. These files should be at the same level as the static resources such as the “admin” and “images” directories.

Add a new path and role

To add a new path under Seraph, edit the seraph-paths.xml file. This file is located in confluence/WEB-INF/classes. Add a new path and define its URL pattern and role-name.

The following example adds the path /api-docs and indicates that only members of the “customers” group can access resources in that directory:

<security-paths>
    <path name="adminJspPath">
        <url-pattern>/admin/*.jsp</url-pattern>
        <role-name>admin_jsp_role</role-name>
    </path>
    <path name="admin">
        <url-pattern>/admin/*</url-pattern>
        <role-name>confluenceadmin_seraph_role</role-name>
    </path>
    <!-- Add new path/role: -->
    <path name="api-docs">
        <url-pattern>/api-docs/*</url-pattern>
        <role-name>customers</role-name>
    </path>
</security-paths>

Apply no decorator by default

By default, Confluence will attempt to apply its decorators to assets protected by Seraph. To avoid this, edit the decorators.xml file. Within the <decorator name="none"> block, add the following:

<url-pattern>/api-docs/*</url-pattern>

This tells Confluence to apply no decorator to any file served up under this path.

Issues/Risks

  1. Seraph only appears to support giving one Crowd group access permissions to the path, despite Atlassian documentation to the contrary.
  2. Upgrading to a new version of the server will require re-adding the paths, roles, and decorator rules. If the new version of Confluence changes syntax or no longer supports Seraph, then this technique may no longer work.
  3. Based on the lack of documentation, I suspect this “feature” is unsupported by Atlassian. Use at your own risk.

Resources

The following resources came in handy when trying to understand how to use static HTML in a Crowd/Confluence setup:

Posted in Uncategorized | Leave a comment

Use a macro within a macro

The concept of using a Confluence macro within a User Macro sounds simple enough, but there are some gotchas.

To create a new macro, go to the Adminstration panel and click User Macros. Click the Create User Macro button:

You get a template:

## Macro title: My Macro
## Macro has a body: Y or N
## Body processing: Selected body processing option
## Output: Selected output option
##
## Developed by: My Name
## Date created: dd/mm/yyyy
## Installed by: My Name
## This is an example macro
## @param Name:title=Name|type=string|required=true|desc=Your name
## @param Colour:title=Favourite Colour|type=enum|enumValues=red,green,blue|
default=red|desc=Choose your favourite colour

Hello, <font color="$paramColour">$paramName</font>!

To use a simple Confluence macro in the body of your custom macro, you can call the renderConfluenceMacro() method on the $helper object (which is a GlobalHelper). But to get the $helper in a macro, you need to call $action.getHelper() first:

$action.getHelper().renderConfluenceMacro("{panel:title=My Panel}")

This inserts a Panel macro in your custom macro, and sets the title parameter to “My Panel”.

The problem is that the Panel macro takes a parameter that you can’t easily set in the  markup: the body text that appears inside the panel. Plus, there are other parameters, such as the background color of the title bar, whose names are not guessable (like “title” was).

To find the names of the parameters and see how Confluence stores the body text for its Panel macro, add a Panel macro to a page in the Confluence WSYWIG editor and save it. Then, while viewing the page, select Tools > View Storage Format.

A dialog box containing the XHTML markup of the page appears. Look around until you find the text of the macro, wrapped in the <ac:macro> tag:

<ac:macro ac:name="panel">
 <ac:parameter ac:name="titleBGColor">#326CA6</ac:parameter>
 <ac:parameter ac:name="title">About This Tutorial</ac:parameter>
 <ac:parameter ac:name="borderStyle">solid</ac:parameter>
 <ac:parameter ac:name="borderColor">black</ac:parameter>
 <ac:parameter ac:name="borderWidth">1</ac:parameter>
 <ac:parameter ac:name="titleColor">#ffffff</ac:parameter>
 <ac:rich-text-body>
 <p><strong>Prerequisite:</strong> This tutorial requires that you have imported the starter
version of the sample app.</p>
 <p><span><strong>Set up:</strong> For information on importing the </span><span>starter</span>
<span> version of the sample app, see <ac:link><ri:page ri:content-title="Run an app"
ri:space-key="gscontent"/></ac:link>.</span></p><p><span><strong>Note:</strong> You are not
required to perform the tutorials in any specific order.</span></p>
 </ac:rich-text-body>
</ac:macro>

You can clearly see the parameters names (like titleBGColor and rich-text-body), as well as the complete markup for the macro. Most importantly, it shows you how to insert body content inside the Panel macro by using the rich-text-body parameter.

Copy this block and paste it into your custom macro, so that it looks something like this:

## Macro title: Custom Panel
## Macro has a body: N
##
## Developed by: Matthew J. Horn
## Date created: 06/11/2012
## Installed by: Matthew J. Horn
## Inserts a panel with instructions/information about tutorials.
## This macro takes no parameters
## @noparams
<ac:macro ac:name="panel">
   . . . // Insert the other XHTML here
</ac:macro>

Another way to get the parameters for a Confluence macro is to use the 3.5 documentation, which was written at a time when you still had access to the wiki markup. The Confluence 4 doc on the Panel macro lists the parameters by their friendly names, but does not give you the actual names of these parameters, so you have no idea how to add them to a custom macro. The 3.5 doc on the Panel macro, however, lists most of the parameter names (but not all: it’s missing the rich-text-body parameter, for example).

Thanks to the information on this page for this great tip.

Posted in macros | Leave a comment

Change default selection of the Notify Watchers input

The default behavior of the Confluence Edit screen is to have the “Notify Watchers” checkbox checked.

When you click the Edit button:

The Edit page loads, with this at the footer:

This is great, except that with 3 full time technical writers working on a single site, and regularly updating many pages every day, that adds up to alot of notifications. And these notifications take the form of emails. Hundreds of emails a week. It’s all or nothing.

I wanted to make the default for “Notify Watchers” checkbox on the edit page to be unchecked.

In the HTML, the notifyWatchers input looks like this:

<input id="notifyWatchers" name="notifyWatchers" value="true" type="checkbox" checked="checked">

So I added the following to the HEAD section of the Administration panel’s Custom HTML:

<script>
  AJS.toInit(function(){
    <!-- Disable "notifyWatchers" checkbox by default (on edit pages only) -->
    AJS.$('input#notifyWatchers').attr('checked', false);
  });
</script>

This little squib of jQuery syntax finds the node that matches “input#notifyWatchers” (in this case, an input with the id of “notifyWatchers” and changes the value of the checked attribute to false. So now when I edit a page, it defaults to this:

jQuery FTW!

Posted in Edit screen, jQuery | Leave a comment