Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Table of Contents macro that includes headings from descendant pages?

Graham Hannington April 5, 2018

I have found existing questions about the Table of Contents macro, but none quite match this question.

Background to this question: what I'm doing

I have recently returned to using Confluence frequently after a long period of infrequent use. I'm relearning what Confluence does, and doesn't, do out of the box.

I'm using Confluence to write policies for my employer. We have decided to use an ISO standard as a template for the policy heading structure. The heading structure contains three levels. I have "chunked" the structure into Confluence pages at the highest heading level, giving me 14 pages. Those pages will grow as I add content to the skeleton heading structure.

On returning to Confluence, after spending time in other documentation systems, I had two nasty surprises. I'd be very happy to be wrong about both of them:

  • The Confluence "Table of Contents" macro does not offer the option to include headings from descendant pages.
  • A related issue: The Confluence browser user interface does not support search-and-replace across multiple pages; in particular, search-and-replace that includes descendant pages of the "current" page. It seems I need to write my own code (possibly based on an existing user macro) that, for example:
    1. Uses the Confluence REST API to traverse the page tree and get the contents of each page
    2. Performs the search and replace for each page
    3. Uses the REST API to update each page

I want a single table of contents that includes all of the policy headings. For now, as a workaround, I've created a "single page" version of the policy that consists of a "Table of Contents" macro followed by an "Include Page" macro for each of the 14 "chunks". Each "Include Page" macro is preceded by a Heading 1 paragraph that duplicates the "chunk" page title. A useful side-effect is that I can use this page to search the entire policy using my browser's built-in "Find" function.

On a few occasions, I've needed to perform search'n'replace across the 14 pages of the policy. For now, without a better solution—and I'm not interested in petitioning my employer to buy another for-money Confluence plugin—I've done that by manually editing each of the 14 pages. That's not been fun, but so far I've been too busy writing and editing content to want to look into writing my own search'n'replace code. It's occurred to me to try WebDAV with an external editor, but after making some enquiries to my local Confluence administrator, I dropped that idea (I can explain, if asked).

I'd like to split those 14 pages into smaller chunks, perhaps even right down to the lowest (third) heading level, but I baulk at the thought of:

  • Increasing the complexity of the "single page" version (many more "Include Page" macros and preceding headings that duplicate page titles)
  • Manually searching'n'replacing across many pages

One feature of the "Table of Contents" macro that I really like is the ability to include or exclude headings based on regular expressions. I'm using that feature to create different versions of the table of contents, based on the presence or absence of strings that I append to the headings, such as "(TODO)" or "(N/A)".

What I want

I want a version of the Table of Contents macro that offers two "checkbox" settings:

  • Include headings from descendant pages
    • Include descendant page titles as headings (treat child page titles as Heading 1, grandchild page titles as Heading 2, and so on)

Does anyone have this, or something similar, as a user macro that they could share with me?

Failing that: (where, in the installed Confluence product code) can I get the source code of the supplied Table of Contents macro, so that I can use it as the basis for my own version with these additional settings? I have some experience writing Confluence user macros, but that was several years ago.

I'd also like a search-and-replace macro that recurses into descendant pages, but I should ask a separate question about that (I mention it here because it's so closely related to my use case for this TOC macro).

2 answers

0 votes
Suraj Borge February 27, 2019

Hi,

I want to know how to include only the headings starting with number

0 votes
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

I think you can get what you are looking for using some other macros in conjunction with the TOC.

On your parent page set up a children display macro like this.

parent.png

On your child pages setup your page like this.

child.PNG

This is what the final result looks like on the parent page.

result.PNG

If you are on the server, as opposed to cloud, version of Confluence you could then add some CSS styling to make the outline look the way you wont on the parent page. Is this what you are looking for.

Graham Hannington April 12, 2018

Thank you very much for your answer.

I have tested it with the following page tree hierarchy (note the various levels of "chunking"):

confluence-toc-testing-page-tree-2018-04-13.png

Result:

confluence-toc-testing-root-toc-2018-04-13.png

Perfect!

Notes:

  • The heading numbers are fixed, to match heading numbers in an ISO standard
  • The "Subheading" is a Heading 4

Is it what I'm looking for? I'm in a quandary:

  • Pro: it has the effect—the result—that I'm looking for. (As you note, I'll need to use CSS to tweak the styling, such as removing the bullets. Yes, I'm using Confluence Server.)
  • Con: the effect isn't achieved using the method I'd anticipated (a single macro in the root page). I'm not happy about adding that Excerpt macro with nested Table of Contents macro to every descendant page. These pages will likely have multiple authors, which in practice means this method is more prone to error (authors must remember to do it).

So, I'm not sure whether to accept your answer or not. I sincerely appreciate your answer, but it's not quite what I'm looking for.

I'd still like a single macro, or: can you tell me whether and where, in the installed Confluence product code, I can get the source code of the supplied Table of Contents macro, so that I can use it as the basis for my own version with these additional settings?

Graham Hannington April 12, 2018

Re:

I think you can get what you are looking for using some other macros in conjunction with the TOC.

On that note: it occurs to me that I don't necessarily need an enhanced version of the Table of Contents macro to get what I want. As long as Confluence—possibly more accurately, my browser—can cope with the potentially large resultant page for viewing, it occurs to me that a macro that does something similar to the "Include Page" macro, but for all descendants of the current page, and that includes descendant page titles as headings, would do. ("Include Descendant Pages"? Or, in Confluence-speak, "Include Child Pages", with an "Include Descendants" checkbox?) Still, because of that "potentially large" issue, a TOC macro would be preferable.

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 13, 2018

With regards to your concern about many people working on the documentation you could create a page template that already has the TOC and excerpt included in the template so your users won't have to remember to put it in.

As for a macro that will include children I actually wrote a user macro a few years ago that does that very thing. Here is the code. Another option is the Include Content add-on in the marketplace. It has an include children macro as well. My user macro has a few more features

Macro Name:
include_children

Macro Title:
Include Children

Macro Body Processing:
No macro body

Template:

## Macro Name: include_children
## Visibility: 
## Macro Title: Include Children
## Description: This will include the content from from the child pages onto the parent page that this macro is placed on.
## Categories: Confluence content
## Icon URL: 
## Documentation URL: https://bitbucket.org/fredclown/confluence-user-macros
## Macro Body Processing: No macro body

##############################
##       Template:          ##
##############################
## Developed By: Davin Studer
## Date Created: 12/4/2014
## Updated by: Milo Grika
## Added: show/hide title, link/unlink title.
## Updated by: Gregor Mirai
## Date updated: 26/1/2016
## Added: page separator, PDF split page marker options.
## Updated by: Davin Studer
## Date updated: 4/13/2018
## Added: Include Descendants checkbox
## Updated by: Davin Studer
## Date updated: 12/3/2018
## Added: Added an option to specify the parent page.

## @param ShowTitle:title=Show Title|type=boolean|desc=Deselect to remove the title of the child page.|default=true
## @param LinkTitle:title=Link Title|type=boolean|desc=Select to turn the titles of the child pages into links to those pages (Show Title must be selected).|default=false
## @param IncludeDescendants:title=Include Descendants|type=boolean|desc=Select to include descendants.|default=false
## @param PageSeparator:title=Page Separator|type=boolean|desc=Separate pages with horizontal ruler.|default=false
## @param SplitPages:title=Split Pages|type=boolean|desc=Split pages marker for PDF export will be inserted.|default=false
## @param Collapsible:title=Collapsible|type=boolean|desc=Should the included pages be collapsible?|default=false
## @param FilterLabel:title=Filter on Page Label|type=string|desc=Include only subpages with the specified label.
## @param Page:title=Page title|type=confluence-content|required=false|desc=If not specified, the current page is used.
## @param Order:title=Order|type=enum|enumValues=Nav Order,Reverse Nav Order,Alphabetical,Reverse Alphabetical,Create Date,Reverse Create Date|default=nav order|desc=In what order should the child pages be displayed? Nav Order refers to the order of the child pages in the page tree. Reverse Nav Order simply reverses that.

#set( $containerManagerClass=$content.class.forName('com.atlassian.spring.container.ContainerManager') )
#set( $getInstanceMethod=$containerManagerClass.getDeclaredMethod('getInstance',null) )
#set( $containerManager=$getInstanceMethod.invoke(null,null) )
#set( $containerContext=$containerManager.containerContext )
#set( $pageManager=$containerContext.getComponent('pageManager') )

#if( $paramPage && $paramPage != "" )
    ##########################
    ## Find the page        ##
    ##########################
    #set( $parts = $paramPage.split(":") )
    #set( $i = 0 )
    #set ( $len = 0 )
    #set( $key = "" )
    #set( $name = "" )

    ##Having trouble finding out the length/size of $parts ... brute force it
    #foreach ( $part in $parts )
        #set ( $len = $len + 1 )
    #end
    
    #if ( $len == 1 )
        #set ( $key = $content.spaceKey )
        #set ( $name = $paramPage )
    #else
        #foreach ( $part in $parts )
            #if ( $i == 0 )
              #set ( $key = $part )
              #set ( $i = $i + 1 )
           #else
              #set ( $name = $part )
           #end
        #end
    #end

    #set ( $tempcontent = "" )
    #if ( $pageManager )
        #set ( $tempcontent = $pageManager.getPage($key, $name) )
    #end
    #if ( $tempcontent && $tempcontent != "" )
        #set ( $content = $tempcontent )
    #else
        The page "$paramPage" was not found in this space.
        #set ( $content = "" )
    #end
#end

#set( $data = "" )
#childPages ( $content.id )

#macro ( childPages $pageid )
    ## Find the array of (sorted) child pages.
    #if( $paramOrder == "Nav Order" || $paramOrder == "Reverse Nav Order" )
        #set( $children =  $pageManager.getPage($pageid).getSortedChildren() )
    #elseif( $paramOrder == "Alphabetical" || $paramOrder == "Reverse Alphabetical" )
        #set( $array =  $pageManager.getPage($pageid).getChildren() )
        ###############################################################
        ## Could not find a method to get them in Alphabetical order ##
        ## Must sort them myself ... Bubble sort                     ##
        ###############################################################
        #set( $size = $array.size() )
        #foreach( $junk in $array )
            #set( $count = -1 )
            #foreach( $line in $array )
                #set( $count = $count + 1 )
                #if( $velocityCount < $size )
                    #if( $line.getTitle().compareToIgnoreCase($array.get($velocityCount).getTitle()) > 0 )
                        #set( $tmp = $array.get($velocityCount) )
                        #set( $junk = $array.set($velocityCount, $line) )
                        #set( $junk = $array.set($count, $tmp) )
                    #end
                #end
            #end
        #end
        #set( $children = $array )
    #elseif( $paramOrder == "Create Date" || $paramOrder == "Reverse Create Date" )
        #set( $children =  $pageManager.getPage($pageid).getChildren() )
    #end
    
    ## Prepare children data. 
    #foreach( $child in $children )
        ## Create the map of labels for the page.
        #set( $labelsMap = {} )
        #foreach( $label in $child.getLabels() )
            #set( $dummy = $labelsMap.put($label.toString(), 1) )
        #end
    
        ## Include all pages (when no filter label is specified) or pages with a specific label.
        #if( !$paramFilterLabel || $paramFilterLabel && $labelsMap.containsKey($paramFilterLabel) )
            #set( $include = "" )
            #if ( $paramCollapsible == true )
                #set( $include = $include + '<ac:structured-macro ac:name="expand">' )
                #set( $include = $include + '<ac:parameter ac:name="title">' + $child.getTitle() + '</ac:parameter>' )
                #set( $include = $include + '<ac:rich-text-body>' )
                #set( $include = $include + '<p>' )
            #end
            #set( $include = $include + '<div class="included-child-page">' )
    
            ## Show title and links.
            #if( $paramShowTitle == true )
                #if( $paramLinkTitle == true )
                    #set( $include = $include + '<h1 class="included-child-page-title"><a href="' + $child.getUrlPath() + '">' + $child.getTitle() + '</a></h1>' )
                #else
                    #set( $include = $include + '<h1 class="included-child-page-title">' + $child.getTitle() + '</h1>' )
                #end
            #end
    
            ## Include sub page.
            #set( $include = $include + '<div class="included-child-page-body">' )
                #set( $include = $include + '<ac:structured-macro ac:name="include">' )
                    #set( $include = $include + '<ac:parameter ac:name="">' )
                        #set( $include = $include + '<ac:link>' )
                            #set( $include = $include + '<ri:page ri:content-title="' + $child.getTitle() + '" ri:space-key="' + $content.spaceKey + '"/>' )
                        #set( $include = $include + '</ac:link>' )
                    #set( $include = $include + '</ac:parameter>' )
                #set( $include = $include + '</ac:structured-macro>' )
            #set( $include = $include + '</div>' )
            #set( $include = $include + '</div>' )

            #if ( $paramCollapsible == true )
                #set( $include = $include + '</p>' )
                #set( $include = $include + '</ac:rich-text-body>' )
                #set( $include = $include + '</ac:structured-macro>' )
            #end
    
            ## Define sorting order.
            #set( $inOrder = false )
            #if( $paramOrder == "Nav Order" || $paramOrder == "Alphabetical" || $paramOrder == "Create Date" )
                #set( $inOrder = true )
            #end
    
            ## Include page separator or include page split marker used for PDF exports.
            #if( $paramPageSeparator == true )
                #set( $include = $include + '<hr/><br/>' )
            #end
            #if( $paramSplitPages == true )
                #set( $include = $include + '<div style="page-break-before:always;"></div>' )
            #end

            ## Add page content in order or in reverse order.
            #if( $inOrder == true )
                #set( $data = $data + $include )
            #else
                #set( $data = $include + $data)
            #end
        #end
        
        #if($paramIncludeDescendants == true)
            #childPages( $child.id )
        #end
    #end
#end

$data
 
<style type="text/css">
    .included-child-page {
        margin-bottom: 30px;
        overflow: hidden;
    }
</style>

 

Roman Joss
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.
January 29, 2019

Hi Davin

That's a great macro. I'm challanged with a requirement similar to this. 

Instead of showing the whole page, my users would like to see only the headings of the child-pages, where the links should be usable. Is this possible? Do you have a hint on which element i have to change or to enhance? 

Thanks for your help.

David E October 15, 2020

This seems to have stopped working for us after upgrading to Confluence 7.8.  Do you have an update by any chance?

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events