<a href="#:~:text=sit-,amet,-auctor">Both prefix and suffix</a>
</li>
<li>
<a href="#:~:text=word?-,Not%20sure.">Text making up a whole, reoccurring block element</a>
<a href="#:~:text=GATTACAGACTGCGATACGT-,TACT,-AGTTAGGACTACGGGATCATATTAC">Point at letters within a word.</a> (possible since <a href="https://github.com/WICG/scroll-to-text-fragment/pull/148">spec update of October 2020</a>; requires quoting the whole word in prefix&suffix)
</li>
<li>
<a href="#:~:text=Fusce%20quis,est.">Select text between two given phrases</a>
@@ -97,13 +97,13 @@
<a href="#:~:text=Aliquam%20urna,scelerisque.">Text between two phrases in different block elements</a>
</li>
<li>
<a href="#:~:text=Phasellus%20tempus%20dui&text=venenatis%20leo&text=ipsum,-eget">Multiple pieces of text (only works in Firefox)</a>
<a href="#:~:text=GATTACA,CATATTAC">Select text between start and end of a long, uninterrupted string</a> <a href="https://github.com/WICG/scroll-to-text-fragment/issues/137">(not possible)</a>
</li>
<li>
<a href="#:~:text=GATTACA,CATATTAC">Select text between start and end of a long, uninterrupted string <em>(fails)</em></a>
<a href="#:~:text=And more.-,And more.">Text making up a whole, reoccurring block element</a>
</li>
<li>
<a href="#:~:text=poi-,i,-nt">Point at letters (e.g. a typo) within a word <em>(fails)</em></a>
<a href="#:~:text=Phasellus%20tempus%20dui&text=venenatis%20leo&text=ipsum,-eget">Multiple pieces of text (only works in Firefox)</a>
</li>
</ul>
<p>
@@ -134,16 +134,16 @@
Vestibulum eu tristique elit. Sed ac ipsum sed sapien ultricies dapibus. Cras efficitur aliquet luctus. Aliquam sit amet auctor tellus, nec rhoncus nisl. Integer at lobortis sapien. Nunc mattis tristique libero, sed ultrices nunc imperdiet eu. Pellentesque accumsan, eros non auctor eleifend, felis massa bibendum lacus, non venenatis orci sapien eu sapien. Integer eu eros fringilla lectus vestibulum aliquam. Cras consectetur nunc nisi, vel molestie justo congue at. Duis eros neque, semper ut commodo in, molestie ut nunc. Vivamus vitae bibendum magna, suscipit sollicitudin elit. Praesent id rhoncus enim, sit amet suscipit velit. Aenean euismod purus velit, et consectetur nulla gravida vitae.
</p>
<p>
The Chrome-osome is GATTACAGACTGCGATACGTTACTAGTTAGGACTACGGGATCATATTAC. Can we select it without quoting the whole thing?
The Chrome-osome is GATTACAGACTGCGATACGTTACTAGTTAGGACTACGGGATCATATTAC. Can we select it without quoting the whole thing? Nope.
</p>
<p>
Not sure.
More.
</p>
<p>
And how to poiint at letters (e.g. a typo) within a word?
And more.
</p>
<p>
Not sure.
And more.
</p>
<p>
Finally, <a href="#:~:text=Text%20Fragments%20playground">a link to scroll back up.</a>
// An implementation of (most of) the Text Fragments draft spec.
// See https://wicg.github.io/scroll-to-text-fragment/
// Based on the version of 28 August 2020. <https://raw.githubusercontent.com/WICG/scroll-to-text-fragment/2475249f76ba20a6e6272e91853001155fdc3d22/index.html>
// Based on the version of 22 October 2020. <https://raw.githubusercontent.com/WICG/scroll-to-text-fragment/1c05e62b77f8f141d567dd287a2a42ea74870552/index.html>
import {
@@ -441,8 +441,8 @@ export function findRangeFromTextDirective(parsedValues: ParsedTextDirective, do
let potentialMatch = null;
// 2. “If parsedValues’s prefix is not null:”
if (parsedValues.prefix !== null) {
// 1. “Let prefixMatch be the the result of running the find a string in range steps given parsedValues’s prefix and searchRange”.
// 1. “Let prefixMatch be the the result of running the find a string in range steps with query parsedValues’s prefix, searchRange searchRange, wordStartBounded true and wordEndBounded false.”
// 9. “Set potentialMatch to the result of running the find a string in range steps with query parsedValues’s textStart, searchRange matchRange, wordStartBounded false, and wordEndBounded mustEndAtWordBoundary.”
// 2. “Set potentialMatch to the result of running the find a string in range steps with query parsedValues’s textStart, searchRange searchRange, wordStartBounded true, and wordEndBounded mustEndAtWordBoundary.”
// 3. “Let textEndMatch be the result of running the find a string in range steps with query parsedValues’s textEnd, searchRange textEndRange, wordStartBounded true, and wordEndBounded mustEndAtWordBoundary.”
// 9. “Let suffixMatch be result of running the find a string in range steps with query parsedValues’s suffix, searchRange suffixRange, wordStartBounded false, and wordEndBounded true.”
// 7. “Run the find a range from a node list steps given query, searchRange, and textNodeList, as input. If the resulting range is not null, then return it.”
// 7. “Run the find a range from a node list steps given query, searchRange, textNodeList, wordStartBounded and wordEndBounded as input. If the resulting range is not null, then return it.”
// “To find a range from a node list given a search string queryString, a range searchRange, and a list of Text nodes nodes, follow the steps”
export function findARangeFromANodeList(queryString: string, searchRange: Range, nodes: Text[]): Range | null {
// “To find a range from a node list given a search string queryString, a range searchRange, a list of Text nodes nodes, and booleans wordStartBounded and wordEndBounded, follow these steps:”
export function findARangeFromANodeList(queryString: string, searchRange: Range, nodes: Text[], wordStartBounded: boolean, wordEndBounded: boolean): Range | null {
// 1. “Let searchBuffer be the concatenation of the data of each item in nodes.”
@@ -828,8 +837,12 @@ export function findARangeFromANodeList(queryString: string, searchRange: Range,
// XXX Assert start and end are non-null? (should be correct, as matchIndex and endIx are both less than the search text’s length)
// 6. “If the substring of searchBuffer starting at matchIndex and of length queryString’s length is not word bounded, given the language from each of start and end’s nodes as the startLocale and endLocale:”
if (!isWordBounded(searchBuffer, matchIndex, queryString.length, languageOf(start[0]), languageOf(end[0]))) {
// 6. “If wordStartBounded is true and matchIndex is not at a word boundary in searchBuffer, given the language from start’s node as the locale; or wordEndBounded is true and matchIndex + queryString’s length is not at a word boundary in searchBuffer, given the language from end’s node as the locale:”
// “To determine if a substring of a larger string is word bounded, given a string text, an integer startPosition, number count, and locales startLocale and endLocale, follow these steps:”
// “startLocale and endLocale must be a valid [BCP47] language tag, or the empty string. An empty string indicates that the primary language is unknown.” <https://tools.ietf.org/html/bcp47>
// XXX Is this, or should this be a step? (should locale strings be validated?)
// “A word boundary is defined in [UAX29] in Unicode Text Segmentation §Word_Boundaries. Unicode Text Segmentation §Default_Word_Boundaries defines a default set of what constitutes a word boundary, but as the specification mentions, a more sophisticated algorithm should be used based on the locale.”
// 2. “If the first code point of text following left bound is not at position startPosition return false.”
if (leftBound !== startPosition) // We should be able to assume leftBound is not inside a multi-unit code point.
// “A locale is a string containing a valid [BCP47] language tag, or the empty string. An empty string indicates that the primary language is unknown.”
// (the locale type is defined in ./common.ts and imported above)
// 3. “Let endPosition be (startPosition + count − 1).”
// From <https://wicg.github.io/scroll-to-text-fragment/#word-bounded>:
// “A word boundary is defined in [UAX29] in Unicode Text Segmentation §Word_Boundaries. Unicode Text Segmentation §Default_Word_Boundaries defines a default set of what constitutes a word boundary, but as the specification mentions, a more sophisticated algorithm should be used based on the locale.”
// TODO Look into the referenced unicode spec.
// TEMP Just use regular expression’s word boundaries, whitespace, and the string’s start and end.
const allBoundaries = [...text.matchAll(/^|\b|\s|$/g)].map(match => match.index as integer);
if (direction === 'before') {
// Find the last match before position. Sure to exist because we also match the start of the string.
allBoundaries.reverse();
return allBoundaries.find(boundaryPosition => boundaryPosition <= position) as number;
} else {
// Find the first match after position. Sure to exist because we also match the end of the string.
return allBoundaries.find(boundaryPosition => boundaryPosition > position) as number;