feat: Add custom branding functionality with CSS and JSON configuration
- Introduced custom branding CSS styles in `custom-branding.css`. - Created example JSON configuration for custom branding in `custom-branding.example.json`. - Implemented branding configuration logic in `BrandingConfig.ts` to normalize and validate branding settings. - Developed CSS loader to manage loading and unloading of custom stylesheets in `BrandingCssLoader.ts`. - Added DOM rendering capabilities for branding elements in `BrandingDomRenderer.ts`. - Defined types and interfaces for branding elements and configurations in `BrandingTypes.ts`. - Included localization support for German in `de-de.js`. - Added unit tests for branding configuration, CSS loader, and DOM renderer. - Validated project structure and static assets with new validation scripts.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { ICssFile } from './BrandingTypes';
|
||||
|
||||
interface ISharedCssEntry {
|
||||
element: HTMLLinkElement;
|
||||
references: number;
|
||||
owned: boolean;
|
||||
}
|
||||
|
||||
export class BrandingCssLoader {
|
||||
private static _registry: { [url: string]: ISharedCssEntry } = {};
|
||||
private _loadedKeys: string[] = [];
|
||||
|
||||
constructor(private ownerId: string, private debugLog: (message: string, data?: any) => void) { } // tslint:disable-line:no-any
|
||||
|
||||
public load(files: ICssFile[]): void {
|
||||
for (let i: number = 0; i < files.length; i++) {
|
||||
this.loadOne(files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
for (let i: number = 0; i < this._loadedKeys.length; i++) {
|
||||
const key: string = this._loadedKeys[i];
|
||||
const entry: ISharedCssEntry = BrandingCssLoader._registry[key];
|
||||
if (!entry) { continue; }
|
||||
entry.references--;
|
||||
if (entry.references <= 0) {
|
||||
if (entry.owned && entry.element.parentNode) {
|
||||
entry.element.parentNode.removeChild(entry.element);
|
||||
}
|
||||
delete BrandingCssLoader._registry[key];
|
||||
}
|
||||
}
|
||||
this._loadedKeys = [];
|
||||
}
|
||||
|
||||
private loadOne(file: ICssFile): void {
|
||||
const absoluteUrl: string = this.toAbsoluteUrl(file.path);
|
||||
const key: string = absoluteUrl.toLowerCase();
|
||||
if (this._loadedKeys.indexOf(key) >= 0) { return; }
|
||||
|
||||
let entry: ISharedCssEntry = BrandingCssLoader._registry[key];
|
||||
if (entry) {
|
||||
entry.references++;
|
||||
this._loadedKeys.push(key);
|
||||
return;
|
||||
}
|
||||
|
||||
const existing: HTMLLinkElement = this.findExistingStylesheet(key);
|
||||
if (existing) {
|
||||
BrandingCssLoader._registry[key] = { element: existing, references: 1, owned: false };
|
||||
this._loadedKeys.push(key);
|
||||
return;
|
||||
}
|
||||
|
||||
const link: HTMLLinkElement = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = absoluteUrl;
|
||||
link.media = file.media || 'all';
|
||||
link.setAttribute('data-custom-branding-owner', this.ownerId);
|
||||
link.setAttribute('data-custom-branding-url', key);
|
||||
const timeout: number = window.setTimeout((): void => {
|
||||
this.debugLog('Stylesheet load timed out.', { path: file.path });
|
||||
}, 10000);
|
||||
link.onload = (): void => {
|
||||
window.clearTimeout(timeout);
|
||||
this.debugLog('Stylesheet loaded.', { path: file.path });
|
||||
};
|
||||
link.onerror = (): void => {
|
||||
window.clearTimeout(timeout);
|
||||
this.debugLog('Stylesheet failed to load.', { path: file.path });
|
||||
};
|
||||
document.head.appendChild(link);
|
||||
|
||||
entry = { element: link, references: 1, owned: true };
|
||||
BrandingCssLoader._registry[key] = entry;
|
||||
this._loadedKeys.push(key);
|
||||
}
|
||||
|
||||
private toAbsoluteUrl(path: string): string {
|
||||
const anchor: HTMLAnchorElement = document.createElement('a');
|
||||
anchor.href = path;
|
||||
return anchor.href;
|
||||
}
|
||||
|
||||
private findExistingStylesheet(key: string): HTMLLinkElement | undefined {
|
||||
const links: NodeListOf<HTMLLinkElement> = document.getElementsByTagName('link');
|
||||
for (let i: number = 0; i < links.length; i++) {
|
||||
if (String(links[i].rel || '').toLowerCase() === 'stylesheet'
|
||||
&& String(links[i].href || '').toLowerCase() === key) {
|
||||
return links[i];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user