/texthighlighter

TextHighlighter allows you to highlight text on web pages.

Primary LanguageJavaScriptOtherNOASSERTION

TextHighlighter

TextHighlighter allows you to highlight text on web pages. Just select it!

Getting started

Using the library as an npm package

Install the library by running:

$ npm install @perlego/text-highlighter

Then you can use it like so:

import TextHighlighter from '@perlego/text-highlighter';

// Example using a React ref if you are building a react application.
const highlighter = new TextHighlighter(sandboxRef.current);

// Example using an element accessed directly from the DOM.
const highlighter = new TextHighlighter(document.getElementById("sandbox"));

CommonJS with Node environments

const TextHighlighter = require('@perlego/text-highlighter/cjs');

// Example using a React ref if you are building a react application.
const highlighter = new TextHighlighter(sandboxRef.current);

// Example using an element accessed directly from the DOM.
const highlighter = new TextHighlighter(document.getElementById("sandbox"));

Supporting IE11

One thing to note when using this library as a package, you are responsible for providing the polyfills for the core JavaScript features outlined in the latest ECMAScript specifications.

Within this library we use features such as Array.prototype.includes and Array.prototype.find which aren't support by IE11 without polyfills.

You will need to install core-js and regenerator-runtime.

npm install --save core-js regenerator-runtime

You can add the following two lines at the BEGINNING of your application's entrypoint file (index.js or main.js):

import "core-js/stable";
import "regenerator-runtime/runtime";

Using the library as a script file

Clone down this repository, checkout to the release tag representing the version you would like to use. (2.x.x + only, this won't work for 1.x.x, please refer to the documentation provided with 1.x.x releases)

Ensure grunt is installed globally:

npm install -g grunt

Build the minified version of the library using the following command:

npm run build

Copy the script file from build/prod/TextHighlighter.min.js to the head section of your web page:

<script type="text/javascript" src="TextHighlighter.min.js"></script>

And use it!

var hltr = new TextHighlighter(document.body);

Features

  • Highlighting of selected text.
  • Highlighting all occurrences of given text (find & highlight).
  • Removing highlights.
  • Selecting highlight color.
  • Serialization & deserialization.
  • Focusing & deselecting overlapping highlights.
  • Works well in iframes.
  • Keeps DOM clean.
  • No dependencies (apart from core-js and regenerator-runtime for IE11). No jQuery or other libraries needed.

Using the library

You can find the API reference here which details the interface for the highlighter and the utility functionality that you could also make use of in your project.

Simple example

import TextHighlighter from '@perlego/text-highlighter';
import { isDuplicate } from './utils'; 
import highlightsApi from './services/highlights-api';

class ArticleView {
  constructor(data) {
    this.data = data;
    const pageElement = document.getElementById("article");
    this.highlighter = new TextHighlighter(
      pageElement, 
      {
        version: "independencia",
        onBeforeHighlight: this.onBeforeHighlight,
        onAfterHighlight: this.onAfterHighlight,
        preprocessDescriptors: this.preprocessDescriptors,
        onRemoveHighlight: this.onRemoveHighlight
    });
  }

  onBeforeHighlight = (range) => {
    return !isDuplicate(range)
  }

  onRemoveHighlight = (highlightElement) => {
    const proceed = window.confirm("Are you sure you want to remove this highlight?");
    return proceed;
  }

  preprocessDescriptors = (range, descriptors, timestamp) => {
    // Add an ID to the class list to identify each highlight 
    // (A highlight can be represented by a group of elements in the DOM).
    const uniqueId = `hlt-${Math.random()
      .toString(36)
      .substring(2, 15) +
      Math.random()
      .toString(36)
      .substring(2, 15)}`;
    
    const descriptorsWithIds = descriptors.map(descriptor => {
      const [wrapper, ...rest] = descriptor;
      return [
        wrapper.replace(
          'class="highlighted"',
          `class="highlighted ${uniqueId}"`
        ),
        ...rest
      ];
    });

    return { descriptors: descriptorsWithIds, meta: { id: uniqueId } };
  }

  onAfterHighlight = (range, descriptors, timestamp, meta) => {
    highlightsApi.saveBatch(meta.id, descriptorsWithIds)
      .then((result) => {
        // Do something with the highlights that have been saved.
      })
      .catch((err) => console.error(err));
  }

  render = () => {
    // Code that takes the data for the article and adds it to the DOM
    // based on a html template here.
  }
}

Example disabling default event binding

For the case where you want to trigger the process of creating highlights from a custom event fired in your application you can do something like the following:

import TextHighlighter from '@perlego/text-highlighter';
import { isDuplicate } from './utils'; 
import highlightsApi from './services/highlights-api';

class ArticleView {
  constructor(data) {
    this.data = data;
    const pageElement = document.getElementById("article");
    this.createButton = document.getElementById("create-highlight");
    this.highlighter = new TextHighlighter(
      pageElement, 
      {
        version: "independencia",
        useDefaultEvents: false,
        onBeforeHighlight: this.onBeforeHighlight,
        onAfterHighlight: this.onAfterHighlight,
        preprocessDescriptors: this.preprocessDescriptors,
        onRemoveHighlight: this.onRemoveHighlight
    });

    // Add your custom event handler.
    this.highlightHandler = highlighter.highlightHandler.bind(highlighter);
    createButton.addEventListener("click", this.highlightHandler);
  }

  // Your custom method that ensures the event handler is removed from the button click.
  destroy() {
    createButton.removeEventListener("click", this.highlightHanlder);
  }

  onBeforeHighlight = (range) => {
    return !isDuplicate(range)
  }

  onRemoveHighlight = (highlightElement) => {
    const proceed = window.confirm("Are you sure you want to remove this highlight?");
    return proceed;
  }

  preprocessDescriptors = (range, descriptors, timestamp) => {
    // Add an ID to the class list to identify each highlight 
    // (A highlight can be represented by a group of elements in the DOM).
    const uniqueId = `hlt-${Math.random()
      .toString(36)
      .substring(2, 15) +
      Math.random()
      .toString(36)
      .substring(2, 15)}`;
    
    const descriptorsWithIds = descriptors.map(descriptor => {
      const [wrapper, ...rest] = descriptor;
      return [
        wrapper.replace(
          'class="highlighted"',
          `class="highlighted ${uniqueId}"`
        ),
        ...rest
      ];
    });

    return { descriptors: descriptorsWithIds, meta: { id: uniqueId } };
  }

  onAfterHighlight = (range, descriptors, timestamp, meta) => {
    highlightsApi.saveBatch(meta.id, descriptorsWithIds)
      .then((result) => {
        // Do something with the highlights that have been saved.
      })
      .catch((err) => console.error(err));
  }

  render = () => {
    // Code that takes the data for the article and adds it to the DOM
    // based on a html template here.
  }
}

Options

See the TextHighlighter API Reference for the list of all the options, their default values and detailed descriptions.

Compatibility

Should work in all decent browsers and IE 11.

Running the tests

First run npm install from the root directory of the repo to install all the test runner dependencies.

To run both integration and unit tests at once use the following:

npm run test:all

Integration tests

The integration tests covers the integration of the larger components that make up the highlighting functionality such as serialisation + deserialisation, focusing, deselecting, normalisation and interaction with callbacks.

To run the integration tests use the following command:

npm run test:integration

Unit tests

The unit tests cover functions that make up the smaller components that query, manipulate the DOM along with pure utility pieces.

To run the unit tests use the following command:

npm run test:unit

Running the Primitivo tests (The first version of the highlighter)

The first version of the highlighter contains tests in a standalone jasmine runner that runs in the browser.

To run those tests, first set up the server:

node webserver.js

Then go to http://localhost:5002/test/test.html and the tests will run on page load.

Building the library for NodeJS (CommonJS modules)

There is a cjs version of the library that gets tracked in version control that is built whenever changes are made.

This is done automatically through a commit hook whenever you make a commit to the repo.

If you need to build this version of the library manually, you can use the following:

npm run build:cjs

And then commit the changes in the cjs folder to version control.

Building the API reference documentation

Ensure all dev dependencies are installed using:

npm install

Ensure grunt is installed globally:

npm install -g grunt

To build the documentation, run the following command:

grunt jsdoc

Ensure the test server is running:

node webserver.js

Then go to http://localhost:5002/doc to see the API reference for the library.

Demos

Documentation

You may check API reference or Wiki pages on GitHub.