feat: Update version to 3.0.4 and enhance CustomBranding functionality
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# CustomBranding
|
||||
|
||||
CustomBranding 3.0.3 ist ein zentraler SPFx-1.4.1-Application-Customizer für SharePoint Server Subscription Edition. Die Solution lädt freigegebene Stylesheets und rendert eine kontrollierte Komponentenstruktur im oberen oder unteren SharePoint-Placeholder. Moderne und klassische Seiten verwenden dieselben `ClientSideComponentProperties`.
|
||||
CustomBranding 3.0.4 ist ein zentraler SPFx-1.4.1-Application-Customizer für SharePoint Server Subscription Edition. Die Solution lädt freigegebene Stylesheets und rendert eine kontrollierte Komponentenstruktur im oberen oder unteren SharePoint-Placeholder. Moderne und klassische Seiten verwenden dieselben `ClientSideComponentProperties`.
|
||||
|
||||
Die Konfiguration liegt in genau einer `SPSite.UserCustomAction` pro Site Collection. Es werden weder eine versteckte Liste noch ein Property Bag benötigt. Dadurch gilt das Branding automatisch für das Root Web, vorhandene Subwebs und später angelegte Subwebs.
|
||||
|
||||
@@ -16,14 +16,16 @@ Site Collection
|
||||
└── klassische Seiten: optionaler SPSite ScriptLink
|
||||
|
||||
CustomHeader
|
||||
├── CustomBrandingTopHost
|
||||
└── MegaMenuHost (wird nicht verändert)
|
||||
├── Branding-Elemente mit `data-custom-branding-placement="top"`
|
||||
└── CustomNavigation (wird nicht verändert)
|
||||
|
||||
CustomFooter
|
||||
└── CustomBrandingBottomHost
|
||||
```
|
||||
|
||||
CustomBranding verändert nur seine eigenen Host-Elemente. Das MegaMenu und andere Erweiterungen im selben Placeholder bleiben bei Navigation und erneutem Rendern erhalten.
|
||||
CustomBranding rendert seine Top-Elemente direkt in `CustomHeader` und entfernt beim erneuten Rendern nur die über `data-custom-branding-owner` markierten eigenen Knoten. `CustomNavigation` und andere Erweiterungen im selben Placeholder bleiben erhalten.
|
||||
|
||||
Die zentrale Custom-Branding-Action verwendet `Sequence = 90`. MegaMenu verwendet `100` und Current Navigation `110`, sodass Custom Branding den gemeinsamen Header zuerst vorbereitet. Die DOM-Integration bleibt trotzdem unabhängig von der tatsächlichen asynchronen Fertigstellungsreihenfolge.
|
||||
|
||||
## Voraussetzungen und Build
|
||||
|
||||
@@ -193,8 +195,8 @@ performance.getEntriesByType('resource')
|
||||
|
||||
({
|
||||
header: !!document.getElementById('CustomHeader'),
|
||||
branding: !!document.getElementById('CustomBrandingTopHost'),
|
||||
megaMenu: !!document.getElementById('MegaMenuHost')
|
||||
branding: document.querySelectorAll('#CustomHeader > [data-custom-branding-placement="top"]').length,
|
||||
megaMenu: !!document.getElementById('CustomNavigation')
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Analysestand: 20.07.2026
|
||||
Umgesetzt: 20.07.2026
|
||||
Ausgangsversion: 1.0.4
|
||||
Zielversion: 3.0.3
|
||||
Zielversion: 3.0.4
|
||||
Status: Implementierung und lokale Qualitätssicherung abgeschlossen
|
||||
|
||||
## Zielbild
|
||||
@@ -67,9 +67,10 @@ CustomBranding wird pro Site Collection genau einmal über eine `SPSite.UserCust
|
||||
|
||||
## MegaMenu-Integration
|
||||
|
||||
- [x] Stabile Hosts `CustomHeader`, `CustomBrandingTopHost`, `MegaMenuHost`, `CustomFooter` und `CustomBrandingBottomHost` definiert.
|
||||
- [x] Renderer verändert ausschließlich eigene Host-Inhalte.
|
||||
- [x] Wiederholtes Rendern löscht den `MegaMenuHost` oder fremde DOM-Knoten nicht.
|
||||
- [x] CustomBranding mit Sequence 90 vor MegaMenu (100) und Current Navigation (110) registriert.
|
||||
- [x] Top-Branding direkt als Geschwister von `CustomNavigation` in `CustomHeader` integriert; Bottom-Host bleibt separat.
|
||||
- [x] Renderer verändert ausschließlich eigene, per Ownership-Attribut markierte Top-Knoten und Bottom-Host-Inhalte.
|
||||
- [x] Wiederholtes Rendern löscht `CustomNavigation` oder fremde DOM-Knoten nicht.
|
||||
- [x] Gemeinsamer Betrieb und Ownership in der README dokumentiert.
|
||||
- [ ] Beide realen Lade-Reihenfolgen mit installiertem MegaMenu auf dem Zielserver abnehmen.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"solution": {
|
||||
"name": "custom-branding-client-side-solution",
|
||||
"id": "03a7c4de-e031-4b83-a683-5ca5c364166e",
|
||||
"version": "3.0.3.0",
|
||||
"version": "3.0.4.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true
|
||||
},
|
||||
|
||||
@@ -187,7 +187,7 @@ try {
|
||||
$action.Title = $componentTitle
|
||||
$action.Description = $Description
|
||||
$action.Location = $componentLocation
|
||||
$action.Sequence = 100
|
||||
$action.Sequence = 90
|
||||
$action.ClientSideComponentId = $componentId
|
||||
$action.ClientSideComponentProperties = [string]($configuration | ConvertTo-Json -Depth 20 -Compress)
|
||||
$action.Update()
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "custom-branding",
|
||||
"version": "3.0.3",
|
||||
"version": "3.0.4",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "custom-branding",
|
||||
"version": "3.0.3",
|
||||
"version": "3.0.4",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
|
||||
@@ -25,7 +25,7 @@ export default class CustomBrandingApplicationCustomizer
|
||||
|
||||
private _topPlaceholder: PlaceholderContent | undefined;
|
||||
private _bottomPlaceholder: PlaceholderContent | undefined;
|
||||
private _topHost: HTMLElement | undefined;
|
||||
private _topElements: HTMLElement[] = [];
|
||||
private _bottomHost: HTMLElement | undefined;
|
||||
private _renderer: BrandingDomRenderer = new BrandingDomRenderer();
|
||||
private _cssLoader: BrandingCssLoader | undefined;
|
||||
@@ -39,7 +39,7 @@ export default class CustomBrandingApplicationCustomizer
|
||||
this.context.pageContext.site.absoluteUrl
|
||||
);
|
||||
this._config = result.config;
|
||||
this.debug(strings.Initialized + ' 3.0.0.', {
|
||||
this.debug(strings.Initialized + ' 3.0.4.', {
|
||||
schemaVersion: this._config.schemaVersion,
|
||||
cssFileCount: this._config.cssfiles.length,
|
||||
warningCount: result.warnings.length
|
||||
@@ -78,11 +78,8 @@ export default class CustomBrandingApplicationCustomizer
|
||||
|
||||
if (this._topPlaceholder && this._topPlaceholder.domElement) {
|
||||
this._topPlaceholder.domElement.id = 'CustomHeader';
|
||||
this._topHost = this.getOrCreateOwnedHost(
|
||||
this._topPlaceholder.domElement,
|
||||
'CustomBrandingTopHost'
|
||||
);
|
||||
this.renderSafely(this._topHost, this._config.placeholdertop.elements);
|
||||
this.removeLegacyTopHost(this._topPlaceholder.domElement);
|
||||
this.renderTopSafely(this._topPlaceholder.domElement, this._config.placeholdertop.elements);
|
||||
}
|
||||
if (this._bottomPlaceholder && this._bottomPlaceholder.domElement) {
|
||||
this._bottomPlaceholder.domElement.id = 'CustomFooter';
|
||||
@@ -106,7 +103,7 @@ export default class CustomBrandingApplicationCustomizer
|
||||
host.style.fontSize = '14px';
|
||||
host.style.color = '#323130';
|
||||
host.style.borderTop = id === 'CustomBrandingBottomHost' ? '1px solid #edebe9' : '';
|
||||
host.style.borderBottom = id === 'CustomBrandingTopHost' ? '1px solid #edebe9' : '';
|
||||
host.style.borderBottom = '';
|
||||
return host;
|
||||
}
|
||||
|
||||
@@ -124,9 +121,48 @@ export default class CustomBrandingApplicationCustomizer
|
||||
}
|
||||
}
|
||||
|
||||
private renderTopSafely(host: HTMLElement, elements: IBrandingElement[]): void {
|
||||
this.clearTopElements();
|
||||
const staging: HTMLElement = document.createElement('div');
|
||||
try {
|
||||
this._renderer.render(staging, elements);
|
||||
} catch (error) {
|
||||
this._renderer.clear(staging);
|
||||
const status: HTMLElement = document.createElement('div');
|
||||
status.className = 'custom-branding-status';
|
||||
status.setAttribute('role', 'status');
|
||||
status.textContent = strings.RenderError;
|
||||
staging.appendChild(status);
|
||||
this.debug('Top rendering failed.', error);
|
||||
}
|
||||
|
||||
const insertBefore: ChildNode | null = host.firstChild;
|
||||
while (staging.firstChild) {
|
||||
const element: HTMLElement = staging.firstChild as HTMLElement;
|
||||
element.setAttribute('data-custom-branding-owner', COMPONENT_ID);
|
||||
element.setAttribute('data-custom-branding-placement', 'top');
|
||||
host.insertBefore(element, insertBefore);
|
||||
this._topElements.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
private clearTopElements(): void {
|
||||
for (let i: number = 0; i < this._topElements.length; i++) {
|
||||
const element: HTMLElement = this._topElements[i];
|
||||
if (element.parentNode) { element.parentNode.removeChild(element); }
|
||||
}
|
||||
this._topElements = [];
|
||||
}
|
||||
|
||||
private removeLegacyTopHost(parent: HTMLElement): void {
|
||||
const legacyHost: HTMLElement = parent.querySelector('#CustomBrandingTopHost') as HTMLElement;
|
||||
if (legacyHost && legacyHost.parentNode === parent) {
|
||||
parent.removeChild(legacyHost);
|
||||
}
|
||||
}
|
||||
|
||||
private _onTopPlaceholderDisposed = (): void => {
|
||||
this.removeHost(this._topHost);
|
||||
this._topHost = undefined;
|
||||
this.clearTopElements();
|
||||
this._topPlaceholder = undefined;
|
||||
this.debug('Top placeholder disposed; waiting for a new placeholder.');
|
||||
}
|
||||
@@ -143,12 +179,11 @@ export default class CustomBrandingApplicationCustomizer
|
||||
if (this._isDisposed) { return; }
|
||||
this._isDisposed = true;
|
||||
this.context.placeholderProvider.changedEvent.remove(this, this._renderPlaceholders);
|
||||
this.removeHost(this._topHost);
|
||||
this.clearTopElements();
|
||||
this.removeHost(this._bottomHost);
|
||||
if (this._cssLoader) {
|
||||
this._cssLoader.dispose();
|
||||
}
|
||||
this._topHost = undefined;
|
||||
this._bottomHost = undefined;
|
||||
this._topPlaceholder = undefined;
|
||||
this._bottomPlaceholder = undefined;
|
||||
|
||||
@@ -23,9 +23,10 @@ var serveText = read('config/serve.json').toLowerCase();
|
||||
var appSource = read('src/extensions/customBranding/CustomBrandingApplicationCustomizer.ts');
|
||||
var rendererSource = read('src/extensions/customBranding/BrandingDomRenderer.ts');
|
||||
var classicSource = read('classic/custom-branding-classic.js');
|
||||
var deploymentSource = read('deployment/add-custombranding.ps1');
|
||||
|
||||
assert(packageJson.version === '3.0.3', 'package.json hat nicht Version 3.0.3.');
|
||||
assert(solution.version === '3.0.3.0', 'Solution-Version ist inkonsistent.');
|
||||
assert(packageJson.version === '3.0.4', 'package.json hat nicht Version 3.0.4.');
|
||||
assert(solution.version === '3.0.4.0', 'Solution-Version ist inkonsistent.');
|
||||
assert(solution.skipFeatureDeployment === true, 'Tenantweite Bereitstellung ist nicht aktiviert.');
|
||||
assert(!solution.features, 'Die alte web-scoped Feature-Registrierung ist noch vorhanden.');
|
||||
assert(!fs.existsSync(path.join(root, 'sharepoint/assets/elements.xml')), 'elements.xml muss entfernt sein.');
|
||||
@@ -38,8 +39,13 @@ assert(classicSource.indexOf('~sitecollection/SitePages/PortalSettings.aspx') >=
|
||||
assert(appSource.indexOf('changedEvent.remove') >= 0, 'Lifecycle-Cleanup fuer changedEvent fehlt.');
|
||||
assert(appSource.indexOf('_onTopPlaceholderDisposed') >= 0 && appSource.indexOf('_onBottomPlaceholderDisposed') >= 0,
|
||||
'Top- und Bottom-Placeholder brauchen unabhaengige Dispose-Handler.');
|
||||
assert(appSource.indexOf("'CustomBrandingTopHost'") >= 0 && appSource.indexOf("'CustomBrandingBottomHost'") >= 0,
|
||||
'Stabile Branding-Hosts fehlen.');
|
||||
assert(appSource.indexOf("'CustomBrandingBottomHost'") >= 0 &&
|
||||
appSource.indexOf("'data-custom-branding-placement', 'top'") >= 0,
|
||||
'Bottom-Host oder direkte Top-Elemente fehlen.');
|
||||
assert(appSource.indexOf('renderTopSafely(this._topPlaceholder.domElement') >= 0,
|
||||
'Top-Branding wird nicht direkt in CustomHeader gerendert.');
|
||||
assert(deploymentSource.indexOf('$action.Sequence = 90') >= 0,
|
||||
'CustomBranding muss vor MegaMenu geladen werden.');
|
||||
assert(appSource.indexOf("'1px solid #edebe9'") >= 0 && appSource.indexOf('Segoe UI Web') >= 0,
|
||||
'SharePoint-Standardschrift und neutraler Host-Rahmen fehlen.');
|
||||
assert(packageJson.scripts.package.indexOf('npm test') === 0, 'Paketierung muss mit Tests beginnen.');
|
||||
|
||||
Reference in New Issue
Block a user