How to Edit Excerpts in Coveo Atomic for React

Learn how to edit excerpts

August 29, 2024

By Gorman Law

Coveo’s Excerpt Field

The excerpt field in Coveo is a segmented text generated at query time from the body of an item in a Coveo organization. When a query is performed, the excerpt yields relevant item body sections in which the queried terms are highlighted. From the Coveo glossary. For more information about how to control it, read this blog from one of Fishtank’s best.

React Implementation

Since the field is generated automatically, it’s as simple as adding field=”excerpt” to an Atomic element. Here’s a small snippet to get you started. Not all imports are listed.

import {
  ...
  AtomicSearchInterface,
  AtomicResultSectionExcerpt,
  AtomicResultText,
  Result,
  buildSearchEngine,
  ...
} from '@coveo/atomic-react';
...
<AtomicSearchInterface>
  ...
  <AtomicResultListdisplay="grid"
    template={(result)=> (
      <>
                <AtomicResultText field="title"></AtomicResultText>
        <AtomicResultSectionExcerpt >
            <AtomicResultText field="excerpt" />
        </AtomicResultSectionExcerpt>
      </>
    )}
  />
  ...
</AtomicSearchInterface>
...

I’ve styled it a little, but you get results like this:

Screenshot of a workflow test page displaying a date and a brief privacy notice.

It will highlight the word you searched for (eg. with)

Headline about the most popular news stories curated by readers.

Duplicate Title in Excerpt Problem

One issue with the automatic excerpts is that the title could be duplicated in the excerpt like these images below.

Headline about the rise and fall of a fintech company.

Headline about the most popular news stories curated by readers.

Headline about a matriarchal tax firm supporting businesses with social impact.

Let’s fix this by editing the excerpt.

The Solution

First let’s take a look at the Results import (result.d.ts). If you scroll down, you will notice two fields: title and excerpt.

/**
 * Contains the title of the item.
 */
title: string;
/**
 * The contextual excerpt generated for the item (see the excerptLength query parameter).
 *
 * @example
 * ... the new Coveo Cloud V2 and Coveo Cloud V1 ... the main differences between the two Coveo Cloud versions ...
 */
excerpt: string;

We can get these from the search results, and process them there. Add preprocessSearchResponseMiddleware to your search configuration options when building the search engine as shown in the code below. Read the comments for more information, and read the related Coveo documentation here.

let engine = buildSearchEngine({
      configuration: {
        accessToken: accessToken,
        organizationEndpoints: getOrganizationEndpoints(organizationName),
        organizationId: organizationName,
        search: {
          ...
          preprocessSearchResponseMiddleware: (response) => {
            // loop
            response.body.results.forEach((result: Result) => {
                // E.g., modify the result object
              ...             
              // getting title
              let titleString = result?.title as string;
              // getting excerpt
              let excerptString = result?.excerpt as string;

              // if the excerpt starts with the title, remove the titleString from the excerptString
              if(richTextExcerpt.startsWith(titleString)) {
                richTextExcerpt = richTextExcerpt.slice(titleString.length).trim();
              }

              // this will add an extra field into your result called modifiedExcerpt
              result.raw.modifiedExcerpt = richTextExcerpt;

              return result;
            });
            return response;
          },
        },
    },
}

Headline about the most popular news stories curated by readers.

Now that you’ve modified your excerpt, you can display it by changing field=”excerpt” to use the new modifiedExcerpt field like.

<AtomicSearchInterface>
  ...
  <AtomicResultListdisplay="grid"
    template={(result)=> (
      <>
                <AtomicResultText field="title"></AtomicResultText>
        <AtomicResultSectionExcerpt >
            <AtomicResultText field="modifiedExcerpt" />
        </AtomicResultSectionExcerpt>
      </>
    )}
  />
  ...
</AtomicSearchInterface>

After reloading your site, you should now see the new search results! You can do more to remove the starting ellipses if you wish.

Headline about the rise and fall of a fintech company.

Search Term Highlighting Issue

Unfortunately, the modified excerpt no longer has the search term highlighted. In this picture, I searched for ‘with’:

Headline about the most popular news stories curated by readers.

As you can see, the title is still removed from the excerpt, but with is not longer in bold. Here’s how we can fix that.

Adding Highlighting to Search Result Excerpt

Let’s take a look at results.d.ts again. There is a field named excerptHighlights which contains an array of HighlightKeyword objects

/**
 * The length and offset of each word to highlight in the item excerpt string.
 */
excerptHighlights: HighlightKeyword[];


export interface HighlightKeyword {
    /**
     * The 0 based offset inside the string where the highlight should start.
     */
    offset: number;
    /**
     * The length of the offset.
     */
    length: number;
}

For example if we had the following excerpt

"Working with dedication, she managed to complete the project with ease…"

excerptHighlights would look like this: {[8,4],[53,4]}

With that in mind, I’ve created a helper function to add in bold tags at those offsets. Take a look at the code below; the comments will explain more

/**
 * Adds highlights (tags of user's choice eg. <strong>) to a string of text based on offset and length
 * @param text - Text to be changed.
 * @param highlights - array of Highlight objects (length: number, offset: number)
 * @param startTag - starting tag eg. <b>
 * @param endTag - closing tag eg. </b>
 * @returns The CSS class for the image position.
 */
export function addHighlights(text: string, highlights: Highlight[], startTag: string, endTag: string): string{

    // reverse the order of the highlight array. This is so we add tags to the end of the string first.
    // If we add tags to the front first, the string indexes will be changed for the next set of tags
  highlights.sort((a, b) => b.offset - a.offset);

  highlights.forEach(({ length, offset }) => {
      // add tag to the end of the offset
    text = text.slice(0, offset + length) + endTag + text.slice(offset + length);
        // add tag to the start of the offset
    text = text.slice(0, offset) + startTag + text.slice(offset);
  });
  return text;
}

/**
 * Adds <b> tags to a string of text based on offset and length
 * @param text - Text to be changed.
 * @param highlights - array of Highlight objects (length: number, offset: number)
 * @returns The CSS class for the image position.
 */
export function addBoldTags(text: string, highlights: Highlight[]): string {
  return addHighlights(text, highlights, '<b>', '</b>');
}

In preprocessSearchResponseMiddleware , modify the code to call the new function

// modify excerpt
let titleString = result?.title as string;
let excerptString = result?.excerpt as string;
let richTextExcerpt = addBoldTags(excerptString, result.excerptHighlights);

if(richTextExcerpt.startsWith(titleString)) {
  richTextExcerpt = richTextExcerpt.slice(titleString.length).trim();
}
result.raw.modifiedExcerpt = richTextExcerpt;

Inside AtomicResultSectionExcerpt I’ve set up a function to return html text, and then output the raw html string, which has tags.

...
function createMarkup(text) {
  return {__html: text};
}

...
<AtomicResultSectionExcerpt class="descriptionSection">
  <p dangerouslySetInnerHTML={createMarkup(result.raw.modifiedExcerpt)}/>
</AtomicResultSectionExcerpt>
...

And here’s the end result! As you can see, the search term is bolded, and the title is included in the excerpt, because the search term was in the title.

Headline about the most popular news stories curated by readers.

Final Thoughts on Mastering Coveo’s Excerpt Field

This blog walked you through the steps of editing Coveo’s automatically generated excerpt field. Thank you for reading!



Gorman Headshot

Gorman Law

Full Stack Developer

Gorman is a Full Stack Developer and a University of Calgary alumni who has a background in Canada's financial industry. Outside of work, he likes to go rock climbing, try out new ice cream places, and watch sports.