Table of Contents (TOC) with anchor links

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

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

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();

3 Likes

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

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:

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

1 Like