Phrase selection UI

Alexandria’s ability to allow users select and save phrases along with its translation is one of the key features that sets her apart from other open-source or commercial products on the market. This particular functionality was challenging to implement due to the way texts are displayed.

Each word in a user text resides within its own span. When user clicks on a word, it is set as the currentWord state, triggering a re-render of the translation input component, which contains the current word, existing translations, links to dictionaries and translations, as well as the option for user to set their level of familiarity with the word.

Phrases on the other hand, are components separate from words. They are also surrounded by highlighted spans based on a user’s level of familiarity with the phrase. We needed a way to create phrases on the fly when a range of words was selected. Such range ought to be added to the userWords state, which would then automatically trigger the highlighting of the new phrase across the text.

First we had to settle on how to select a range of words, and debated two options:

  1. Selecting the text itself. This would bring the challenge of handling incomplete word selections. Consider, for example, the phrase “better the devil you know”. If a user started and/or ended their selection in the middle of a word, the currentWord might end up being displayed as “er the devil you kn” instead of the intended phrase. It was very important to us that this kind of “sloppy” selection was possible to enhance the user experience instead of forcing users to carefully point the cursor (or worse: their finger) at the exact start and end of the words.
  2. Select the span elements containing the words. From a UI perspective we were drawn towards this approach because the span elements are the smallest units of our text, and we would not have the problem of incomplete words.

But while we got a prototype of element selection working on desktop browsers, the problems on mobile browsers were unsurmountable within our given time frame. We turned towards developing a solution using text selection instead.

Using the browser API Selection, we were able to leverage anchorNode and focusNode to access where the selection started and ended. The next step was to determine whether the selection was forward (left to right) or backward (right to left), because this would influence how the selection had to be extended to complete words. This was done by spanning a new Range between anchorNode and focusNode and checking whether it was collapsed (meaning the selection was backwards). The selection would be extended in the appropriate directions using the setBaseAndExtend method.

Finally, the textContent of the Nodes within the selection was extracted and set as the new phrase to be added to the UserWords state.