A New Viewport in Confluence

November 18th 2014 Travis Smith in Confluence, Grunt

You may have noticed a new experience for developer.atlassian.com, which includes changes to how our content in Confluence is displayed. This was an interesting challenge for us as we also have static front-end pages around the site that aren't part of Confluence. These pages are the source for the style applied to the Confluence content, so it was important that we had a way to easily share the layout (templates, Javascript, and styling) between the handful of front-end pages at the Confluence content.

To solve this challenge and keep maintenance low we have implemented an add-on, Scroll Viewport by K15t in Confluence. Viewport provides an excellent way to display Confluence content with custom skins. It does not change your Confluence software, but simply provides an additional view of each page. We set up a process to take the styles from the static front end and create the Viewport theme, so we can easily import every time there's a change. This way, every time there's an update to our styling and layouts, we can apply it to both our static front end and all our Confluence content, in a jiffy.

We manage our static front-end pages (landing pages, for example) with Linemanjs. Linemanjs is a wrapper around grunt tasks used to generate static web sites.

Technical approach

The pieces connecting Linemanjs to Viewport consist of a grunt task that finishes up a Viewport theme and packages it up, so it can be imported into Confluence. Viewport themes are just plugins with the right manifests and velocity templates. Our grunt task is pretty simple based upon that:

grunt.registerTask("viewports", "Creates a viewports theme jar", function() {
    var done = this.async();

    copyTemplates();
    writeTemplate(DAC_PAGE_TEMPLATE);
    writeTemplate(DAC_ERROR_TEMPLATE);
    writeDescriptor();
    zip(done);
});

The first step, copyTemplates(), just copies the template theme content to a location to be packaged up. writeTemplate is used to take process a couple of pages from the underscore templates that we use with Linemanjs into velocity templates.

function writeTemplate(output) {
    var layoutTemplate = createTemplate(LAYOUT_TEMPLATE);
    var velocity = fs.readFileSync(output, "utf8");
    var compiled = layoutTemplate({
        "yield": velocity,
        "css": "/css/app.css",
        "js": "/js/app.js"
    });
    fs.writeFileSync(output, compiled);
}

The inner content of the velocity templates already exists in the theme template, so we just read in that file and use it for the content of the velocity template. Once that's done we write out the plugin descriptor.

function writeDescriptor() {
    var files = grunt.file.expand({ cwd: DAC_THEME_DIR_PATH }, path.join("**/*.*"));
    var pluginXmlTemplate = createTemplate(ATLASSIAN_PLUGIN_TEMPLATE);
    var atlassianPluginXml = pluginXmlTemplate({
        resources: files
    });

    fs.writeFileSync(path.join(TEMP_DIR, "atlassian-plugin.xml"), atlassianPluginXml);
}

With a simple descriptor template:

<atlassian-plugin key="com.k15t.scroll.viewport.themes.mytheme" name="my theme" plugins-version="2">
    <plugin-info>
        <description>my theme (Scroll Viewport)</description>
    </plugin-info>

    <scroll-viewport-theme key="my theme" name="my theme">
        <% _.each(resources, function(resource) {
        %><resource name="<%= resource %>" location="theme/<%= resource %>" />
        <% }) %>
    </scroll-viewport-theme>
</atlassian-plugin>

Then finally zip() is called to zip everything up into a jar file.

All of this together allows us to manage the styling of both the static front-end and Viewport displayed content in Confluence together. We initially considered this to be one of our biggest hurdles So far this has worked out great and we will be extending this to further expand how other static documentation content on DAC appears.