DigitalConsumer.ISS/src/helpers/cms-content-helper.js
2023-04-06 14:04:44 -04:00

441 lines
16 KiB
JavaScript

import { dynamicStrings } from '@/constants/dynamic-strings';
import { useMainStore } from '@/store';
export function fetchCmsContentForPage(issPage) {
const store = useMainStore()
const clientName = store.issConfig.clientName;
const accountNumber = store.issConfig.accountNumber;
const clientOverride = (clientName.length > 0 && accountNumber > 0);
return store.getPageData(issPage)
.then(
// Get the base/default page first.
(baseResponse) => {
if ( !clientOverride )
{
// Return the base page if there are no client override.
return processPageData(baseResponse, null);
}
else {
// Else get the client override page.
const pageName = issPage + '_' + clientName.toLowerCase().replace(/ /g, '');
return store.getPageData(pageName)
.then(
(clientResponse) => {
// Process the client override if it exists.
return processPageData(baseResponse, clientResponse);
},
(error) => {
console.error(error);
// Process the just the base if no client override exists.
return processPageData(baseResponse, null);
}
);
}
}
);
};
// Support method for processing the page data from the CMS call.
// widgets = current widget collection used by page.
// baseResponse = contains the widgets from the base page.
// clientResponse = contains the widgets from the client override page. (null if none)
function processPageData(baseResponse, clientResponse) {
const pageDataFromCms = {};
let widgets = [];
if (!baseResponse?.data?.Result) {
console.error('No result data found'); // Something has gone terribly wrong.
return {}
}
if (!clientResponse?.data?.Result) {
widgets = baseResponse.data.Result;
}
else
{
// Override any base widgets with the client override widgets if found.
baseResponse.data.Result.forEach((baseWidget) => {
let found = false;
clientResponse.data.Result.forEach((clientWidget) => {
if ( clientWidget.Name == baseWidget.Name )
{
widgets.push(clientWidget);
found = true;
}
});
if ( !found )
{
widgets.push(baseWidget);
}
});
// Add any unique client widgets that does not exist in the current main widgets collection.
clientResponse.data.Result.forEach((clientWidget) => {
let found = false;
widgets.forEach((currentWidget) => {
if ( clientWidget.Name == currentWidget.Name )
{
found = true;
}
});
if ( !found )
{
widgets.push(clientWidget);
}
});
}
widgets.forEach((widget) => {
// Global state value replacement.
let widgetWithReplacements = findAndReplaceGlobalStateValues(
widget.Model,
widget.Name
);
// If we already have this widget, push it on the collection
if (widgetWithReplacements.Name in pageDataFromCms) {
pageDataFromCms[widgetWithReplacements.Name].push(
widgetWithReplacements.Model
);
return;
}
pageDataFromCms[widgetWithReplacements.Name] = [
widgetWithReplacements.Model,
];
});
Object.keys(pageDataFromCms).forEach((key) => {
if (pageDataFromCms[key].length === 1) {
pageDataFromCms[key] = pageDataFromCms[key][0];
}
});
return pageDataFromCms;
}
// Parent function for processWidgetItemForReplacement. This will loop through the parent
// object and pass any objects that need additional processing to the processWidgetItemForReplacement function.
function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
const objWithReplacements = {
Name: widgetName,
Model: {},
};
Object.keys(widgetModel).forEach((key) => {
let modelWithReplacements = processWidgetItemForReplacement(
widgetModel,
key
);
objWithReplacements.Model[key] = modelWithReplacements;
});
return objWithReplacements;
}
// This function will process the widget item and replace any global state variables with their values.
// This is a recursive function, it will call itself until it runs out of items to iterate on given the object.
function processWidgetItemForReplacement(widgetModel, key) {
// If we have a string, and it needs to be replaced.
if (typeof widgetModel[key] === 'string') {
widgetModel[key] = processIfStatements(
widgetModel[key],
dynamicStrings.GLOBAL_STATE,
getStoreValueFromString
);
if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) {
widgetModel[key] = mapStringToState(widgetModel[key]);
}
if (widgetModel[key].includes(dynamicStrings.MODAL_LINK)) {
widgetModel[key] = mapStringToModal(widgetModel[key]);
}
return widgetModel[key];
}
// If we have an object. array, etc
if (
typeof widgetModel[key] === 'object' &&
Object.keys(widgetModel[key]).length
) {
Object.keys(widgetModel[key]).forEach((item) => {
processWidgetItemForReplacement(widgetModel[key], item);
});
return widgetModel[key];
}
// If we have something else like a number, boolean, etc. just return it
return widgetModel[key];
}
function mapStringToModal(str) {
let startIndex = str.indexOf('{' + dynamicStrings.MODAL_LINK);
let linkToReplace = str.substring(startIndex, str.length);
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);
let params = linkToReplace.substring((dynamicStrings.MODAL_LINK).length + 2, linkToReplace.length -1)
let splitParams = params.split(',');
let bodyText = '<a href="#!" data-bs-toggle="modal" data-bs-target="#' + splitParams[0] + '" aria-label="Modal window">' + splitParams[1] + '</a>';
return str.replace(linkToReplace, bodyText);
}
// Function to convert a string, into a matching global state item.
function mapStringToState(str) {
// Pull all matches out of the string.
const regexExp = new RegExp('{(.*?):(.*?)}', 'g');
const regexMatches = [...str.matchAll(regexExp)];
const globalStateMatches = regexMatches.filter(match => {
return match[1] === dynamicStrings.GLOBAL_STATE;
});
// Our final string value that will be built from the matches.
let stringBuilder = '';
for (const match of globalStateMatches) {
// Reset store state for each match.
const valueFromStore = getStoreValueFromString(match[2]);
if (!valueFromStore) {
return '';
}
const stringWithReplacement = str.replace(match[0], valueFromStore);
// If we still have values we need to substitute, call this function again.
if (stringWithReplacement.includes(dynamicStrings.GLOBAL_STATE)) {
return mapStringToState(stringWithReplacement);
}
// Concatenate the string.
stringBuilder = `${stringBuilder} ${stringWithReplacement}`;
}
return stringBuilder.trimStart();
}
function getStoreValueFromString(str) {
let storeOrStateObject = useMainStore();
for (const s of str.split('.')) {
if (s === 'getters') continue;
if (storeOrStateObject[s] != undefined) {
storeOrStateObject = storeOrStateObject[s];
} else {
return ''
}
}
return storeOrStateObject;
}
///////////////////////////////////
// If Statement Processing Logic //
///////////////////////////////////
/**
* Recursive function - replaces all instances of if statements from the CMS that utilize the specified ifConditionKeyword
* @param {*} str string - Input string to be processed
* @param {*} ifConditionKeyword string - Defines which if statements to process ex: 'globalState'
* @param {*} replacePlaceholderCallback function - Callback to replace CMS placeholder values
* @returns The processed string
*/
export function processIfStatements(str, ifConditionKeyword, replacePlaceholderCallback) {
const containsRelevantIfStatement = new RegExp('{if:' + ifConditionKeyword + ':.+?}', 'g').test(
str
);
//str = str.replace(/\r?\n|\r/g, '');
const hasEmbeddedCrLf = /\r?\n|\r/g.test(str);
if (!containsRelevantIfStatement || hasEmbeddedCrLf) {
return str;
} else {
const ifStatementRegexExpression = getIfStatementRegexExpression();
const ifStatementRegexMatches = [...str.matchAll(ifStatementRegexExpression)];
const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword(
ifStatementRegexMatches,
ifConditionKeyword
);
executeIfStatementAndSetProcessedStrings(
completeIfStatementArray,
replacePlaceholderCallback
);
const reconstructedPostProcessedString = joinProcessedRegexArray(ifStatementRegexMatches);
return processIfStatements(
reconstructedPostProcessedString,
ifConditionKeyword,
replacePlaceholderCallback
);
}
}
function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyword) {
let index = 0;
for (const match of matches) {
if (match.groups.isIfStatement && match.groups.ifConditionType === ifConditionKeyword) {
let interiorIndex = 0;
let nestedLevel = 0;
let elseStatementIndex = null;
for (const interiorMatch of matches.slice(index + 1)) {
if (interiorMatch.groups.isIfStatement) {
if (interiorMatch.groups.ifConditionType === ifConditionKeyword) {
break;
} else {
nestedLevel++;
}
} else if (interiorMatch.groups.isElseStatement) {
if (!nestedLevel) {
elseStatementIndex = interiorIndex + 1;
}
} else if (interiorMatch.groups.isEndStatement) {
if (nestedLevel) {
nestedLevel--;
} else {
const ifStatementArray = matches.slice(index, index + interiorIndex + 2);
flagMatchesForProcessing(ifStatementArray, elseStatementIndex);
return ifStatementArray;
}
}
interiorIndex++;
}
}
index++;
}
console.error('Did not find end in conditional logic');
return matches;
}
function flagMatchesForProcessing(matches, elseStatementIndex) {
matches[0].isFlaggedForProcessing = true;
matches[matches.length - 1].isFlaggedForProcessing = true;
if (elseStatementIndex) {
matches[elseStatementIndex].isFlaggedForProcessing = true;
}
}
function joinProcessedRegexArray(regexMatches) {
let processedString = '';
regexMatches.forEach((match) => {
const rawString = match[0];
processedString += match.groups.processedString ?? rawString;
});
return processedString;
}
function executeIfStatementAndSetProcessedStrings(ifStatementArray, replacePlaceholderCallback) {
const ifCondition = replacePlaceholderCallback(ifStatementArray[0].groups.ifCondition);
let isInsideDesiredBlock = ifCondition;
ifStatementArray.forEach((entry) => {
if (entry.groups.isElseStatement && entry.isFlaggedForProcessing) {
isInsideDesiredBlock = !ifCondition;
} else if (entry.groups.isEndStatement && entry.isFlaggedForProcessing) {
isInsideDesiredBlock = true;
}
setProcessedStringOnEntry(entry, isInsideDesiredBlock);
});
}
function setProcessedStringOnEntry(entry, isInsideDesiredBlock) {
if (!isInsideDesiredBlock) {
entry.groups.processedString = '';
} else {
if (entry.groups.isIfStatement) {
entry.groups.processedString = entry.isFlaggedForProcessing
? entry.groups.ifTrailingString
: entry[0];
} else if (entry.groups.isElseStatement) {
entry.groups.processedString = entry.isFlaggedForProcessing
? entry.groups.elseTrailingString
: entry[0];
} else {
entry.groups.processedString = entry.isFlaggedForProcessing
? entry.groups.endTrailingString
: entry[0];
}
}
}
function getIfStatementRegexExpression() {
// Matches but does not capture:
// {if:...} or {else} or {end}
const anyLogicOperatorNonCapture = '(?:{(?:end|else|if:.*?)})';
// NOTE: ?<variableName> syntax stores the captured match like so: match.groups.variableName
const matchStartOfString =
'(?<processedString>^.+?)' + // Match and Capture all characters (lazy), cannot be empty
'(?=(?:{if))'; // Looks ahead but does not capture {if
const matchIfOperator =
'(?<isIfStatement>{if:)' + // Match & Capture {if:
'(?<ifConditionType>.*?):' + // Match all chars up to and including next ':' - Capture all chars up to ':'
'(?<ifCondition>.*?)}' + // Match all chars up to and including next '}' - Capture all chars up to '}'
'(?<ifTrailingString>.*?)' + // Match and Capture all characters (lazy), can be empty
'(?=' +
anyLogicOperatorNonCapture +
')'; // Looks ahead but does not capture the next logic operator
const matchElseOperator =
'(?<isElseStatement>{else})' + // Match & Capture {else}
'(?<elseTrailingString>.*?)' + // Match & Capture all characters (lazy), can be empty
'(?=' +
anyLogicOperatorNonCapture +
')'; // Looks ahead but does not capture the next logic operator
const matchEndOperator =
'(?<isEndStatement>{end})' + // Match & Capture {end}
'(?<endTrailingString>.*?)' + // Match & Capture all chracters (lazy), can be empty
'(?=' +
anyLogicOperatorNonCapture +
'|$)'; // Looks ahead but does not capture the next logic operator
// Combine all matching patterns, separated by 'or' pipes
return new RegExp(
matchStartOfString +
'|' +
matchIfOperator +
'|' +
matchElseOperator +
'|' +
matchEndOperator,
'g'
);
}
//////////////////////////////////////////
// End of If Statement Processing Logic //
//////////////////////////////////////////
export function doesCopyContainTextLink(copy) {
return copy.includes(dynamicStrings.TEXT_LINK);
}
export function doesCopyContainRouterLink(copy) {
return copy.includes(this.dynamicStrings.ROUTER_LINK);
}
export function splitCopyOnCMSPlaceHolder(copy) {
// splits copy on { ... } such as {routerlink: ...}
return copy.split(/{(.*?)}/g);
}
export function getRouterLinkRouteFromCopy(copy) {
// sample input: {routerLink:estimate,provide your VIN}
// first split would return 'estimate,provide your VIN'
// second split would return 'estimate'
return copy.split(':')[1].split(',')[0];
}
export function getRouterLinkDisplayTextFromCopy(copy) {
// sample input: {routerLink:estimate,provide your VIN}
// first split would return 'estimate,provide your VIN'
// second split would return 'provide your VIN'
return copy.split(':')[1].split(',')[1];
}
// Copy returned from the CMS that has newlines will return blocks wrapped in
// <p ... >...</p>
// This function returns an array of each paragraph, works with or without html
// attributes present
export function splitCMSCopyOnParagraphTag(copy) {
// filter removes empty strings that are a result of string.split with regex
return copy.split(/(?:<p(?:.*?)>)|(?:<\/p>)/g).filter((paragraph) => paragraph !== '');
}