Last updated Dec 8, 2017

Extending the Confluence insert link dialog

Applicability

This tutorial applies to Confluence 4.0, and later

Level of experience:

This is an intermediate tutorial. You should have completed at least one beginner tutorial before working through this tutorial. See the list of developer tutorials.

Time estimate:

It should take you approximately half an hour to complete this tutorial.

Overview of the tutorial

This tutorial shows you how you can add a panel to the Insert Link dialog in Confluence. The plugin you will create inserts a new tab called Demo in the dialog. When you click the Demo tab, the Demo panel appears with a single text box:

Our plugin simply converts text entered in the field into an HTML link. This is a simple plugin, but provides a starting point for the more advanced link dialog customizations you might like to add to Confluence.

To create this plugin, you will create a Confluence plugin consisting of the following components:

When you have finished, all these components will be packaged in a single JAR file.

Required knowledge

To get the most out of this tutorial, you should be familiar with:

  • The basics of Java development, such as classes, interfaces, methods, and so on.
  • How to create an Atlassian plugin project using the Atlassian Plugin SDK.
  • How to use and administer Confluence.

Plugin source

We encourage you to work through this tutorial. If you want to skip ahead or check your work when you have finished, you can find the plugin source code on Atlassian Bitbucket. Bitbucket serves a public Git repository containing the tutorial's code. To clone the repository, issue the following command:

1
2
git clone https://bitbucket.org/atlassian_tutorial/confluence-insert-link-demo-plugin

Alternatively, you can download the source as a ZIP archive by choosing download here: https://bitbucket.org/atlassian_tutorial/confluence-insert-link-demo-plugin.

About these Instructions

You can use any supported combination of OS and IDE to create this plugin. These instructions were written using IntelliJ IDEA on Ubuntu Linux. If you are using another OS or IDE combination, you should use the equivalent operations for your specific environment.

This tutorial was last tested with Confluence 5.1.4.

Step 1. Create the plugin project

In this step, you'll use an atlas command to generate stub code for your plugin and set up the stub code as an Eclipse project. The atlas commands are part of the Atlassian Plugin SDK. They automate much of the work of plugin development for you.

  1. If you have not already set up the Atlassian Plugin SDK, do that now: Set up the Atlassian Plugin SDK and Build a Project.

  2. Open a terminal and navigate to the directory where you want to place your project files.

  3. Enter the following command:

    1
    2
    atlas-create-confluence-plugin
    
  4. As prompted, enter the following information to identify your plugin:

    group-id

    com.example.plugins.tutorial

    artifact-id

    confluence-insert-link-demo-plugin

    version

    1.0-SNAPSHOT

    package

    com.example.plugins.tutorial

  5. Confirm your entries when prompted.

Step 2. Clean up some project files

The SDK gave us most of the files we'll need for this plugin, and a few that we don't. For instance, it gave us several Java code stub files and test files for the code. This plugin doesn't use Java at all; it's entirely implemented with JavaScript and Soy. Some of these will interfere with our work, so let's deal with them first:

  1. Change to the new project directory created by the SDK, confluence-insert-link-demo-plugin.
  2. Remove the entire test directory at src/test.

You can also remove the Java files from your project by removing the src/main/java directory. However, removing those files is not necessary. Keeping them gives you a place to put Java code if you plan to expand this plugin in the future.

Step 3. Create a Soy template for the custom panel

The SDK generated some resource files for us. However, we'll keep the resource files associated with this plugin in a separate directory, just for clarity.

First, create the Soy template used to generate our Insert Link panel:

  1. Change to the following directory under the new project home: src/main/resources/
  2. Create a new directory called insert-link. This is where we'll keep the resource files specific for our plugin.
  3. In the new directory, create a file named demo-panel.soy.
  4. Add the following content to the file:
1
2
{namespace Confluence.Templates.LinkBrowser}

/**
 * The contents of the panel.
 */
{template .demoPanel}
    <div class="input-field">
        <label for="demo-panel-destination">{getText('link.browser.demo.link.label')}:</label>
        <input type="text" tabindex="0" class="text" id="demo-panel-destination" name="destination" size="60">
    </div>
    <div class="description">{getText('link.browser.demo.link.desc')}</div>
{/template}

The namespace must be Confluence.Templates.LinkBrowser, as shown here. The name of the template, demoPanel, must be the key of your panel (as specified in the JavaScript file below) with the suffix -Panel. In this case, our panel's key is demo, so the template name is demoPanel.

The template should contain the markup you want to appear in the body of the link dialog. In this case, we have a label with an i18n key of link.browser.demo.link.label, a text box with the name destination, and some description text with an i18n key of link.browser.demo.link.desc.

Step 4. Create a JavaScript file to initialise the panel and implement the logic

Now add the JavaScript code: 

In the src/main/resources/insert-link directory, create a file named confluence-insert-link-demo-plugin.js.

Add the following content to the file:

1
2
/**
 * Initialisation and logic for the panel.
 */
(function($) {

    // Registers the tab when the Link Browser is created.
    AJS.bind("dialog-created.link-browser", function (e, linkBrowser) {

        var key = 'demo',    // This panel's key
            $linkField,      // The jQueryfied link input element.
            thisPanel,       // A reference to this panel
            tab;             // A reference to the tab

        tab = linkBrowser.tabs[key] = {

            // Called when the panel is created - register event handlers here
            createPanel: function (context) {
                thisPanel = context.baseElement;
                
                // panel initialisation logic here
            },

            // Called when the panel is selected
            onSelect: function () {
            },

            // Called when this panel is no longer selected
            onDeselect: function () {
            },

            // Called when the submit button is clicked, before the location is retrieved
            preSubmit: function () {
            },

            // Called before the dialog opens to determine which tab to highlight
            handlesLink: function (linkObj) {
                // return true if the link should be cause this panel to be selected
                return false;
            }
        };
    });

})(jQuery);

This forms the scaffolding for our JavaScript code, with only placeholders for individual functions so far. We'll develop the logic for each function as we go. The important parts of this scaffolding are:

    • The panel key demo must match the web-item link ID (see below) and the ID of the Soy template for the panel (see above).
    • The $linkField variable will be used to keep a jQuery reference to our link field, which will be used in several of the handler functions.
    • The functions createPanel, onSelect, onDeselect, preSubmit and handlesLink are hooks called by the Insert Link dialog as the tab is respectively: initialised, displayed, hidden, submitted and opened in edit mode.

Replace the createPanel function in the scaffolding with our initialization logic:

1
2
// called when the panel is created - register event handlers here
createPanel: function (context) {
    thisPanel = context.baseElement;
    $linkField = thisPanel.find("input[name='destination']");

    // prevent enter submitting any forms when the button is disabled
    thisPanel.find("form").keydown(function(e) {
        if(e.keyCode == 13 && !linkBrowser.isSubmitButtonEnabled()) {
            e.preventDefault();
        }
    });
    
    // update the link to be inserted by the dialog when the user types
    $linkField.keyup(function (e) {
        AJS.log("link field keyup");
        var url = $linkField.val();
        var linkObj = url ? Confluence.Link.makeExternalLink(url) : null;
        if (linkObj) {
            // will enable the Submit button when a URL is added
            linkBrowser.setLink(linkObj);
        }
    });
},

This handler code does the following:

    • Stores a reference to the panel element for future use.
    • Finds the destination input field and stores a reference to a jQuery wrapper around that element for later use.
    • Ignores the user hitting enter (keycode 13) in the form if the submit button is not enabled.
    • Listens for "keyup" events on the link field, and updates the link destination as the user types. The link is created via makeExternalLink in the Confluence.Link object. See link-object.js in the Confluence source code for information about other link types which can be inserted.

The only other handler we will implement for this tutorial is the onSelect function, which will be as follows:

1
2
// Called when the panel is selected
onSelect: function () {
    // Defer focus to after dialog is shown, gets around AJS.Dialog tabindex issues
    setTimeout(function() {
        $linkField.focus();
    });
},

This handler code focuses the link text box when the panel is opened.

Step 5. Create CSS and i18n files to provide styling and text for the panel

  1. Create a file named confluence-insert-link-demo-plugin.css in the src/main/resources/insert-link/ directory.

  2. Insert the following CSS code into the file:

    1
    2
    .demo-panel label {
        width: 54px;
    }
    .demo-panel .description {
        margin-left: 64px;
    }
    
  3. Open src/main/resources/confluence-insert-link-demo-plugin.properties for editing.

  4. Add the following properties to the file:

    1
    2
    link.browser.tab.name.demo=Demo
    
    link.browser.demo.link.label=Link
    link.browser.demo.link.desc=The destination for the link
    

The CSS and properties files will provide the styling and text for our panel.

Step 6. Register the plugin modules in the plugin descriptor

In the plugin XML descriptor file, src/main/resources/atlassian-plugin.xml, we need to register a web item module to add the Demo tab to the dialog, as well as our i18n, JavaScript, Soy and CSS files.

Open the plugin descriptor file and replace the existing content with the following:

1
2
 <atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2">
    <plugin-info>
        <description>${project.description}</description>
        <version>${project.version}</version>
        <vendor name="${project.organization.name}" url="${project.organization.url}" />
        <param name="plugin-icon">images/pluginIcon.png</param>
        <param name="plugin-logo">images/pluginLogo.png</param>
    </plugin-info>

    <!-- add our i18n resource -->
    <resource type="i18n" name="i18n" location="confluence-insert-link-demo-plugin"/>

    <web-item key="link-browser-tab" name="Link Browser Tab"
            section="system.editor.link.browser.tabs" weight="60">
        <description>Displays a custom tab in the Link Browser.</description>
        <label key="link.browser.tab.name.demo"/>
        <link linkId="demo"/><!-- has to match the 'key' specified in the panel JS file -->
    </web-item>

    <web-resource key="insert-link-javascript" name="Insert Link Dialog JavaScript">
        <!-- transform calls to AJS.getText() inside JS files -->
        <transformation extension="js">
            <transformer key="jsI18n"/>
        </transformation>

        <!-- transform Soy templates into JS -->
        <transformation extension="soy">
            <transformer key="soyTransformer">
                <functions>com.atlassian.confluence.plugins.soy:soy-core-functions</functions>
            </transformer>
        </transformation>

        <!-- JavaScript resources -->
        <resource name="demo.js" type="download" location="insert-link/demo.js"/>
        <resource name="demo-panel-soy.js" type="download" location="insert-link/demo-panel.soy"/>

        <!-- we need the editor resources to be loaded before these ones -->
        <dependency>com.atlassian.confluence.tinymceplugin:editor-javascript-resources</dependency>

        <!-- these resources will be included with any page containing the editor -->
        <context>editor</context>
    </web-resource>

    <web-resource key="insert-link-css" name="Insert Link Dialog CSS">
        <!-- CSS resources -->
        <resource name="demo.css" type="download" location="insert-link/demo.css"/>

        <!-- we need the editor resources to be loaded before these ones -->
        <dependency>com.atlassian.confluence.tinymceplugin:editor-javascript-resources</dependency>

        <!-- these resources will be included with any page containing the editor -->
        <context>editor</context>
    </web-resource>

</atlassian-plugin>

The important parts of the plugin descriptor are as follows:

  • The web-item create a new tab in the link dialog. This allows the new panel to appear. The linkId attribute on the link (in this case "demo") must match the key of the plugin and the name of the Soy template, as noted above.
  • The JavaScript web-resource must have the Soy transformer and the i18n transformer registered as above to work properly.
  • Both web-resources have a context of editor, so they appear on any page containing the page editor. This means that your JS code will be included with the batch.js file for pages that include "editor" in the URL.
  • The i18n resource declaration references the path to the i18n file without a "properties" suffix.

Because your JS code is loaded the editor context, it will be included with the "batch.js" file that includes "editor" in the URL.

That's it! Now let's try the plugin.

Step 7. Try it out

Now start up Confluence and try out your plugin:

  1. Make sure you have saved all your code changes to this point.

  2. Open a terminal window and navigate to the plugin root folder (where the pom.xml file is).

  3. Run the following command:

    1
    2
    atlas-run
    

    This command builds your plugin code, starts a Confluence instance, and installs your plugin in it. This may take several seconds. When the process has finished, you will see many status lines on your screen concluding with something like the following:

    1
    2
    [INFO] confluence started successfully in 119s at http://atlas-laptop:1990/confluence
    [INFO] Type CTRL-D to shutdown gracefully
    [INFO] Type CTRL-C to exit
    
  4. From your browser, open the Confluence URL indicated in the output.

  5. At the Confluence login screen, enter the username/password combination of admin and admin.

  6. Create a new page.

  7. In the edit pane for your page, click the link icon.

  8. Confirm that the Demo tab appears in the Insert Link dialog.  Also, make sure that the link insert pane appears with no errors.

Troubleshooting

If the Demo tab does not appear in the dialog, make sure you have the web-item correctly registered in atlassian-plugin.xml. Also make sure that you have installed the plugin in Confluence and it is enabled.

If the panel itself is not working properly, check for JavaScript errors in the debugging console in your browser. Firebug or the WebKit inspector may help you debug your code and track down the problem.

Rate this page: