yoastseo-dep
Version:
Yoast clientside page analysis
149 lines (130 loc) • 6.06 kB
JavaScript
import matchWords from "../match/matchTextWithArray";
import arrayToRegex from "../regex/createRegexFromArray";
import addMark from "../../../markers/addMarkSingleWord";
import Mark from "../../../values/Mark";
import { escapeRegExp } from "lodash-es";
import getAnchorsFromText from "../link/getAnchorsFromText";
// Regex to deconstruct an anchor into open tag, content and close tag.
const anchorDeconstructionRegex = /(<a[\s]+[^>]+>)([^]*?)(<\/a>)/;
/**
* Deconstructs an anchor to the opening tag and the content. The content is the anchor text.
* We don't return the closing tag since the value would always be the same, i.e. </a>.
*
* @param {string} anchor An anchor of the shape <a ...>...</a>.
*
* @returns {object} An object containing the opening tag and the content.
*/
export const deConstructAnchor = function( anchor ) {
// The const array mirrors the anchorDeconstructionRegex, using a comma to access the first element without a name.
const [ , openTag, content ] = anchor.match( anchorDeconstructionRegex );
return {
openTag: openTag,
content: content,
};
};
/**
* Reconstructs an anchor from an openTag, the content, and the closing tag.
*
* @param {string} openTag The opening tag of the anchor. Must be of the shape <a ...>.
* @param {string} content The text of the anchor.
*
* @returns {string} An anchor.
*/
export const reConstructAnchor = function( openTag, content ) {
return `${openTag}${content}</a>`;
};
/**
* Gets the anchors and marks the anchors' text if the words are found in it.
*
* @param {string} sentence The sentence to retrieve the anchors from.
* @param {RegExp} wordsRegex The regex of the words.
*
* @returns {Object} The anchors and the marked anchors.
*/
const getMarkedAnchors = function( sentence, wordsRegex ) {
// Retrieve the anchors.
const anchors = getAnchorsFromText( sentence );
// For every anchor, apply the markings only to the anchor tag.
const markedAnchors = anchors.map( anchor => {
// Retrieve the open tag and the content/anchor text.
const { openTag, content } = deConstructAnchor( anchor );
// Apply the marking to the anchor text if there is a match.
const markedAnchorText = content.replace( wordsRegex, ( x ) => addMark( x ) );
// Create a new anchor tag with a (marked) anchor text.
return reConstructAnchor( openTag, markedAnchorText );
} );
return { anchors, markedAnchors };
};
/**
* Adds marks to a sentence and merges marks if those are only separated by a space
* (e.g., if highlighting words "ballet" and "shoes" in a sentence "I have a lot of ballet shoes and other paraphernalia."
* the marks will be put around "ballet shoes" together, not "`ballet` `shoes`".)
*
* @param {string} sentence The sentence to mark words in.
* @param {[string]} wordsFoundInSentence The words to mark in the sentence.
* @param {function} matchWordCustomHelper The language-specific helper function to match word in text.
*
* @returns {string} The sentence with marks.
*/
export const collectMarkingsInSentence = function( sentence, wordsFoundInSentence, matchWordCustomHelper ) {
wordsFoundInSentence = wordsFoundInSentence.map( word => escapeRegExp( word ) );
// If a language has a custom helper to match words, we disable the word boundary when creating the regex.
const wordsRegex = matchWordCustomHelper ? arrayToRegex( wordsFoundInSentence, true ) : arrayToRegex( wordsFoundInSentence );
// Retrieve the anchors and mark the anchors' text if the words are found in the anchors' text.
const { anchors, markedAnchors } = getMarkedAnchors( sentence, wordsRegex );
let markup = sentence.replace( wordsRegex, function( x ) {
return addMark( x );
} );
/**
* In 'markup', we apply the markings also inside the anchor's attribute if there is a match, on top of
* marking the anchor's text.
* The step below is to replace the incorrectly marked anchors with the marked anchors that we want:
* where the markings are only applied in the anchor's text.
*/
if ( anchors.length > 0 ) {
const markupAnchors = getAnchorsFromText( markup );
for ( let i = 0; i < markupAnchors.length; i++ ) {
markup = markup.replace( markupAnchors[ i ], markedAnchors[ i ] );
}
}
/*
* If two marks are separated by only a space, remove the closing tag of the first mark and the opening tag of the
* second mark so that the two marks can be combined into one.
*/
return ( markup.replace( new RegExp( "</yoastmark> <yoastmark class='yoast-text-mark'>", "ig" ), " " ) );
};
/**
* Adds marks to a sentence.
*
* @param {string} sentence The sentence in which we want to apply highlighting.
* @param {Array} wordsFoundInSentence The words to highlight in a sentence.
* @param {function} matchWordCustomHelper The language-specific helper function to match word in text.
* @returns {Mark[]} The array of Mark objects of each sentence.
*/
export function markWordsInASentence( sentence, wordsFoundInSentence, matchWordCustomHelper ) {
return [ new Mark( {
original: sentence,
marked: collectMarkingsInSentence( sentence, wordsFoundInSentence, matchWordCustomHelper ),
} ) ];
}
/**
* Adds marks to an array of sentences.
*
* @param {[string]} wordsToMark The words to mark.
* @param {[string]} sentences The sentences in which to mark these words.
* @param {string} locale The locale.
* @param {function} matchWordCustomHelper The language-specific helper function to match word in text.
*
* @returns {[string]} The sentences with marks.
*/
export function markWordsInSentences( wordsToMark, sentences, locale, matchWordCustomHelper ) {
let wordsFoundInSentence = [];
let markings = [];
sentences.forEach( function( sentence ) {
wordsFoundInSentence = matchWords( sentence, wordsToMark, locale, matchWordCustomHelper ).matches;
if ( wordsFoundInSentence.length > 0 ) {
markings = markings.concat( markWordsInASentence( sentence, wordsFoundInSentence, matchWordCustomHelper ) );
}
} );
return markings;
}