456 lines
17 KiB
JavaScript
456 lines
17 KiB
JavaScript
import { dynamicStrings } from '@/constants/dynamic-strings';
|
|
import { useMainStore } from '@/store';
|
|
|
|
export function fetchCmsContentForPage(issPage) {
|
|
const store = useMainStore();
|
|
const { clientName } = store.issConfig;
|
|
const { accountNumber } = store.issConfig;
|
|
const clientOverride = (clientName.length > 0 && accountNumber > 0);
|
|
|
|
return store.getPageData(issPage)
|
|
// Get the base/default page first.
|
|
.then((baseResponse) => {
|
|
if (!clientOverride) {
|
|
// Return the base page if there are no client override.
|
|
return processPageData(baseResponse, null);
|
|
}
|
|
// 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.
|
|
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.
|
|
const 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) => {
|
|
const 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') {
|
|
if (widgetModel[key].includes('{if:')) {
|
|
widgetModel[key] = processIfStatements(widgetModel[key],
|
|
dynamicStrings.GLOBAL_STATE,
|
|
getStoreValueFromString);
|
|
}
|
|
|
|
if (widgetModel[key].includes(dynamicStrings.MODAL_LINK)) {
|
|
widgetModel[key] = mapStringToModal(widgetModel[key]);
|
|
}
|
|
if (widgetModel[key].includes(dynamicStrings.EXTERNAL_LINK)) {
|
|
widgetModel[key] = mapStringToLink(widgetModel[key]);
|
|
}
|
|
if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) {
|
|
widgetModel[key] = mapStringToState(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) {
|
|
const startIndex = str.indexOf(`{${dynamicStrings.MODAL_LINK}`);
|
|
let linkToReplace = str.substring(startIndex, str.length);
|
|
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);
|
|
|
|
const params = linkToReplace.substring((dynamicStrings.MODAL_LINK).length + 2, linkToReplace.length - 1);
|
|
const splitParams = params.split(',');
|
|
|
|
const bodyText
|
|
= `<a href="#" modalTarget="${splitParams[0]}" class="modal-text" aria-label="Modal window">${splitParams[1]}</a>`;
|
|
|
|
let returnVal = str.replace(linkToReplace, bodyText);
|
|
|
|
if (returnVal.includes(dynamicStrings.MODAL_LINK)) {
|
|
returnVal = mapStringToModal(returnVal);
|
|
}
|
|
return returnVal;
|
|
}
|
|
|
|
function mapStringToLink(str) {
|
|
const startIndex = str.indexOf(`{${dynamicStrings.EXTERNAL_LINK}`);
|
|
let linkToReplace = str.substring(startIndex, str.length);
|
|
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);
|
|
|
|
const params = linkToReplace.substring((dynamicStrings.EXTERNAL_LINK).length + 2, linkToReplace.length - 1);
|
|
const splitParams = params.split(',');
|
|
|
|
const bodyText = `<a href="${splitParams[0]}" class="external-text" target="_blank">${splitParams[1]}</a>`;
|
|
|
|
let returnVal = str.replace(linkToReplace, bodyText);
|
|
|
|
if (returnVal.includes(dynamicStrings.EXTERNAL_LINK)) {
|
|
returnVal = mapStringToLink(returnVal);
|
|
}
|
|
return returnVal;
|
|
}
|
|
|
|
// Function to convert a string, into a matching global state item.
|
|
function mapStringToState(str) {
|
|
// Pull all matches out of the string.
|
|
const regexExp = /{([^{}]*?):([^{}]*?)}/g;
|
|
const regexMatches = [...str.matchAll(regexExp)];
|
|
const globalStateMatches = regexMatches.filter((match) => match[1] === dynamicStrings.GLOBAL_STATE);
|
|
|
|
// Our final string value that will be built from the matches.
|
|
const stringBuilder = '';
|
|
|
|
for (const match of globalStateMatches) {
|
|
// Reset store state for each match.
|
|
const valueFromStore = getStoreValueFromString(match[2]);
|
|
if (!valueFromStore) {
|
|
console.warning('Unable to resolve global state data.');
|
|
return ''; // if we can't map our string to state data, return an empty string.
|
|
}
|
|
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.
|
|
str = `${stringBuilder} ${stringWithReplacement}`;
|
|
}
|
|
|
|
return str.trimStart();
|
|
}
|
|
|
|
function getStoreValueFromString(str) {
|
|
if (!str) return '';
|
|
|
|
let storeOrStateObject = useMainStore();
|
|
for (const s of str.split('.')) {
|
|
if (s === 'getters') continue; // For backward compatibility
|
|
if (storeOrStateObject[s] != undefined) {
|
|
storeOrStateObject = storeOrStateObject[s];
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
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);
|
|
|
|
const hasEmbeddedCrLf = /\r?\n|\r/g.test(str);
|
|
if (!containsRelevantIfStatement) {
|
|
return str;
|
|
}
|
|
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 characters (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 setupModalLinks(context) {
|
|
context.$nextTick(() => {
|
|
const elements = document.getElementsByClassName('modal-text');
|
|
for (const element of elements) {
|
|
const target = element.getAttribute('modalTarget');
|
|
if (target) {
|
|
element.addEventListener('click', () => context.$refs[target].openModal());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
export function doesCopyContainRouterLink(copy) {
|
|
return copy.includes(this.dynamicStrings.ROUTER_LINK);
|
|
}
|
|
|
|
/**
|
|
* splits copy on { ... } such as {routerlink: ...}
|
|
* @returns array of strings
|
|
*/
|
|
export function splitCopyOnCMSPlaceHolder(copy) {
|
|
// splits copy on { ... } such as {routerlink: ...}
|
|
return copy.split(/{(.*?)}/g);
|
|
}
|
|
|
|
/**
|
|
* Returns string2 of input following this pattern: {string1:string2,string3}
|
|
* @returns string
|
|
*/
|
|
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];
|
|
}
|
|
|
|
/**
|
|
* Returns string3 of input following this pattern: { string1: string2, string3 }
|
|
* @returns string
|
|
*/
|
|
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 !== '');
|
|
}
|