Skip to content

Commit 3a8bead

Browse files
committed
Adjust teaser list
1 parent d2c1ae6 commit 3a8bead

4 files changed

Lines changed: 152 additions & 55 deletions

File tree

blocks/helpers.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export function getCurrentCountryLanguage(): string[];
2-
export async function getDictionary(): Prmomise<any> | null;
2+
export async function getDictionary(): Promise<any> | null;

blocks/helpers.js

Lines changed: 148 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import {
2+
DEFAULT_LANGUAGE,
3+
ROOT_PATH,
4+
DEFAULT_LOCALE,
5+
} from '../scripts/global/constants.js';
16
/**
27
* Get the current country and language codes label by matching the current
38
* location pathname to a regex.
@@ -10,54 +15,149 @@ export function getCurrentCountryLanguage() {
1015
return match ? match.slice(1, 3) : ['', ''];
1116
}
1217

18+
/**
19+
* Get the current language code by matching the current location pathname to a regex.
20+
*
21+
* IMPORTANT: Assumes a "/language" page structure (no countries).
22+
* @returns {string} The current code on success (e.g. "en"), empty string otherwise.
23+
*/
24+
export function getCurrentLanguage() {
25+
const match = window.location.pathname
26+
.replace(ROOT_PATH, '')
27+
.match('^/([a-z]{2})');
28+
const currentLanguage = match ? match.at(1) : DEFAULT_LANGUAGE;
29+
return currentLanguage || DEFAULT_LANGUAGE;
30+
}
31+
1332
/** @param {string[]} classes */
1433
export function cx(...classes) {
1534
return classes.filter(Boolean).join(' ');
1635
}
1736

18-
/** @type {Promise<any> | null} */
19-
let dictionaryPromise = null;
20-
export async function getDictionary() {
21-
const lang = document.documentElement.lang.toLowerCase() || 'en-us';
22-
if (dictionaryPromise === null) {
23-
dictionaryPromise = fetch('/api/dictionary.json')
24-
.then((res) => res.json());
37+
/**
38+
* Helper function to set a property on an object using a dot-separated path.
39+
* It creates nested objects as needed.
40+
* @param {object} obj The object to modify.
41+
* @param {string} path The dot-separated path (e.g., "path.to.destination").
42+
* @param {any} value The value to set at the destination.
43+
*/
44+
function setNestedProperty(obj, path, value) {
45+
const keys = path.split('.');
46+
let current = obj;
47+
// Iterate through the keys until the second-to-last key
48+
for (let i = 0; i < keys.length - 1; i += 1) {
49+
const key = keys[i];
50+
// If the nested object doesn't exist, create it.
51+
if (!current[key] || typeof current[key] !== 'object') {
52+
current[key] = {};
53+
}
54+
// Move to the next level down.
55+
current = current[key];
2556
}
57+
// Set the value on the final key.
58+
current[keys[keys.length - 1]] = value;
59+
}
2660

27-
/** @type Array<{ key:string} & Record<string, string>> */
28-
const dictionary = (await dictionaryPromise).data;
29-
const dictionaryLangValues = dictionary.filter((item) => Object.keys(item).includes(lang));
30-
const dictionaryLang = dictionaryLangValues.map((item) => {
31-
const { key } = item;
32-
const value = item[lang];
33-
return [key, value];
61+
/**
62+
* Extracts key-value pairs for a specific column and builds a nested object.
63+
* If the given column doesn't exist or any entry is empty for it,
64+
* the default language (first) and the default locale (second) are used as fallbacks.
65+
* @param {object} dataObject The full input data from the source.
66+
* @param {string} column The column to extract.
67+
* @returns {object} A new nested object with keys mapped to their translations.
68+
*/
69+
function getTranslationsForDictionaryColumn(dataObject, column) {
70+
const finalObject = {};
71+
dataObject.data.forEach((item) => {
72+
// Check if the current item has a translation for the selected column.
73+
if (Object.hasOwn(item, column)) {
74+
setNestedProperty(
75+
finalObject,
76+
item.key,
77+
item[column] || item[DEFAULT_LANGUAGE] || item[DEFAULT_LOCALE],
78+
);
79+
} else if (Object.hasOwn(item, DEFAULT_LOCALE)) {
80+
// Fallback to the "en" language or "com-en" locale
81+
setNestedProperty(finalObject, item.key, item[DEFAULT_LANGUAGE] || item[DEFAULT_LOCALE]);
82+
}
3483
});
84+
return finalObject;
85+
}
3586

36-
// key is e.g. blogpost.backtotop and value is 'Back to top'. We are creatating a
37-
// new object split by '.' and creating nested objects.
38-
const dictionaryLangNested = dictionaryLang.reduce(
39-
(/** @type {Record<string, any>} */ acc, [key, value]) => {
40-
const keys = key.split('.');
41-
const last = keys.pop();
42-
if (!last) {
43-
return acc;
44-
}
45-
const obj = acc;
46-
// eslint-disable-next-line no-shadow
47-
keys.reduce((acc, k) => {
48-
if (!(k in acc)) {
49-
acc[k] = {};
50-
}
51-
return acc[k];
52-
}, obj);
53-
obj[last] = value;
54-
return acc;
55-
},
56-
{},
57-
);
58-
return dictionaryLangNested;
87+
/**
88+
* Fetches the dictionary for a given column.
89+
* @param {string} column The column to extract.
90+
* @returns {Record<string, string>} The dictionary map for the column.
91+
*/
92+
async function fetchDictionary(column) {
93+
try {
94+
const resp = await fetch(`${window.location.origin}${window.hlx.codeBasePath}/api/dictionary.json`);
95+
const data = await resp.json();
96+
return {
97+
promise: null,
98+
data: getTranslationsForDictionaryColumn(data, column),
99+
};
100+
} catch (error) {
101+
// eslint-disable-next-line no-console
102+
console.warn(`Could not fetch dictionary for column ${column} due to:`, error);
103+
return {
104+
promise: null,
105+
data: {},
106+
};
107+
}
59108
}
109+
/**
110+
* Gets the dictionary object for the given column.
111+
* @param {string?} column The target column.
112+
* @returns {Promise<object>} The dictionary object.
113+
*/
114+
export async function getDictionaryColumn(column = null) {
115+
// Initialize window
116+
window.dictionary = window.dictionary || {};
117+
window.dictionary[column] = window.dictionary[column] || {
118+
data: null,
119+
promise: null,
120+
};
60121

122+
// IMPORTANT: never replace this entry but only mutate its fields to avoid a race condition!
123+
const stableCacheEntry = window.dictionary[column];
124+
125+
// Return dictionary if already loaded
126+
if (stableCacheEntry.data) {
127+
return stableCacheEntry.data;
128+
}
129+
130+
// Return promise if dictionary is currently loading
131+
if (stableCacheEntry.promise) {
132+
return stableCacheEntry.promise;
133+
}
134+
135+
// Fetch dictionary and store the promise
136+
stableCacheEntry.promise = fetchDictionary(column).then((result) => {
137+
stableCacheEntry.data = result.data;
138+
// Clear promise once resolved
139+
stableCacheEntry.promise = null;
140+
return stableCacheEntry.data;
141+
});
142+
143+
return stableCacheEntry.promise;
144+
}
145+
/**
146+
* Gets the dictionary object for the current site language, with fallback to the default one.
147+
*
148+
* IMPORTANT: Assumes that the dictionary spreadsheet has language only columns
149+
* (e.g. "en", "zh").
150+
* @param {string?} language The language code (e.g. "en").
151+
* @returns {Promise<object>} The dictionary object.
152+
*/
153+
export async function getDictionary(language = null) {
154+
const currentLanguage = language || getCurrentLanguage();
155+
let dictionary = await getDictionaryColumn(currentLanguage);
156+
if (!dictionary || !dictionary.length) {
157+
dictionary = await getDictionaryColumn(DEFAULT_LOCALE);
158+
}
159+
return dictionary;
160+
}
61161
/**
62162
* Creates an HTML element with the specified tag name and attributes
63163
* @param {string} tag - The HTML tag name
@@ -171,21 +271,17 @@ export async function queryEntireIndex(indexFile, pageSize = 500) {
171271
}
172272

173273
/**
174-
* Fetch query-index.json preferring localized path (/<country>-<lang>/query-index.json)
175-
* with a fallback to the root (/query-index.json). The result is cached in
176-
* the module-scoped queryIndexPromise.
177-
* @returns {Promise<any>} Parsed JSON of the query index
274+
* Fetch query-index.json preferring localized path (/<lang>/query-index.json)
275+
* with a fallback to the root (/query-index.json).
276+
* @returns {Promise<import('./types.js').IndexedPageMetadata[]>} The parsed query index
178277
*/
179-
export function getQueryIndex() {
180-
let queryIndexPromise = null;
181-
if (queryIndexPromise === null) {
182-
const [currentCountry, currentLanguage] = getCurrentCountryLanguage();
183-
const localizedUrl = `/${currentCountry}-${currentLanguage}/query-index.json`;
184-
const fallbackUrl = '/query-index';
185-
queryIndexPromise = queryEntireIndex(localizedUrl)
186-
.then((res) => (res.ok ? res : Promise.reject(new Error('Localized query-index not found'))))
187-
.then((res) => res.json())
188-
.catch(() => queryEntireIndex(fallbackUrl).then((res) => res.json()));
278+
export async function getQueryIndex() {
279+
/** @type {import('./types.js').IndexedPageMetadata[]?} */
280+
let queryIndex = null;
281+
try {
282+
queryIndex = (await queryEntireIndex(`${getCurrentLanguage()}/query-index`))?.data ?? [];
283+
} catch {
284+
queryIndex = (await queryEntireIndex('query-index'))?.data ?? [];
189285
}
190-
return queryIndexPromise;
286+
return (queryIndex ?? []);
191287
}

blocks/teaser-list/teaser-list.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ export default async function decorate(block) {
8383
teaserList.className = 'teaser-list-inner';
8484
teaserList.setAttribute('role', 'list');
8585
pagesData.forEach((page) => {
86-
const image = createOptimizedPicture(page.teaserimage, page.title, '16-9');
86+
const imageUrl = page.teaserimage || page.image;
87+
const image = createOptimizedPicture(imageUrl, page.title, false, [{ media: '(min-width: 600px)', width: '600' }]);
8788
const title = page.teasertitle || page.title;
8889

8990
const description = page.teaserdescription || page.description;

scripts/global/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const SAMPLE = 'sample';
1414
// Defaults
1515
/** @type {import("./types").ColorScheme} */
1616
export const DEFAULT_COLOR_SCHEME = 'light';
17-
export const DEFAULT_COUNTRY = 'com';
17+
export const DEFAULT_COUNTRY = 'us';
1818
export const DEFAULT_LANGUAGE = 'en';
1919
export const DEFAULT_LOCALE = `${DEFAULT_COUNTRY}-${DEFAULT_LANGUAGE}`;
2020

0 commit comments

Comments
 (0)