Update MegaMenu Application Customizer: Version bump to 1.1.0, localization support added, and unnecessary files removed
- Deleted obsolete localization file for English (en-us). - Updated version number from 1.0.4 to 1.1.0 in manifests.js and manifests.json. - Added localization support for German (de-de) in a new localization file. - Removed unnecessary dependencies from manifests. - Added .gitignore to exclude build artifacts and temporary files.
This commit is contained in:
@@ -22,7 +22,9 @@ const LOG_SOURCE: string = 'MegaMenuApplicationCustomizer';
|
||||
export const UserCustomActionMegaMenuId: string = 'abc3361f-bb2d-491f-aba3-cd51c19a299b';
|
||||
|
||||
export interface IMegaMenuApplicationCustomizerProperties {
|
||||
termSetName: string;
|
||||
termSetName?: string;
|
||||
termSetId?: string;
|
||||
cacheMinutes?: number;
|
||||
cssUrl?: string;
|
||||
debug?: boolean;
|
||||
}
|
||||
@@ -31,6 +33,8 @@ export default class MegaMenuApplicationCustomizer
|
||||
extends BaseApplicationCustomizer<IMegaMenuApplicationCustomizerProperties> {
|
||||
|
||||
private _topPlaceholder: PlaceholderContent | undefined;
|
||||
private _menuContainer: HTMLElement | undefined;
|
||||
private _renderer: MegaMenuRenderer | undefined;
|
||||
|
||||
@override
|
||||
public onInit(): Promise<void> {
|
||||
@@ -64,16 +68,18 @@ export default class MegaMenuApplicationCustomizer
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.properties.termSetName) {
|
||||
debugWarn(this._isDebugEnabled(), LOG_SOURCE, 'No termSetName configured. Mega Menu rendering is skipped.');
|
||||
const termSetIdentifier: string = this._getTermSetIdentifier();
|
||||
if (!termSetIdentifier) {
|
||||
debugWarn(this._isDebugEnabled(), LOG_SOURCE, 'No termSetId or termSetName configured. Mega Menu rendering is skipped.');
|
||||
this._renderStatus(strings.ConfigurationMissing);
|
||||
return;
|
||||
}
|
||||
|
||||
this._renderMegaMenu(this.properties.termSetName);
|
||||
this._renderMegaMenu(termSetIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
private async _renderMegaMenu(termSetName: string, debug: boolean = this._isDebugEnabled()): Promise<void> {
|
||||
private async _renderMegaMenu(termSetIdentifier: string, debug: boolean = this._isDebugEnabled()): Promise<void> {
|
||||
if (!this._topPlaceholder) {
|
||||
return;
|
||||
}
|
||||
@@ -81,28 +87,34 @@ export default class MegaMenuApplicationCustomizer
|
||||
try {
|
||||
const taxonomyService: TaxonomyNavigationService = new TaxonomyNavigationService(
|
||||
this.context,
|
||||
termSetName,
|
||||
debug
|
||||
termSetIdentifier,
|
||||
debug,
|
||||
this._getCacheMinutes()
|
||||
);
|
||||
|
||||
const menuItems: IMenuItem[] = await taxonomyService.getMenuItems();
|
||||
|
||||
const renderer: MegaMenuRenderer = new MegaMenuRenderer(
|
||||
if (this._renderer) {
|
||||
this._renderer.dispose();
|
||||
}
|
||||
|
||||
this._renderer = new MegaMenuRenderer(
|
||||
menuItems,
|
||||
debug
|
||||
);
|
||||
|
||||
const container = this._getOrCreateContainer('CustomHeader', this._topPlaceholder);
|
||||
this._menuContainer = this._getOrCreateContainer('CustomHeader', this._topPlaceholder);
|
||||
|
||||
if (container) {
|
||||
renderer.render(container);
|
||||
if (this._menuContainer) {
|
||||
this._renderer.render(this._menuContainer);
|
||||
} else {
|
||||
renderer.render(this._topPlaceholder.domElement);
|
||||
this._renderer.render(this._topPlaceholder.domElement);
|
||||
}
|
||||
|
||||
debugLog(debug, LOG_SOURCE, 'MegaMenu rendered successfully with ' + menuItems.length + ' top-level items.');
|
||||
} catch (error) {
|
||||
debugError(debug, LOG_SOURCE, 'Error rendering MegaMenu.', error);
|
||||
this._renderStatus(strings.LoadError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,9 +122,13 @@ export default class MegaMenuApplicationCustomizer
|
||||
const container = document.getElementById(id);
|
||||
|
||||
if (container) {
|
||||
const div = document.createElement('div');
|
||||
container.appendChild(div);
|
||||
return div;
|
||||
let host: HTMLElement = document.getElementById('MegaMenuHost');
|
||||
if (!host) {
|
||||
host = document.createElement('div');
|
||||
host.id = 'MegaMenuHost';
|
||||
container.appendChild(host);
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
return placeholder.domElement;
|
||||
@@ -123,6 +139,15 @@ export default class MegaMenuApplicationCustomizer
|
||||
let link: HTMLLinkElement = document.getElementById(externalCssLinkId) as HTMLLinkElement;
|
||||
|
||||
if (cssUrl && cssUrl.trim() !== '') {
|
||||
const validatedUrl: string | undefined = this._validateCssUrl(cssUrl);
|
||||
if (!validatedUrl) {
|
||||
debugWarn(debug, LOG_SOURCE, 'Rejected unsafe external CSS URL:', cssUrl);
|
||||
if (link) {
|
||||
link.remove();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!link) {
|
||||
const head: HTMLHeadElement = document.getElementsByTagName('head')[0];
|
||||
link = document.createElement('link');
|
||||
@@ -138,16 +163,76 @@ export default class MegaMenuApplicationCustomizer
|
||||
head.appendChild(link);
|
||||
}
|
||||
|
||||
link.href = cssUrl;
|
||||
link.href = validatedUrl;
|
||||
} else if (link) {
|
||||
link.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private _onDispose = (): void => {
|
||||
this.context.placeholderProvider.changedEvent.remove(this, this._renderPlaceHolders);
|
||||
if (this._renderer) {
|
||||
this._renderer.dispose();
|
||||
this._renderer = undefined;
|
||||
}
|
||||
const externalCssLink: HTMLElement = document.getElementById('mega-menu-additional-css-34FAB720');
|
||||
if (externalCssLink) {
|
||||
externalCssLink.remove();
|
||||
}
|
||||
this._menuContainer = undefined;
|
||||
this._topPlaceholder = undefined;
|
||||
debugLog(this._isDebugEnabled(), LOG_SOURCE, 'Disposed custom top placeholder.');
|
||||
}
|
||||
|
||||
private _getTermSetIdentifier(): string {
|
||||
const termSetId: string = this.properties && this.properties.termSetId
|
||||
? this.properties.termSetId.trim()
|
||||
: '';
|
||||
if (termSetId && this._isGuid(termSetId)) {
|
||||
return termSetId;
|
||||
}
|
||||
return this.properties && this.properties.termSetName
|
||||
? this.properties.termSetName.trim()
|
||||
: '';
|
||||
}
|
||||
|
||||
private _getCacheMinutes(): number {
|
||||
const value: number = this.properties ? Number(this.properties.cacheMinutes) : 15;
|
||||
return isFinite(value) ? Math.min(Math.max(Math.floor(value), 1), 1440) : 15;
|
||||
}
|
||||
|
||||
private _validateCssUrl(cssUrl: string): string | undefined {
|
||||
try {
|
||||
const parsed: URL = new URL(cssUrl.trim(), window.location.origin);
|
||||
const isHttp: boolean = parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
||||
const isSameOrigin: boolean = parsed.origin === window.location.origin;
|
||||
if (!isHttp || (!isSameOrigin && parsed.protocol !== 'https:')) {
|
||||
return undefined;
|
||||
}
|
||||
return parsed.href;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _renderStatus(message: string): void {
|
||||
if (!this._topPlaceholder) {
|
||||
return;
|
||||
}
|
||||
this._menuContainer = this._getOrCreateContainer('CustomHeader', this._topPlaceholder);
|
||||
const container: HTMLElement = this._menuContainer || this._topPlaceholder.domElement;
|
||||
container.innerHTML = '';
|
||||
const status: HTMLElement = document.createElement('div');
|
||||
status.className = 'mega-menu-status';
|
||||
status.setAttribute('role', 'status');
|
||||
status.textContent = message;
|
||||
container.appendChild(status);
|
||||
}
|
||||
|
||||
private _isGuid(value: string): boolean {
|
||||
return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(value);
|
||||
}
|
||||
|
||||
private _isDebugEnabled(debugOverride?: boolean): boolean {
|
||||
if (typeof debugOverride === 'boolean') {
|
||||
return debugOverride;
|
||||
@@ -155,4 +240,4 @@ export default class MegaMenuApplicationCustomizer
|
||||
|
||||
return !!(this.properties && this.properties.debug === true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user