feat: Enhance MegaMenu with localization, caching, and new properties
- Added localization support for loading states, empty navigation, and submenu actions in both German and English. - Introduced new properties in IMenuItem for description, openInNewWindow, and external links. - Updated SPTermStorePickerService to handle cache versioning and language-specific caching. - Implemented a new MegaMenuCore service for handling term properties, cache management, and navigation URL resolution. - Refactored TaxonomyNavigationService to build menu hierarchy and filter hidden terms. - Added automated tests for MegaMenu core functionalities and static asset validation. - Removed unused ItemDictionary service. - Created a comprehensive ToDo document outlining the implementation status and future tasks.
This commit is contained in:
@@ -9,9 +9,9 @@ import { ISPTermStorePickerServiceProps } from './ISPTermStorePickerServiceProps
|
||||
import { IPickerTerm } from './IPickerTerm';
|
||||
import { ApplicationCustomizerContext } from '@microsoft/sp-application-base';
|
||||
import { debugError, debugLog, debugWarn } from './MegaMenuDebug';
|
||||
import { buildTermCacheKey, compareTermsByConfiguredOrder, readCachedValue } from './MegaMenuCore';
|
||||
|
||||
const EmptyGuid: string = '00000000-0000-0000-0000-000000000000';
|
||||
const TermCachePrefix: string = 'MegaMenu:TermSet:v2:';
|
||||
const PickerCachePrefix: string = 'MegaMenu:PickerTerms:v2:';
|
||||
|
||||
interface ITermCacheEntry {
|
||||
@@ -41,7 +41,9 @@ export default class SPTermStorePickerService {
|
||||
clientServiceUrl: this.clientServiceUrl,
|
||||
termSetNameOrId: this.props.termsetNameOrID,
|
||||
useSessionStorage: this.props.useSessionStorage,
|
||||
cacheMinutes: this.props.cacheMinutes
|
||||
cacheMinutes: this.props.cacheMinutes,
|
||||
cacheVersion: this.props.cacheVersion,
|
||||
languageLcid: this.props.languageLcid
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,7 +177,8 @@ export default class SPTermStorePickerService {
|
||||
}
|
||||
}
|
||||
|
||||
const childTerms = this.getCachedTermSet(termsetId, useSessionStorage);
|
||||
const childTerms = this.getCachedTermSet(termsetId, useSessionStorage, false);
|
||||
const staleTerms = this.getCachedTermSet(termsetId, useSessionStorage, true);
|
||||
|
||||
if (childTerms) {
|
||||
debugLog(this.debug, 'SPTermStorePickerService', 'Returning terms from session cache.', {
|
||||
@@ -272,9 +275,10 @@ export default class SPTermStorePickerService {
|
||||
expiresAt: Date.now() + ttlMinutes * 60 * 1000,
|
||||
value: termStoreResultTermSet
|
||||
};
|
||||
window.sessionStorage.setItem(TermCachePrefix + termsetId, JSON.stringify(cacheEntry));
|
||||
const cacheKey: string = this.getTermCacheKey(termsetId);
|
||||
window.sessionStorage.setItem(cacheKey, JSON.stringify(cacheEntry));
|
||||
debugLog(this.debug, 'SPTermStorePickerService', 'Term set stored in session cache.', {
|
||||
key: TermCachePrefix + termsetId,
|
||||
key: cacheKey,
|
||||
expiresAt: cacheEntry.expiresAt
|
||||
});
|
||||
}
|
||||
@@ -290,6 +294,12 @@ export default class SPTermStorePickerService {
|
||||
debugWarn(this.debug, 'SPTermStorePickerService', 'ProcessQuery response contained no term set.');
|
||||
return null;
|
||||
});
|
||||
}).catch((error: any) => {
|
||||
if (staleTerms) {
|
||||
debugWarn(this.debug, 'SPTermStorePickerService', 'Taxonomy request failed; returning stale cache.', error);
|
||||
return staleTerms;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -420,21 +430,20 @@ export default class SPTermStorePickerService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private getCachedTermSet(termId: string, useSessionStorage: boolean = true): ITermSet {
|
||||
private getCachedTermSet(termId: string, useSessionStorage: boolean = true, allowExpired: boolean = false): ITermSet {
|
||||
try {
|
||||
if (useSessionStorage && window.sessionStorage) {
|
||||
const key: string = TermCachePrefix + termId;
|
||||
const key: string = this.getTermCacheKey(termId);
|
||||
const terms: string = window.sessionStorage.getItem(key);
|
||||
if (!terms) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cacheEntry: ITermCacheEntry = JSON.parse(terms) as ITermCacheEntry;
|
||||
if (!cacheEntry || !cacheEntry.value || !cacheEntry.expiresAt || cacheEntry.expiresAt <= Date.now()) {
|
||||
const cachedValue: ITermSet = readCachedValue<ITermSet>(terms, Date.now(), allowExpired);
|
||||
if (!cachedValue && allowExpired) {
|
||||
window.sessionStorage.removeItem(key);
|
||||
return null;
|
||||
}
|
||||
return cacheEntry.value;
|
||||
return cachedValue || null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -473,6 +482,15 @@ export default class SPTermStorePickerService {
|
||||
return Math.min(Math.max(Math.floor(value), 1), 1440);
|
||||
}
|
||||
|
||||
private getTermCacheKey(termId: string): string {
|
||||
return buildTermCacheKey(
|
||||
this.props.siteCollectionUrl,
|
||||
termId,
|
||||
this.props.languageLcid,
|
||||
this.props.cacheVersion
|
||||
);
|
||||
}
|
||||
|
||||
private ensureSuccessfulResponse(response: SPHttpClientResponse, operation: string): void {
|
||||
if (!response.ok) {
|
||||
throw new Error(operation + ' request failed (' + response.status + ' ' + response.statusText + ').');
|
||||
@@ -623,23 +641,7 @@ export default class SPTermStorePickerService {
|
||||
* @param b term 2
|
||||
*/
|
||||
private sortTermByPath(a: ITerm, b: ITerm) {
|
||||
if (a.CustomSortOrderIndex === -1) {
|
||||
if (a.PathOfTerm.toLowerCase() < b.PathOfTerm.toLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
if (a.PathOfTerm.toLowerCase() > b.PathOfTerm.toLowerCase()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
if (a.CustomSortOrderIndex < b.CustomSortOrderIndex) {
|
||||
return -1;
|
||||
}
|
||||
if (a.CustomSortOrderIndex > b.CustomSortOrderIndex) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return compareTermsByConfiguredOrder(a, b);
|
||||
}
|
||||
|
||||
private convertTermToPickerTerm(term: ITerm): IPickerTerm {
|
||||
|
||||
Reference in New Issue
Block a user