diff --git a/.github/workflows/sync_develop_and_main.yml b/.github/workflows/sync_develop_and_main.yml new file mode 100644 index 000000000..3c1bfb9d1 --- /dev/null +++ b/.github/workflows/sync_develop_and_main.yml @@ -0,0 +1,27 @@ +name: create PR from main to develop + +on: + push: + branches: [main, master] + +permissions: + contents: read + pull-requests: write + +jobs: + createPullRequest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: extract package version + id: vars + run: | + PACKAGE_VERSION="v$(cat ./package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[[:space:]]')" + echo ::set-output name=tag::${PACKAGE_VERSION} + - uses: repo-sync/pull-request@v2 + with: + source_branch: "${{ github.event.repository.default_branch }}" + destination_branch: "develop" + pr_title: "Merge ${{ github.event.repository.default_branch }} (${{ steps.vars.outputs.tag }}) into develop" + pr_body: "Merge ${{ github.event.repository.default_branch }} (${{ steps.vars.outputs.tag }}) into develop" + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/directanswercards/allfields-standard/template.hbs b/directanswercards/allfields-standard/template.hbs index bc3210a53..22986058f 100644 --- a/directanswercards/allfields-standard/template.hbs +++ b/directanswercards/allfields-standard/template.hbs @@ -14,7 +14,9 @@ {{> cta CTA linkTarget=linkTarget}} {{/if}} - {{> footer }} + {{#if feedbackEnabled}} + {{> footer }} + {{/if}} {{#*inline 'icon'}} diff --git a/directanswercards/card_component.js b/directanswercards/card_component.js index 851d27e95..c28b340dc 100644 --- a/directanswercards/card_component.js +++ b/directanswercards/card_component.js @@ -26,6 +26,7 @@ BaseDirectAnswerCard["{{componentName}}"] = class extends ANSWERS.Component { return super.setState({ ...cardData, + feedbackEnabled: ANSWERS.getAnalyticsOptIn(), feedbackSubmitted: data.feedbackSubmitted, isArray: Array.isArray(this.answer.value), cardName: `{{componentName}}`, diff --git a/directanswercards/documentsearch-standard/template.hbs b/directanswercards/documentsearch-standard/template.hbs index 028de17c5..24585baac 100644 --- a/directanswercards/documentsearch-standard/template.hbs +++ b/directanswercards/documentsearch-standard/template.hbs @@ -9,7 +9,9 @@ {{> cta CTA linkTarget=linkTarget}} - {{> footer}} + {{#if feedbackEnabled}} + {{> footer }} + {{/if}} {{#*inline 'title'}} diff --git a/directanswercards/multilang-allfields-standard/template.hbs b/directanswercards/multilang-allfields-standard/template.hbs index 025addb96..475b72481 100644 --- a/directanswercards/multilang-allfields-standard/template.hbs +++ b/directanswercards/multilang-allfields-standard/template.hbs @@ -14,7 +14,9 @@ {{> cta CTA linkTarget=linkTarget}} {{/if}} - {{> footer }} + {{#if feedbackEnabled}} + {{> footer }} + {{/if}} {{#*inline 'icon'}} diff --git a/global_config.json b/global_config.json index e72e40d11..7a37f27ca 100644 --- a/global_config.json +++ b/global_config.json @@ -1,5 +1,5 @@ { - "sdkVersion": "1.10", // The version of the Answers SDK to use + "sdkVersion": "release/v1.11", // The version of the Answers SDK to use // "apiKey": "", // The answers api key found on the experiences page. This will be provided automatically by the Yext CI system // "experienceVersion": "", // the Answers Experience version to use for API requests. This will be provided automatically by the Yext CI system // "businessId": "", // The business ID of the account. This will be provided automatically by the Yext CI system diff --git a/hooks/templatedatavalidator.js b/hooks/templatedatavalidator.js index 6f120ba08..7eaafe50e 100644 --- a/hooks/templatedatavalidator.js +++ b/hooks/templatedatavalidator.js @@ -1,6 +1,6 @@ const path = require('path'); const { parseJamboConfig } = require('../commands/helpers/utils/jamboconfigutils'); -const { error } = require('../commands/helpers/utils/logger'); +const { error, warn } = require('../commands/helpers/utils/logger'); /** * Validates the template data and partials used during jambo build. @@ -13,8 +13,9 @@ const { error } = require('../commands/helpers/utils/logger'); */ module.exports = function (pageData, partials) { const jamboConfig = parseJamboConfig(); + const { JAMBO_INJECTED_DATA } = pageData.env; const validatorResults = [ - isGlobalConfigValid(pageData.global_config), + isGlobalConfigValid(pageData.global_config, JAMBO_INJECTED_DATA), isPageVerticalConfigValid(pageData, jamboConfig, partials) ]; const isValid = validatorResults.every(result => result); @@ -25,13 +26,50 @@ module.exports = function (pageData, partials) { * Validates global config for the page template * * @param {Object} globalConfig + * @param {Object} JAMBO_INJECTED_DATA * @returns {boolean} */ -function isGlobalConfigValid(globalConfig) { - if (!globalConfig.experienceKey) { +function isGlobalConfigValid(globalConfig, JAMBO_INJECTED_DATA) { + const { experienceKey } = globalConfig; + if (!experienceKey) { error('Missing Info: no experienceKey found.'); return false; } + if (globalConfig.useJWT || globalConfig.apiKey) { + return true; + } + + const injectedDataForExperience = JAMBO_INJECTED_DATA.answers.experiences[experienceKey]; + if (!injectedDataForExperience) { + error(`No JAMBO_INJECTED_DATA found for experience key: "${experienceKey}"`); + error(`Found JAMBO_INJECTED_DATA: "${JSON.stringify(JAMBO_INJECTED_DATA, null, 2)}.`); + return false; + } + + const productionApiKey = injectedDataForExperience.configByLabel.PRODUCTION.apiKey; + const deprecatedApiKey = injectedDataForExperience.apiKey; + if (!productionApiKey) { + if (!deprecatedApiKey) { + error(`No injected production api key found for experience key: "${experienceKey}"`); + } else { + warn('No injected production api key found, using the default apiKey instead.'); + } + } + + const stagingApiKey = injectedDataForExperience.configByLabel.STAGING.apiKey; + if (!stagingApiKey) { + if (!deprecatedApiKey) { + error(`No injected staging api key found for experience key: "${experienceKey}"`); + } else { + warn('No injected staging api key found, using the default apiKey instead.'); + } + } + + if ((!productionApiKey || !stagingApiKey) && !deprecatedApiKey) { + error(`JAMBO_INJECTED_DATA is missing an api key.`); + error(`Found JAMBO_INJECTED_DATA: "${JSON.stringify(JAMBO_INJECTED_DATA, null, 2)}.`); + return false; + } return true; } diff --git a/package-lock.json b/package-lock.json index a3971cf84..4ca1c3a08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "answers-hitchhiker-theme", - "version": "1.24.1", + "version": "1.25.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e4a6a756b..2e2ea20ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "answers-hitchhiker-theme", - "version": "1.24.1", + "version": "1.25.0", "description": "A starter answers theme for hitchhikers", "scripts": { "test": "cross-env NODE_ICU_DATA=node_modules/full-icu jest --verbose", diff --git a/script/core.hbs b/script/core.hbs index 6b78f0e3e..8f5798b5e 100644 --- a/script/core.hbs +++ b/script/core.hbs @@ -106,6 +106,7 @@ }); } }).catch(err => { + console.error(err); window.AnswersExperience.AnswersInitializedPromise.reject('Answers failed to initialized.'); }); {{> script/after-init}} diff --git a/static/js/formatters-internal.js b/static/js/formatters-internal.js index fbdc7fb5f..dbbe9bcb9 100644 --- a/static/js/formatters-internal.js +++ b/static/js/formatters-internal.js @@ -82,15 +82,19 @@ export function toLocalizedDistance(profile, key = 'd_distance', displayUnits) { return this.toMiles(profile, undefined, undefined, locale); } +export function _getLocaleWithDashes(locale) { + return locale && locale.replace(/_/g, '-'); +} + export function _getDocumentLocale() { - return document.documentElement.lang.replace('_', '-'); + return _getLocaleWithDashes(document.documentElement.lang); } export function toKilometers(profile, key = 'd_distance', displayUnits = 'km', locale) { if (!profile[key]) { return ''; } - locale = locale || _getDocumentLocale() + locale = _getLocaleWithDashes(locale) || _getDocumentLocale(); const distanceInKilometers = profile[key] / 1000; // Convert meters to kilometers return new Intl.NumberFormat(locale, { style: 'decimal', maximumFractionDigits: 1, minimumFractionDigits: 1}) @@ -101,7 +105,7 @@ export function toMiles(profile, key = 'd_distance', displayUnits = 'mi', locale if (!profile[key]) { return ''; } - locale = locale || _getDocumentLocale() + locale = _getLocaleWithDashes(locale) || _getDocumentLocale(); const distanceInMiles = profile[key] / 1609.344; // Convert meters to miles return new Intl.NumberFormat(locale, { style: 'decimal', maximumFractionDigits: 1, minimumFractionDigits: 1 }) @@ -234,7 +238,7 @@ export function snakeToTitle(snake) { * @returns {string} The pretty printed value. */ export function prettyPrintObject(obj, locale) { - locale = locale || _getDocumentLocale(); + locale = _getLocaleWithDashes(locale) || _getDocumentLocale(); switch (typeof obj) { case 'string': @@ -459,7 +463,7 @@ export function openStatus(profile, key = 'hours', isTwentyFourHourClock, locale } const hoursLocalizer = new HoursStringsLocalizer( - locale || _getDocumentLocale(), isTwentyFourHourClock); + _getLocaleWithDashes(locale) || _getDocumentLocale(), isTwentyFourHourClock); return new OpenStatusMessageFactory(hoursLocalizer) .create(hours.openStatus); } @@ -501,7 +505,7 @@ export function hoursList(profile, opts = {}, key = 'hours', locale) { }; const hoursLocalizer = new HoursStringsLocalizer( - locale || _getDocumentLocale(), opts.isTwentyFourHourClock); + _getLocaleWithDashes(locale) || _getDocumentLocale(), opts.isTwentyFourHourClock); return new HoursTableBuilder(hoursLocalizer).build(hours, standardizedOpts); } @@ -515,7 +519,7 @@ export { generateCTAFieldTypeLink }; * returns the price value without formatting */ export function price(fieldValue = {}, locale) { - const localeForFormatting = locale || _getDocumentLocale() || 'en'; + const localeForFormatting = _getLocaleWithDashes(locale) || _getDocumentLocale() || 'en'; const price = fieldValue.value && parseFloat(fieldValue.value); const currencyCode = fieldValue.currencyCode && fieldValue.currencyCode.split('-')[0]; if (!price || isNaN(price) || !currencyCode) { diff --git a/static/js/theme-map/Util/GeoSearchFormBinder.js b/static/js/theme-map/Util/GeoSearchFormBinder.js deleted file mode 100644 index 0860461ba..000000000 --- a/static/js/theme-map/Util/GeoSearchFormBinder.js +++ /dev/null @@ -1,89 +0,0 @@ -import { HTML5Geolocation } from './Html5Geolocation.js'; -import { SpinnerModal } from '../SpinnerModal/SpinnerModal.js'; - -export class GeoSearchFormBinder { - - constructor(input, form, submitHandler, spinnerParent) { - this.input = input; - this.form = form; - this.useSpinner = spinnerParent != undefined; - this.running = false; - - // Google's example values - const geoLocationOptions = { - "timeout": 5 * 1000, - "maximumAge": 5 * 60 * 1000, - }; - - HTML5Geolocation.initClass(geoLocationOptions); - - if (typeof submitHandler === 'function') { - this.submitHandler = submitHandler; - } else { - console.warn('the submit handler should be a function, was: ', typeof submitHandler); - } - - if (this.useSpinner) { - this.spinner = new SpinnerModal(spinnerParent); - } - } - - fillPosition(position) { - if ('latitude' in position && 'longitude' in position) { - let query = `${position.latitude},${position.longitude}`; - this.input.name = 'qp'; - let q = document.createElement('input'); - q.name ='q'; - q.type = 'hidden'; - q.value = query; - this.form.appendChild(q); - if (this.submitHandler) { - this.submitHandler(); - return - } - // This will not get fired if you provide a submitHandler function. - // It's useful because browsers do not fire the 'submit' event when form - // submits are triggered via javascript. So if you need to do something - // with the form before it submits, pass a submitHandler!!!!!! - this.form.submit(); - } - } - - geolocateAndSearch() { - if (this.running) return; - this.running = true; - if (this.useSpinner) { - this.spinner.showSpinner(); - } - - this.form.classList.add('js-geolocating'); - document.body.classList.add('js-geolocating'); - - HTML5Geolocation.geolocate(this.geoLocationSuccess.bind(this), this.geoLocationFailure.bind(this)); - } - - geoLocationSuccess(position) { - this.running = false; - this.fillPosition(position); - } - - geoLocationFailure(error) { - this.running = false; - - if (error.code == error.PERMISSION_DENIED) { - console.warn(error.message); - } else { - alert('Sorry, we could not geolocate you at this time'); - } - - console.error(error); - - Array.from(document.getElementsByClassName('js-geolocating')).forEach(function(element) { - element.classList.remove('js-geolocating'); - }); - - if (this.useSpinner) { - this.spinner.hideSpinner(); - } - } -} diff --git a/static/js/theme-map/VerticalFullPageMapOrchestrator.js b/static/js/theme-map/VerticalFullPageMapOrchestrator.js index 74484dee2..39113226d 100644 --- a/static/js/theme-map/VerticalFullPageMapOrchestrator.js +++ b/static/js/theme-map/VerticalFullPageMapOrchestrator.js @@ -19,6 +19,13 @@ class VerticalFullPageMapOrchestrator extends ANSWERS.Component { constructor(config, systemConfig) { super(config, systemConfig); + /** + * Name of a location card type + * + * @type {string} + */ + this.cardType = config.cardType; + /** * The container in the DOM for the interactive map * @type {HTMLElement} @@ -182,14 +189,14 @@ class VerticalFullPageMapOrchestrator extends ANSWERS.Component { this.searchThisArea(); }); - this.setupCssForBreakpoints(); + this.setupMobileBreakpointListener(); this.addMapComponent(); } /** * Properly set CSS classes for mobile and desktop */ - setupCssForBreakpoints () { + setupMobileBreakpointListener () { if (!this.isMobile()) { this.updateCssForDesktop(); } @@ -198,6 +205,7 @@ class VerticalFullPageMapOrchestrator extends ANSWERS.Component { if (this.isMobile()) { this.updateCssForMobile(); } else { + this.core.storage.set('DISABLE_RENDER_RESULTS', false); this.updateCssForDesktop(); } }; @@ -466,22 +474,29 @@ class VerticalFullPageMapOrchestrator extends ANSWERS.Component { */ pinFocusListener (index, cardId) { this.core.storage.set(StorageKeys.LOCATOR_SELECTED_RESULT, cardId); - const selector = `.yxt-Card[data-opts='{ "_index": ${index - 1} }']`; - const card = document.querySelector(selector); - document.querySelectorAll('.yxt-Card--pinFocused').forEach((el) => { el.classList.remove('yxt-Card--pinFocused'); }); - card.classList.add('yxt-Card--pinFocused'); - if (this.isMobile()) { document.querySelectorAll('.yxt-Card--isVisibleOnMobileMap').forEach((el) => removeElement(el)); const isDetailCardOpened = document.querySelectorAll('.yxt-Card--isVisibleOnMobileMap').length; - this._detailCard = card.cloneNode(true); + const entityId = cardId.replace('js-yl-', ''); + const verticalResults = this.core.storage.get(StorageKeys.VERTICAL_RESULTS).results; + const entityData = verticalResults.find(entity => entity.id.toString() === entityId); + const opts = { + parentContainer: this._container, + container: `.yxt-Card-${entityId}`, + data: { + result: entityData, + verticalKey: this.verticalKey + } + }; + ANSWERS.addComponent(this.cardType, opts); + this._detailCard = this._container.querySelector(`.yxt-Card-${entityId}`); this._detailCard.classList.add('yxt-Card--isVisibleOnMobileMap'); - this._container.appendChild(this._detailCard); + this._detailCard.classList.add('yxt-Card--pinFocused'); if (!isDetailCardOpened) { window.requestAnimationFrame(() => { @@ -502,6 +517,9 @@ class VerticalFullPageMapOrchestrator extends ANSWERS.Component { this.addCssClassesForState(MobileStates.DETAIL_SHOWN); } else { + const selector = `.yxt-Card[data-opts='{ "_index": ${index - 1} }']`; + const card = document.querySelector(selector); + card.classList.add('yxt-Card--pinFocused'); this.scrollToResult(card); } } @@ -532,8 +550,10 @@ class VerticalFullPageMapOrchestrator extends ANSWERS.Component { this.deselectAllResults(); window.scrollTo(0, 0); if (this._mobileView === MobileStates.LIST_VIEW) { + this.core.storage.set('DISABLE_RENDER_RESULTS', true); this.setMobileMapView(); } else { + this.core.storage.set('DISABLE_RENDER_RESULTS', false); this.setMobileListView(); } }); diff --git a/static/js/transform-facets.js b/static/js/transform-facets.js index 09b715c06..8bd62b968 100644 --- a/static/js/transform-facets.js +++ b/static/js/transform-facets.js @@ -6,39 +6,97 @@ * @param {FilterOptionsConfig} config the config of the FilterOptionsConfig from answers-search-ui * @returns {(DisplayableFacet | FilterOptionsConfig)[]} */ -export default function transformFacets (facets, config) { - if(!config || !('fields' in config)) { +export default function transformFacets(facets, config) { + if (!config || !('fields' in config)) { return facets; } return facets.map(facet => { - const isConfigurationForFacet = facet.fieldId in config.fields; - if (!isConfigurationForFacet) { + const hasConfigurationForFacet = facet.fieldId in config.fields; + if (!hasConfigurationForFacet) { return facet; } - const facetConfig = config.fields[facet.fieldId]; + + if (typeof config.fields[facet.fieldId] !== 'object') { + console.error( + `The "fields" config for ${facet.fieldId} should be an object. ` + + `Received ${config.fields[facet.fieldId]} instead.`); + } + + const { + fieldLabels, + optionsOrder, + optionsOrderList, + ...filterOptionsConfig + } = config.fields[facet.fieldId]; let options = facet.options; - if ('fieldLabels' in facetConfig) { + if (fieldLabels) { options = facet.options.map(option => { - const fieldLabels = facetConfig.fieldLabels; - const displayName = (option.displayName in fieldLabels) ? fieldLabels[option.displayName] : option.displayName; - - return Object.assign({}, option, { displayName }); + return { ...option, displayName }; }) } - const filterOptionsConfig = Object.entries(facetConfig).reduce((filterOptions, [option, value]) => { - if (option !== 'fieldLabels') { - filterOptions[option] = value; - } - return filterOptions; - }, {}); - - return Object.assign({}, facet, filterOptionsConfig, { options }); + if (optionsOrderList) { + options = sortFacetOptionsCustom(options, optionsOrderList); + } else if (optionsOrder) { + options = sortFacetOptions(options, optionsOrder, facet.fieldId); + } + + return { + ...facet, + ...filterOptionsConfig, + options + }; + }); +} + +/** + * Sorts the facet options and returns a new array. + * + * @param {{ displayName: string }[]} options The facet options to sort. + * @param {'ASC' | 'DESC'} optionsOrder + * @param {string} fieldId + * @returns {{ displayName: string }[]} + */ +function sortFacetOptions(options, optionsOrder, fieldId) { + const getSortComparator = () => { + if (optionsOrder === 'ASC') { + return (a, b) => a.displayName.localeCompare(b.displayName); + } else if (optionsOrder === 'DESC') { + return (a, b) => b.displayName.localeCompare(a.displayName); + } else { + console.error(`Unknown facet optionsOrder "${optionsOrder}" for the "${fieldId}" facet.`); + return undefined; + } + } + const comparator = getSortComparator(); + if (!comparator) { + return options; + } + return [...options].sort(comparator); +} + + +/** + * Sorts the facet options using the priority specified in + * the optionsOrderList and returns a new array. + * + * @param {{ displayName: string }[]} options The facet options to sort. + * @param {string[]} optionsOrderList + * @returns {{ displayName: string }[]} + */ +function sortFacetOptionsCustom(options, optionsOrderList) { + const getPriority = displayName => { + const index = optionsOrderList.indexOf(displayName); + return index === -1 ? optionsOrderList.length : index; + } + + return [...options].sort((a, b) => { + return getPriority(a.displayName) - getPriority(b.displayName); }); } \ No newline at end of file diff --git a/static/package-lock.json b/static/package-lock.json index c5b09f86f..8606337f0 100644 --- a/static/package-lock.json +++ b/static/package-lock.json @@ -1,6 +1,6 @@ { "name": "answers-hitchhiker-theme", - "version": "1.24.1", + "version": "1.25.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/static/package.json b/static/package.json index c59b534d7..fd97fd6f9 100644 --- a/static/package.json +++ b/static/package.json @@ -1,6 +1,6 @@ { "name": "answers-hitchhiker-theme", - "version": "1.24.1", + "version": "1.25.0", "description": "Toolchain for use with the HH Theme", "main": "Gruntfile.js", "scripts": { diff --git a/static/scss/answers/cards/document-standard.scss b/static/scss/answers/cards/document-standard.scss index a2772937c..f67464fe5 100644 --- a/static/scss/answers/cards/document-standard.scss +++ b/static/scss/answers/cards/document-standard.scss @@ -64,6 +64,7 @@ &-detailsText { @include rich_text_formatting; + color: var(--yxt-color-text-primary); } &-ctasWrapper diff --git a/static/scss/answers/cards/event-standard.scss b/static/scss/answers/cards/event-standard.scss index 0fad3e910..238b42a76 100644 --- a/static/scss/answers/cards/event-standard.scss +++ b/static/scss/answers/cards/event-standard.scss @@ -79,6 +79,7 @@ &-detailsText { margin-top: calc(var(--yxt-base-spacing) / 2); + color: var(--yxt-color-text-primary); } &-content diff --git a/static/scss/answers/cards/faq-accordion.scss b/static/scss/answers/cards/faq-accordion.scss index 32d84f701..f5ca64522 100644 --- a/static/scss/answers/cards/faq-accordion.scss +++ b/static/scss/answers/cards/faq-accordion.scss @@ -90,10 +90,14 @@ margin: calc(var(--yxt-base-spacing) / 2) var(--yxt-base-spacing); font-size: var(--yxt-font-size-md); - color: var(--yxt-color-text-primary); line-height: var(--yxt-line-height-md); } + &-detailsText + { + color: var(--yxt-color-text-primary); + } + &-details-toggle { margin-left: var(--yxt-base-spacing); diff --git a/static/scss/answers/cards/financial-professional-location.scss b/static/scss/answers/cards/financial-professional-location.scss index 632f9014c..79db7aeea 100644 --- a/static/scss/answers/cards/financial-professional-location.scss +++ b/static/scss/answers/cards/financial-professional-location.scss @@ -95,6 +95,11 @@ $financial-professional-location-ordinal-dimensions: 18px !default; padding-bottom: 0; } + &-distance + { + color: var(--yxt-color-text-primary); + } + &-distance { font-style: italic; @@ -103,7 +108,12 @@ $financial-professional-location-ordinal-dimensions: 18px !default; margin-right: 0; } - &-address, + &-address + { + margin-top: calc(var(--hh-financial-professional-location-spacing) / 2); + color: var(--yxt-color-text-primary); + } + &-cardDetails { margin-top: calc(var(--hh-financial-professional-location-spacing) / 2); @@ -112,6 +122,7 @@ $financial-professional-location-ordinal-dimensions: 18px !default; &-detailsText { @include rich_text_formatting; + color: var(--yxt-color-text-primary); } &-listTitle @@ -127,9 +138,15 @@ $financial-professional-location-ordinal-dimensions: 18px !default; padding-left: calc(var(--hh-financial-professional-location-spacing) / 2); } + &-listWrapper + { + color: var(--yxt-color-text-primary); + } + &-phone { margin-top: calc(var(--hh-financial-professional-location-spacing) / 2); + color: var(--yxt-color-text-primary); } &-phone--desktop { diff --git a/static/scss/answers/cards/job-standard.scss b/static/scss/answers/cards/job-standard.scss index d3616eefa..d35c16588 100644 --- a/static/scss/answers/cards/job-standard.scss +++ b/static/scss/answers/cards/job-standard.scss @@ -67,6 +67,7 @@ &-detailsText { @include rich_text_formatting; + color: var(--yxt-color-text-primary); } &-ctasWrapper diff --git a/static/scss/answers/cards/link-standard.scss b/static/scss/answers/cards/link-standard.scss index 6174af735..81a579fef 100644 --- a/static/scss/answers/cards/link-standard.scss +++ b/static/scss/answers/cards/link-standard.scss @@ -62,6 +62,7 @@ &-detailsText { @include rich_text_formatting; + color: var(--yxt-color-text-primary); br { display: none; // TODO (agrow) figure out a less hacky way to do this diff --git a/static/scss/answers/cards/location-standard.scss b/static/scss/answers/cards/location-standard.scss index 751f7342c..71f0fd001 100644 --- a/static/scss/answers/cards/location-standard.scss +++ b/static/scss/answers/cards/location-standard.scss @@ -123,6 +123,7 @@ $location-standard-ordinal-dimensions: calc(var(--hh-location-standard-base-spac font-style: italic; white-space: nowrap; padding-left: 4px; + color: var(--yxt-color-text-primary); } &-subtitle @@ -205,13 +206,16 @@ $location-standard-ordinal-dimensions: calc(var(--hh-location-standard-base-spac &-address { + color: var(--yxt-color-text-primary); + .c-AddressRow:last-child { display: none; } } &-phone - { + { + color: var(--yxt-color-text-primary); display: flex; font-weight: var(--yxt-font-weight-semibold); margin-top: calc(var(--hh-location-standard-base-spacing) / 4); @@ -242,8 +246,14 @@ $location-standard-ordinal-dimensions: calc(var(--hh-location-standard-base-spac margin-top: calc(var(--hh-location-standard-base-spacing) / 2); } + &-detailsText + { + color: var(--yxt-color-text-primary); + } + &-hoursText { + color: var(--yxt-color-text-primary); margin-top: calc(var(--hh-location-standard-base-spacing) / 2); } @@ -254,6 +264,7 @@ $location-standard-ordinal-dimensions: calc(var(--hh-location-standard-base-spac &-services { + color: var(--yxt-color-text-primary); flex-basis: 100%; margin-top: calc(var(--hh-location-standard-base-spacing) / 2); } diff --git a/static/scss/answers/cards/menuitem-standard.scss b/static/scss/answers/cards/menuitem-standard.scss index 407d2181d..9a94f6d14 100644 --- a/static/scss/answers/cards/menuitem-standard.scss +++ b/static/scss/answers/cards/menuitem-standard.scss @@ -61,6 +61,11 @@ padding-bottom: calc(var(--yxt-base-spacing) / 2); } + &-listWrapper + { + color: var(--yxt-color-text-primary); + } + &-content { margin-top: calc(var(--yxt-base-spacing) / 2); @@ -69,6 +74,7 @@ &-detailsText { @include rich_text_formatting; + color: var(--yxt-color-text-primary); } &-contentCtasWrapper diff --git a/static/scss/answers/cards/product-prominentimage-clickable.scss b/static/scss/answers/cards/product-prominentimage-clickable.scss index 922eba9db..a91959837 100644 --- a/static/scss/answers/cards/product-prominentimage-clickable.scss +++ b/static/scss/answers/cards/product-prominentimage-clickable.scss @@ -113,5 +113,6 @@ &-detailsText { @include rich_text_formatting; + color: var(--yxt-color-text-primary); } } diff --git a/static/scss/answers/cards/product-prominentimage.scss b/static/scss/answers/cards/product-prominentimage.scss index 308a2d5df..c1a5331fd 100644 --- a/static/scss/answers/cards/product-prominentimage.scss +++ b/static/scss/answers/cards/product-prominentimage.scss @@ -95,6 +95,7 @@ &-detailsText { @include rich_text_formatting; + color: var(--yxt-color-text-primary); } &-ctasWrapper diff --git a/static/scss/answers/cards/product-prominentvideo.scss b/static/scss/answers/cards/product-prominentvideo.scss index a07f58f9b..b6f305198 100644 --- a/static/scss/answers/cards/product-prominentvideo.scss +++ b/static/scss/answers/cards/product-prominentvideo.scss @@ -61,6 +61,7 @@ &-detailsText { @include rich_text_formatting; + color: var(--yxt-color-text-primary); } &-cardDetails diff --git a/static/scss/answers/cards/product-standard.scss b/static/scss/answers/cards/product-standard.scss index 006d7da6d..31b311e62 100644 --- a/static/scss/answers/cards/product-standard.scss +++ b/static/scss/answers/cards/product-standard.scss @@ -66,7 +66,8 @@ &-detailsText { - @include rich_text_formatting; + @include rich_text_formatting; + color: var(--yxt-color-text-primary); } &-ctasWrapper diff --git a/static/scss/answers/cards/professional-location.scss b/static/scss/answers/cards/professional-location.scss index 3b7168669..5d0fb1363 100644 --- a/static/scss/answers/cards/professional-location.scss +++ b/static/scss/answers/cards/professional-location.scss @@ -103,7 +103,12 @@ $professional-location-ordinal-dimensions: 18px !default; margin-right: 0; } - &-address, + &-address + { + margin-top: calc(var(--hh-professional-location-spacing) / 2); + color: var(--yxt-color-text-primary); + } + &-cardDetails { margin-top: calc(var(--hh-professional-location-spacing) / 2); @@ -112,6 +117,7 @@ $professional-location-ordinal-dimensions: 18px !default; &-detailsText { @include rich_text_formatting; + color: var(--yxt-color-text-primary); } &-listTitle @@ -127,9 +133,15 @@ $professional-location-ordinal-dimensions: 18px !default; padding-left: calc(var(--hh-professional-location-spacing) / 2); } + &-listWrapper + { + color: var(--yxt-color-text-primary); + } + &-phone { margin-top: calc(var(--hh-professional-location-spacing) / 2); + color: var(--yxt-color-text-primary); } &-phone--desktop { diff --git a/static/scss/answers/cards/professional-standard.scss b/static/scss/answers/cards/professional-standard.scss index aefe6e15c..330c59055 100644 --- a/static/scss/answers/cards/professional-standard.scss +++ b/static/scss/answers/cards/professional-standard.scss @@ -71,6 +71,7 @@ $professional-standard-spacing: var(--yxt-base-spacing) !default; &-detailsText { @include rich_text_formatting; + color: var(--yxt-color-text-primary); } &-listTitle @@ -86,9 +87,15 @@ $professional-standard-spacing: var(--yxt-base-spacing) !default; padding-left: calc(var(--hh-professional-standard-spacing) / 2); } + &-listWrapper + { + color: var(--yxt-color-text-primary); + } + &-phone { margin-top: calc(var(--hh-professional-standard-spacing) / 2); + color: var(--yxt-color-text-primary); } &-phone--desktop { diff --git a/static/scss/answers/cards/standard.scss b/static/scss/answers/cards/standard.scss index 7182a178e..9b1a93d10 100644 --- a/static/scss/answers/cards/standard.scss +++ b/static/scss/answers/cards/standard.scss @@ -64,6 +64,7 @@ &-detailsText { @include rich_text_formatting; + color: var(--yxt-color-text-primary); } &-ctasWrapper diff --git a/templates/universal-standard/script/map-pin.hbs b/templates/universal-standard/script/map-pin.hbs index 0ff478714..88f2a1d81 100644 --- a/templates/universal-standard/script/map-pin.hbs +++ b/templates/universal-standard/script/map-pin.hbs @@ -32,7 +32,7 @@ if (mapProvider === 'google') { config.svg = svg; {{!-- Don't use the sdk's built-in label for GoogleMapProvider --}} - config.label = {}; + config.label = ''; config.scaledSize = { w: pinStyling.width, h: pinStyling.height, diff --git a/templates/vertical-full-page-map/page-config.json b/templates/vertical-full-page-map/page-config.json index 54bb8016b..3d5142d4a 100644 --- a/templates/vertical-full-page-map/page-config.json +++ b/templates/vertical-full-page-map/page-config.json @@ -66,7 +66,7 @@ // "verticalLimit": 15, // The result count limit for vertical search // "universalLimit": 5, // The result count limit for universal search "cardType": "location-standard", // The name of the card to use - e.g. accordion, location, customcard - "icon": "pin", // The icon to use on the card for this vertical + // "icon": "pin", // The icon to use on the card for this vertical "mapConfig": { //"enablePinClustering": true, // Cluster pins on the map that are close together. Defaults false "mapProvider": "MapBox", // The name of the provider (e.g. Mapbox, Google) diff --git a/templates/vertical-full-page-map/script/verticalresults.hbs b/templates/vertical-full-page-map/script/verticalresults.hbs index 3972d3232..87fda4b24 100644 --- a/templates/vertical-full-page-map/script/verticalresults.hbs +++ b/templates/vertical-full-page-map/script/verticalresults.hbs @@ -1,5 +1,15 @@ ANSWERS.addComponent("VerticalResults", Object.assign({}, { container: "#js-answersVerticalResults", + onCreate: function() { + this.core.storage.registerListener({ + storageKey: 'DISABLE_RENDER_RESULTS', + eventType: 'update', + callback: () => { this.setState(this.getState()) } + }); + }, + beforeMountOverride: function(data) { + return !this.core.storage.get('DISABLE_RENDER_RESULTS'); + }, {{#if verticalKey}} verticalKey: "{{{verticalKey}}}", modifier: "{{{verticalKey}}}", diff --git a/templates/vertical-grid/page-config.json b/templates/vertical-grid/page-config.json index 242f0d6ea..7d1c4279d 100644 --- a/templates/vertical-grid/page-config.json +++ b/templates/vertical-grid/page-config.json @@ -65,8 +65,10 @@ "verticalsToConfig": { "": { // The vertical key from your search configuration // "label": "", // The name of the vertical in the section header and the navigation bar + // "verticalLimit": 15, // The result count limit for vertical search + // "universalLimit": 5, // The result count limit for universal search "cardType": "product-prominentimage", // The name of the card to use - e.g. accordion, location, customcard - "icon": "star", // The icon to use on the card for this vertical + // "icon": "star", // The icon to use on the card for this vertical "universalSectionTemplate": "standard" } } diff --git a/templates/vertical-map/page-config.json b/templates/vertical-map/page-config.json index 603662232..21beb8b33 100644 --- a/templates/vertical-map/page-config.json +++ b/templates/vertical-map/page-config.json @@ -68,7 +68,7 @@ // "verticalLimit": 15, // The result count limit for vertical search // "universalLimit": 5, // The result count limit for universal search "cardType": "location-standard", // The name of the card to use - e.g. accordion, location, customcard - "icon": "pin", // The icon to use on the card for this vertical + // "icon": "pin", // The icon to use on the card for this vertical "mapConfig": { "mapProvider": "MapBox", // The name of the provider (e.g. Mapbox, Google) "pin": { diff --git a/templates/vertical-map/script/map-pin.hbs b/templates/vertical-map/script/map-pin.hbs index 2bf925670..a59c623fc 100644 --- a/templates/vertical-map/script/map-pin.hbs +++ b/templates/vertical-map/script/map-pin.hbs @@ -32,7 +32,7 @@ if (mapProvider === 'google') { config.svg = svg; {{!-- Don't use the sdk's built-in label for GoogleMapProvider --}} - config.label = {}; + config.label = ''; config.scaledSize = { w: pinStyling.width, h: pinStyling.height, diff --git a/templates/vertical-standard/page-config.json b/templates/vertical-standard/page-config.json index d67f4fc8e..7286a26f5 100644 --- a/templates/vertical-standard/page-config.json +++ b/templates/vertical-standard/page-config.json @@ -68,7 +68,7 @@ // "verticalLimit": 15, // The result count limit for vertical search // "universalLimit": 5, // The result count limit for universal search "cardType": "standard", // The name of the card to use - e.g. accordion, location, customcard - "icon": "star", // The icon to use on the card for this vertical + // "icon": "star", // The icon to use on the card for this vertical "universalSectionTemplate": "standard" } } diff --git a/test-site/config-overrides/people.json b/test-site/config-overrides/people.json index 04e824c16..53c8d3dae 100644 --- a/test-site/config-overrides/people.json +++ b/test-site/config-overrides/people.json @@ -11,12 +11,21 @@ "fieldLabels": { "Frodo": "FRODO !!!", "Marty": "MARTY !!!" - } + }, + "optionsOrder": "ASC" }, "c_employeeDepartment": { "fieldLabels": { "Strategy": "STRATEGY !!!" - } + }, + "optionsOrder": "DESC" + }, + "languages": { + "optionsOrderList": [ + "Italian", + "French", + "German" + ] } } }, diff --git a/test-site/config/global_config.json b/test-site/config/global_config.json index b86160394..91bd5f242 100644 --- a/test-site/config/global_config.json +++ b/test-site/config/global_config.json @@ -1,5 +1,5 @@ { - "sdkVersion": "1.10", // The version of the Answers SDK to use + "sdkVersion": "release/v1.11", // The version of the Answers SDK to use "apiKey": "2d8c550071a64ea23e263118a2b0680b", // The answers api key found on the experiences page. This will be provided automatically by the Yext CI system // "experienceVersion": "", // the Answers Experience version to use for API requests. This will be provided automatically by the Yext CI system // "businessId": "", // The business ID of the account. This will be provided automatically by the Yext CI system diff --git a/tests/static/js/formatters.js b/tests/static/js/formatters.js index da57bbc16..7e543d68d 100644 --- a/tests/static/js/formatters.js +++ b/tests/static/js/formatters.js @@ -5,19 +5,25 @@ describe('Formatters', () => { describe('toLocalizedDistance', () => { const profile = { d_distance: 10000000 }; // Distance in meters const distanceKey = 'd_distance'; - document.documentElement.lang = 'en'; + + beforeEach(() => { + document.documentElement.lang = ''; + }); it('Formats a distance in kilometers', () => { + document.documentElement.lang = 'en'; const distance = Formatters.toLocalizedDistance(profile, distanceKey, 'km'); expect(distance).toEqual('10,000.0 km'); }); it('Formats a distance in miles', () => { + document.documentElement.lang = 'en'; const distance = Formatters.toLocalizedDistance(profile, distanceKey, 'mi'); expect(distance).toEqual('6,213.7 mi'); }); it('Fallbacks to miles', () => { + document.documentElement.lang = 'en'; const distance = Formatters.toLocalizedDistance(profile, distanceKey, 'unknown-unit'); expect(distance).toEqual('6,213.7 mi'); }); @@ -51,6 +57,15 @@ describe('Formatters', () => { const distance = Formatters.toLocalizedDistance(profile); expect(distance).toEqual('10,000.0 km'); }); + + it('Correctly formats distance for Chinese display', () => { + document.documentElement.lang = 'zh-CN'; + let distance = Formatters.toLocalizedDistance(profile); + expect(distance).toEqual('10,000.0 km'); + document.documentElement.lang = 'zh-Hant_TW'; + distance = Formatters.toLocalizedDistance(profile); + expect(distance).toEqual('10,000.0 km'); + }); }) describe('price', () => { @@ -58,14 +73,22 @@ describe('Formatters', () => { value: '100.99', currencyCode: 'USD-US Dollar' }; + + beforeEach(() => { + document.documentElement.lang = ''; + }); + it('Formats a price in USD', () => { const price = Formatters.price(priceField, 'en'); expect(price).toEqual('$100.99'); }); + it('Formats a price in USD with no provided locale', () => { + document.documentElement.lang = 'en'; const price = Formatters.price(priceField); expect(price).toEqual('$100.99'); }); + it('Formats a price in USD with a non-en locale', () => { const price = Formatters.price(priceField, 'fr'); expect(price).toEqual('100,99 $US'); @@ -76,6 +99,7 @@ describe('Formatters', () => { const price = Formatters.price(priceField); expect(price).toEqual('€100.99'); }); + it('Formats a price in EUR with a non-en locale', () => { priceField.currencyCode = 'EUR-Euro'; const price = Formatters.price(priceField, 'fr'); diff --git a/tests/static/js/transform-facets.js b/tests/static/js/transform-facets.js index c498f9344..5df3a6c97 100644 --- a/tests/static/js/transform-facets.js +++ b/tests/static/js/transform-facets.js @@ -27,13 +27,13 @@ it('can specify both filterOptionsConfig values and fieldLabels', () => { } } const expectedTransformedFacets = [{ - displayName: "Meal type", - fieldId: "c_mealType", + displayName: 'Meal type', + fieldId: 'c_mealType', searchable: true, placeholderText: 'Search...', options: [{ ...defaultOption, - displayName: "BREAKFAST!!!", + displayName: 'BREAKFAST!!!', }] }]; const actualTransformedFacets = transformFacets(defaultFacets, facetsConfig); @@ -51,10 +51,10 @@ it('can specify filterOptionsConfig values', () => { } } const expectedTransformedFacets = [{ - displayName: "Meal type", - fieldId: "c_mealType", + displayName: 'Meal type', + fieldId: 'c_mealType', options: [defaultOption], - placeholderText: "Search", + placeholderText: 'Search', searchable: true, showMoreLimit: 5 }]; @@ -91,11 +91,11 @@ it('fieldLabels updates option displayNames', () => { } } const expectedTransformedFacets = [{ - displayName: "Meal type", - fieldId: "c_mealType", + displayName: 'Meal type', + fieldId: 'c_mealType', options: [{ ...defaultOption, - displayName: "BREAKFAST!!!", + displayName: 'BREAKFAST!!!', value: 'breakfast' }, { ...defaultOption, @@ -113,10 +113,102 @@ it('fieldLabels updates option displayNames', () => { it('facets do not change if fields is not specified in in the config', () => { const facets = [{ - displayName: "Meal type", - fieldId: "c_mealType", + displayName: 'Meal type', + fieldId: 'c_mealType', options: [defaultOption] }]; const actualTransformedFacets = transformFacets(facets, {}); expect(actualTransformedFacets).toEqual(facets); +}); + +describe('optionsOrder', () => { + const facets = [ + { + fieldId: 'c_mealType', + displayName: 'Meal type', + options: [ + { + ...defaultOption, + displayName: 'Breakfast', + value: 'breakfast' + }, + { + ...defaultOption, + displayName: 'Lunch', + value: 'lunch' + }, + { + ...defaultOption, + displayName: 'Dinner', + value: 'dinner' + } + ] + } + ]; + + function createFacetsConfig(optionsOrder, fieldLabels) { + return { + fields: { + c_mealType: { + fieldLabels: fieldLabels || { + Breakfast: 'ze breakfast', + Lunch: 'a lunch', + Dinner: 'duh dinner' + }, + optionsOrder + } + } + } + }; + + it('works for ASC order', () => { + const actualOptions = transformFacets(facets, createFacetsConfig('ASC'))[0].options; + expect(actualOptions[0].displayName).toEqual('a lunch'); + expect(actualOptions[1].displayName).toEqual('duh dinner'); + expect(actualOptions[2].displayName).toEqual('ze breakfast'); + }); + + it('works for DESC order', () => { + const actualOptions = transformFacets(facets, createFacetsConfig('DESC'))[0].options; + expect(actualOptions[0].displayName).toEqual('ze breakfast'); + expect(actualOptions[1].displayName).toEqual('duh dinner'); + expect(actualOptions[2].displayName).toEqual('a lunch'); + }); + + it('logs an error if you use an unknown optionsOrder', () => { + const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + expect(consoleError).toHaveBeenCalledTimes(0); + transformFacets(facets, createFacetsConfig('PACER'))[0].options; + expect(consoleError).toHaveBeenCalledWith( + 'Unknown facet optionsOrder "PACER" for the "c_mealType" facet.'); + consoleError.mockRestore(); + }); +}); + +describe('sorting facets using optionsOrderList', () => { + function createFacets(displayNames) { + return [ + { + fieldId: 'c_mealType', + options: displayNames.map(d => ({ displayName: d })) + } + ]; + } + + function createFacetsConfig(optionsOrderList) { + return { + fields: { + c_mealType: { + optionsOrderList + } + } + } + }; + + it('will assign priority based on the optionsOrderList', () => { + const displayNames = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + const transformedFacets = transformFacets(createFacets(displayNames), createFacetsConfig(['e', 'g'])); + const actualOptions = transformedFacets[0].options; + expect(actualOptions.map(o => o.displayName)).toEqual(['e', 'g', 'a', 'b', 'c', 'd', 'f']) + }); }); \ No newline at end of file diff --git a/theme-components/vertical-full-page-map/script.js b/theme-components/vertical-full-page-map/script.js index a8c251736..06142ad9b 100644 --- a/theme-components/vertical-full-page-map/script.js +++ b/theme-components/vertical-full-page-map/script.js @@ -22,6 +22,9 @@ ANSWERS.addComponent('VerticalFullPageMapOrchestrator', Object.assign({}, }, locale: "{{global_config.locale}}", verticalKey: "{{{verticalKey}}}", + {{#with (lookup verticalsToConfig verticalKey)}} + {{#if cardType}}cardType: "{{{cardType}}}",{{/if}} + {{/with}} verticalPages: [ {{#each verticalConfigs}} {{#if verticalKey}}