import dynamicStrings from '@/constants/dynamic-strings'; import { useMainStore } from '@/store'; // 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 * @param widgetModel * @param key */ 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]; } // Parent function for processWidgetItemForReplacement. This will loop through the parent // object and pass any objects that need additional processing to the processWidgetItemForReplacement function. /** * @function processWidgetItemForfindAndReplaceGlobalStateValuesReplacement * @param widgetModel * @param widgetName */ 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; } // 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 * @param baseResponse * @param clientResponse */ 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; } /** * * @param issPage */ export function fetchCmsContentForPage(issPage) { const store = useMainStore(); const { clientName } = store.issConfig; const { parentAccountNumber } = store.issConfig; const clientOverride = clientName.length > 0 && parentAccountNumber > 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); } ); }) ); } /** * @function mapStringToModal * @param str */ 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 = `${splitParams[1]}`; let returnVal = str.replace(linkToReplace, bodyText); if (returnVal.includes(dynamicStrings.MODAL_LINK)) { returnVal = mapStringToModal(returnVal); } return returnVal; } /** * @function mapStringToLink * @param str */ 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 = `${splitParams[1]}`; 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 * @param str */ 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 = ''; // eslint-disable-next-line no-restricted-syntax 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 * @param str */ function getStoreValueFromString(str) { if (!str) return ''; let storeOrStateObject = useMainStore(); // eslint-disable-next-line no-restricted-syntax for (const s of str.split('.')) { if (s === 'getters') continue; // For backward compatibility if (typeof 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); 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 ); } /** * * @param matches * @param ifConditionKeyword */ function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyword) { let index = 0; // eslint-disable-next-line no-restricted-syntax for (const match of matches) { if (match.groups.isIfStatement && match.groups.ifConditionType === ifConditionKeyword) { let interiorIndex = 0; let nestedLevel = 0; let elseStatementIndex = null; // eslint-disable-next-line no-restricted-syntax 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; } /** * * @param matches * @param elseStatementIndex */ function flagMatchesForProcessing(matches, elseStatementIndex) { matches[0].isFlaggedForProcessing = true; matches[matches.length - 1].isFlaggedForProcessing = true; if (elseStatementIndex) { matches[elseStatementIndex].isFlaggedForProcessing = true; } } /** * * @param regexMatches */ function joinProcessedRegexArray(regexMatches) { let processedString = ''; regexMatches.forEach((match) => { const rawString = match[0]; processedString += match.groups.processedString ?? rawString; }); return processedString; } /** * * @param ifStatementArray * @param replacePlaceholderCallback */ 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); }); } /** * * @param entry * @param 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: ? syntax stores the captured match like so: match.groups.variableName const matchStartOfString = '(?^.+?)' // Match and Capture all characters (lazy), cannot be empty + '(?=(?:{if))'; // Looks ahead but does not capture {if const matchIfOperator = '(?{if:)' // Match & Capture {if: + '(?.*?):' // Match all chars up to and including next ':' - Capture all chars up to ':' + '(?.*?)}' // Match all chars up to and including next '}' - Capture all chars up to '}' + '(?.*?)' // Match and Capture all characters (lazy), can be empty + `(?=${anyLogicOperatorNonCapture})`; // Looks ahead but does not capture the next logic operator const matchElseOperator = '(?{else})' // Match & Capture {else} + '(?.*?)' // Match & Capture all characters (lazy), can be empty + `(?=${anyLogicOperatorNonCapture})`; // Looks ahead but does not capture the next logic operator const matchEndOperator = '(?{end})' // Match & Capture {end} + '(?.*?)' // 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 // /// /////////////////////////////////////// /** * * @param copy */ export function doesCopyContainTextLink(copy) { return copy.includes(dynamicStrings.TEXT_LINK); } /** * * @param context */ 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()); } } }); } /** * * @param context * @param desiredTarget */ export function setupModalLink(context, desiredTarget) { context.$nextTick(() => { const element = document.querySelector(`[modalTarget=${desiredTarget}]`); if (element) { element.addEventListener('click', () => context.$refs[desiredTarget].openModal()); } }); } /** * * @param copy */ export function doesCopyContainRouterLink(copy) { return copy.includes(this.dynamicStrings.ROUTER_LINK); } /** * splits copy on { ... } such as {routerlink: ...} * @param copy * @returns array of strings */ export function splitCopyOnCMSPlaceHolder(copy) { // splits copy on { ... } such as {routerlink: ...} return copy.split(/{(.*?)}/g); } export function getExternalLink(copy) { const url = copy.split(':')[1].split(',')[0]; if (url.includes('https-')) { const prefixAdded = url.replace('https-', 'https://'); return prefixAdded; } return '#!'; } /** * Returns string2 of input following this pattern: {string1:string2,string3} * @param copy * @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 } * @param copy * @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]; } /** * Returns a router link as an 'a' tag element * @param copy * @returns string */ export function getRouterLinkHtmlStringFromCopy(copy) { return `${getRouterLinkDisplayTextFromCopy(copy)}`; } // Copy returned from the CMS that has newlines will return blocks wrapped in //

...

// This function returns an array of each paragraph, works with or without html // attributes present /** * * @param copy */ export function splitCMSCopyOnParagraphTag(copy) { // filter removes empty strings that are a result of string.split with regex return copy.split(/(?:)|(?:<\/p>)/g).filter((paragraph) => paragraph !== ''); }