# Table of Contents (TOC) with anchor links

**URL:** <https://community.fibery.io/t/table-of-contents-toc-with-anchor-links/6011>\
**Category:** Fibery Showcase\
**Created:** [March 23, 2024, 5:50pm UTC](https://community.fibery.io/t/table-of-contents-toc-with-anchor-links/6011 "2024-03-23T17:50:27Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![Yuri\_BC](https://sea2.discourse-cdn.com/flex020/user_avatar/community.fibery.io/yuri_bc/32/8803_2.png) [@Yuri\_BC](https://community.fibery.io/u/Yuri_BC)\
**Post date:** [March 23, 2024, 5:50pm UTC](https://community.fibery.io/t/table-of-contents-toc-with-anchor-links/6011/1 "2024-03-23T17:50:27Z")

</div>

The script generates and appends a Table of Contents (TOC) for a rich text field (Description). It works by fetching the text, identifying headings and GUID anchor links to construct a TOC, and then updating the text with this TOC added at the beginning.

In the gif I show two button automations, with and without ‘Callout’.

![chrome_4vWWRtma7Z](https://us1.discourse-cdn.com/flex020/uploads/fibery/original/2X/7/733c82fe072ce43f689f0a9abfc5f4f31c11a934.gif)

## Explanation

**Note** : I chose to use JSON fetching and saving because directly manipulating the document content via JSON ensures that the existing GUIDs in the headers remain unchanged.

### Step 1: Initialization and Current Entity Details Retrieval

- The script starts by accessing the Fibery API to and identifies the database of interest (`pageType`) and establishes the base URL for constructing anchor links.
- It retrieves details of the current entity, focusing on the `Description` field with its unique Document ID and secret. The `Public Id` of the current entity is also retrieved, which is crucial for constructing anchor links within the TOC.

### Step 2: Generating the Table of Contents (TOC)

- Utilizing the Document ID and secret of the `Description` field, the script fetches the current content of the Document entity associated with the current entity’s rich text field.
- It then proceeds to generate the TOC by analyzing the headings within the document content, calling upon `createHierarchicalTOCJson`.

### Step 3: TOC Construction

- The TOC is initiated with a “Table of Contents” title in bold, followed by a line break to separate it from subsequent entries.
- As the script iterates through each block of the document content, it identifies headings and creates corresponding TOC entries. Each entry:
  - Is prefixed with a bullet point, where indentation is applied based on the heading level to mirror the document’s structure hierarchically. This indentation is calculated by adding spaces, adjusting for the heading’s level.
  - Includes a clickable link composed of the heading’s text and an anchor link crafted using the `Public Id` of the current entity and the heading’s unique identifier (`guid`).
  - Is separated by line breaks (`hard_break`), ensuring each TOC entry appears on a new line, maintaining readability.

### Step 4: Document Update with TOC

- After assembling the TOC, the script prepares a new JSON structure for the document, combining the TOC (assembled as a cohesive paragraph with entries and breaks) with the original content.
- This new document content is then saved back to the `Description` rich text field of the current entity, updating it with the TOC at the forefront, using the dedicated Document ID and secret for the update operation.

## Script

```auto
const fibery = context.getService('fibery');
const pageType = 'YourSpace/DataBaseName'; // Replace with your actual database name
const baseUrl = 'https://youraccount.fibery.io/YourSpace/DatabaseName/'; // Your Fibery instance base URL

async function generateTOCAndAppend() {
    try {
        const currentPageEntity = args.currentEntities[0];
        const pageEntity = await fibery.getEntityById(pageType, currentPageEntity.id, ['Description', 'Public Id']);

        if (pageEntity.Description && pageEntity.Description.Secret) {
            let descriptionContentJson = await fibery.getDocumentContent(pageEntity.Description.Secret, 'json');
            const tocContentJson = createHierarchicalTOCJson(descriptionContentJson, pageEntity['Public Id']);

            let updatedDescriptionContentJson = {
                ...descriptionContentJson,
                doc: {
                    ...descriptionContentJson.doc,
                    content: [...tocContentJson, ...descriptionContentJson.doc.content]
                }
            };

            await fibery.setDocumentContent(pageEntity.Description.Secret, JSON.stringify(updatedDescriptionContentJson), 'json');
        }
    } catch (error) {
        console.error('Error in script:', error);
    }
}

function createHierarchicalTOCJson(descriptionContentJson, publicId) {
    const tocEntries = [{
        type: "paragraph",
        content: [{
            type: "text",
            text: "Table of Contents",
            marks: [{ type: "strong" }]
        }, {
            type: "hard_break"
        }]
    }];

    let tocContentArray = tocEntries[0].content; // Use the content array of the first (and only) paragraph.

    descriptionContentJson.doc.content.forEach((block, index, array) => {
        if (block.type === 'heading' && block.attrs && block.content) {
            const level = block.attrs.level;
            const textContent = block.content.map(element => element.text || '').join('');
            const anchorLink = `${baseUrl}${publicId}/anchor=${block.attrs.guid}`;

            // Adjusting to always include a bullet point
            let prefix = " ".repeat((level - 1) * 4);
            let bulletPoint = "• ";
            let prefixContent = `${prefix}${bulletPoint}`;

            // Add a hard break before every entry except the first
            if (tocContentArray.length > 2) { // Accounts for title and initial break
                tocContentArray.push({
                    type: "hard_break"
                });
            }

            // Add prefix content (bullet point)
            tocContentArray.push({
                type: 'text',
                text: prefixContent
            });

            // Add the clickable text content
            tocContentArray.push({
                type: 'text',
                text: textContent,
                marks: [{
                    type: 'link',
                    attrs: { href: anchorLink }
                }]
            });
        }
    });

    return tocEntries; 
}

await generateTOCAndAppend();

```

---

<div class="post-metadata">

**Author:** ![Yuri\_BC](https://sea2.discourse-cdn.com/flex020/user_avatar/community.fibery.io/yuri_bc/32/8803_2.png) [@Yuri\_BC](https://community.fibery.io/u/Yuri_BC)\
**Post date:** [March 23, 2024, 8:22pm UTC](https://community.fibery.io/t/table-of-contents-toc-with-anchor-links/6011/2 "2024-03-23T20:22:48Z")

</div>

## Additional feature: Set max TOC depth

You can make the TOC configurable to show the number of levels deep, through a user input form in the automation.

![chrome_JR6MNGilU3](https://us1.discourse-cdn.com/flex020/uploads/fibery/original/2X/1/15706346f68fa0d21ca83d8e3c22edd4d0652db6.gif)

This requires you to add an automation step before the script that gets user input. This can be any field you like in the current entity or a dedicated Setting entity:

 ![chrome_0eRvmkPrRK](https://us1.discourse-cdn.com/flex020/uploads/fibery/original/2X/3/347712783c513ff97a3cca1f64b64bca0cda4c54.png)

If you are interested in that, I can share the script of that automation too.
