From 6f548362e27497971651e767bff385d5b5eb155f Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Tue, 18 Aug 2026 15:09:56 +0200 Subject: [PATCH] feat: Update version to 3.0.1 and enhance portal settings configuration --- README.md | 14 +++++++++++--- ToDo.md | 7 +++++-- config/package-solution.json | 2 +- deployment/add-custombranding.ps1 | 10 ++++++++++ examples/custom-branding.example.json | 5 +++++ package-lock.json | 2 +- package.json | 2 +- src/extensions/customBranding/BrandingConfig.ts | 14 ++++++++++++-- src/extensions/customBranding/BrandingTypes.ts | 9 ++++++++- tests/BrandingConfig.test.js | 2 ++ tests/validate-static-assets.js | 4 ++-- 11 files changed, 58 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 9031cdd..2f30e0a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CustomBranding -CustomBranding 3.0 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.1 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. @@ -79,6 +79,11 @@ Das aktuelle Konfigurationsschema hat die Version 2: ```json { "schemaVersion": 2, + "portalSettings": { + "providerKey": "custombranding", + "contractVersion": 1, + "minimumPortalSettingsVersion": "3.0.0" + }, "enabled": true, "debug": false, "allowedCssHosts": [], @@ -151,12 +156,15 @@ Der site-scoped ScriptLink gilt auch für später angelegte Subwebs. Entfernen l ## PortalSettings v3 -CustomBranding funktioniert unabhängig von PortalSettings. Das geplante PortalSettings-v3-Webpart kann die Action anhand folgender Werte erkennen und die Properties schemaerhaltend bearbeiten: +CustomBranding funktioniert unabhängig von PortalSettings. PortalSettings ab Version 3.0.1 kann die zentrale Action anhand folgender Werte erkennen, die Properties schemaerhaltend bearbeiten und eine noch fehlende Action beim ersten Speichern selbst anlegen: - Component ID: `035ba968-6488-4d42-86b3-0470ffcc95b9` - Location: `ClientSideExtension.ApplicationCustomizer` - Scope: `SPSite.UserCustomActions` - Schema: `schemaVersion: 2` +- Provider-Marker: `portalSettings.providerKey: custombranding` + +Ist noch keine Action vorhanden, erscheint CustomBranding in PortalSettings als **Bereit zur Aktivierung**. Voraussetzung ist lediglich, dass `custom-branding.sppkg` bereits im App Catalog bereitgestellt wurde. Der Button **CustomBranding aktivieren** erzeugt anschließend genau eine site-scoped Action. Das PowerShell-Skript bleibt für automatisierte Rollouts und Classic-ScriptLink-Registrierungen verfügbar. Ein Editor muss unbekannte Properties erhalten und vor dem Speichern dieselben Element-, Attribut-, URL- und CSS-Grenzen beachten. @@ -195,7 +203,7 @@ window.CustomBrandingClassic.reload() ## Upgrade von 1.x -1. Paket im App Catalog durch Version `3.0.0.0` ersetzen und bereitstellen. +1. Paket im App Catalog durch Version `3.0.1.0` ersetzen und bereitstellen. 2. `add-custombranding.ps1` einmal pro Site Collection ausführen; bestehende Properties bleiben erhalten. 3. Moderne und gegebenenfalls klassische Seiten testen. 4. Erst nach erfolgreicher Abnahme alte web-scoped Aktionen als bereinigt bestätigen. diff --git a/ToDo.md b/ToDo.md index bf44ce7..051cfb8 100644 --- a/ToDo.md +++ b/ToDo.md @@ -3,12 +3,12 @@ Analysestand: 20.07.2026 Umgesetzt: 20.07.2026 Ausgangsversion: 1.0.4 -Zielversion: 3.0.0 +Zielversion: 3.0.1 Status: Implementierung und lokale Qualitätssicherung abgeschlossen ## Zielbild -CustomBranding wird pro Site Collection genau einmal über eine `SPSite.UserCustomAction` registriert und konfiguriert. Moderne und klassische Seiten lesen dieselben versionierten `ClientSideComponentProperties`. Es werden weder eine versteckte Liste noch ein Property Bag eingesetzt. PortalSettings bleibt in diesem Durchlauf unverändert; der Vertrag für das spätere PortalSettings-v3-Webpart ist in der README dokumentiert. +CustomBranding wird pro Site Collection genau einmal über eine `SPSite.UserCustomAction` registriert und konfiguriert. Moderne und klassische Seiten lesen dieselben versionierten `ClientSideComponentProperties`. Es werden weder eine versteckte Liste noch ein Property Bag eingesetzt. PortalSettings 3.0.1 kann die zentrale Action erkennen, bearbeiten und bei Bedarf beim ersten Speichern anlegen. ## Sicherheit und Konfigurationsmodell @@ -26,6 +26,9 @@ CustomBranding wird pro Site Collection genau einmal über eine `SPSite.UserCust - [x] `enabled`, `debug`, `allowedCssHosts`, `cssfiles`, Top und Bottom konsistent typisiert. - [x] Unbekannte Properties werden ignoriert; Warnungen erscheinen nur im Debug-Modus. - [x] Keine feste Abhängigkeit oder Verlinkung zu PortalSettings mehr vorhanden. +- [x] Versionierten, rein deklarativen PortalSettings-Vertragsmarker ergänzt. +- [x] Runtime, Standardkonfiguration und Deployment-Skript auf denselben Providervertrag gebracht. +- [x] Direkte Aktivierung aus PortalSettings 3.0.1 dokumentiert. ## Lifecycle und Stylesheet-Management diff --git a/config/package-solution.json b/config/package-solution.json index c230f50..98f5e03 100644 --- a/config/package-solution.json +++ b/config/package-solution.json @@ -3,7 +3,7 @@ "solution": { "name": "custom-branding-client-side-solution", "id": "03a7c4de-e031-4b83-a683-5ca5c364166e", - "version": "3.0.0.0", + "version": "3.0.1.0", "includeClientSideAssets": true, "skipFeatureDeployment": true }, diff --git a/deployment/add-custombranding.ps1 b/deployment/add-custombranding.ps1 index 055b1fd..d55f0c1 100644 --- a/deployment/add-custombranding.ps1 +++ b/deployment/add-custombranding.ps1 @@ -42,6 +42,11 @@ $classicActionName = 'CustomBranding.Classic.ScriptLink' function New-DefaultCustomBrandingConfiguration { return @{ schemaVersion = 2 + portalSettings = @{ + providerKey = 'custombranding' + contractVersion = 1 + minimumPortalSettingsVersion = '3.0.0' + } enabled = $true debug = $false allowedCssHosts = @() @@ -122,6 +127,11 @@ try { $configuration = Get-ExistingConfiguration -Action $action $configuration.schemaVersion = 2 + $configuration.portalSettings = @{ + providerKey = 'custombranding' + contractVersion = 1 + minimumPortalSettingsVersion = '3.0.0' + } if (-not $configuration.ContainsKey('placeholdertop')) { $configuration.placeholdertop = @{ elements = @() } } if (-not $configuration.ContainsKey('placeholderbottom')) { $configuration.placeholderbottom = @{ elements = @() } } if (-not $configuration.ContainsKey('cssfiles')) { $configuration.cssfiles = @() } diff --git a/examples/custom-branding.example.json b/examples/custom-branding.example.json index db826e0..00fd266 100644 --- a/examples/custom-branding.example.json +++ b/examples/custom-branding.example.json @@ -1,5 +1,10 @@ { "schemaVersion": 2, + "portalSettings": { + "providerKey": "custombranding", + "contractVersion": 1, + "minimumPortalSettingsVersion": "3.0.0" + }, "enabled": true, "debug": false, "allowedCssHosts": [], diff --git a/package-lock.json b/package-lock.json index 0221819..137e04d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "custom-branding", - "version": "3.0.0", + "version": "3.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 86ea43f..57d4596 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "custom-branding", - "version": "3.0.0", + "version": "3.0.1", "private": true, "main": "lib/index.js", "engines": { diff --git a/src/extensions/customBranding/BrandingConfig.ts b/src/extensions/customBranding/BrandingConfig.ts index 5c36196..5a02f27 100644 --- a/src/extensions/customBranding/BrandingConfig.ts +++ b/src/extensions/customBranding/BrandingConfig.ts @@ -5,7 +5,8 @@ import { IBrandingNormalizationResult, ICssFile, ICustomBrandingApplicationCustomizerProperties, - ICustomBrandingConfig + ICustomBrandingConfig, + IPortalSettingsDescriptor } from './BrandingTypes'; export const BrandingSchemaVersion: number = 2; @@ -59,6 +60,7 @@ export function normalizeBrandingConfig( const config: ICustomBrandingConfig = { schemaVersion: BrandingSchemaVersion, + portalSettings: createPortalSettingsDescriptor(), enabled: raw.enabled !== false, debug: raw.debug === true, allowedCssHosts: allowedCssHosts, @@ -128,6 +130,7 @@ export function sanitizeStyle(propertyName: string, value: string): string | und function createEmptyConfig(): ICustomBrandingConfig { return { schemaVersion: BrandingSchemaVersion, + portalSettings: createPortalSettingsDescriptor(), enabled: true, debug: false, allowedCssHosts: [], @@ -137,6 +140,14 @@ function createEmptyConfig(): ICustomBrandingConfig { }; } +function createPortalSettingsDescriptor(): IPortalSettingsDescriptor { + return { + providerKey: 'custombranding', + contractVersion: 1, + minimumPortalSettingsVersion: '3.0.0' + }; +} + function normalizeHosts(value: any): string[] { if (!Array.isArray(value)) { return []; @@ -303,4 +314,3 @@ function hasKeys(value: { [key: string]: string }): boolean { } return false; } - diff --git a/src/extensions/customBranding/BrandingTypes.ts b/src/extensions/customBranding/BrandingTypes.ts index e02317c..0cd214b 100644 --- a/src/extensions/customBranding/BrandingTypes.ts +++ b/src/extensions/customBranding/BrandingTypes.ts @@ -17,8 +17,15 @@ export interface IPlaceholderConfig { elements: IBrandingElement[]; } +export interface IPortalSettingsDescriptor { + providerKey: 'custombranding'; + contractVersion: number; + minimumPortalSettingsVersion: string; +} + export interface ICustomBrandingConfig { schemaVersion: number; + portalSettings: IPortalSettingsDescriptor; enabled: boolean; debug: boolean; allowedCssHosts: string[]; @@ -29,6 +36,7 @@ export interface ICustomBrandingConfig { export interface ICustomBrandingApplicationCustomizerProperties { schemaVersion?: number; + portalSettings?: IPortalSettingsDescriptor; enabled?: boolean; debug?: boolean; allowedCssHosts?: string[]; @@ -42,4 +50,3 @@ export interface IBrandingNormalizationResult { config: ICustomBrandingConfig; warnings: string[]; } - diff --git a/tests/BrandingConfig.test.js b/tests/BrandingConfig.test.js index 591d816..4ff1b03 100644 --- a/tests/BrandingConfig.test.js +++ b/tests/BrandingConfig.test.js @@ -18,6 +18,8 @@ function element(raw) { var empty = normalize({}, siteUrl); assert(empty.config.schemaVersion === 2, 'Das aktuelle Schema muss Version 2 verwenden.'); +assert(empty.config.portalSettings.providerKey === 'custombranding', 'PortalSettings Provider-Key fehlt.'); +assert(empty.config.portalSettings.contractVersion === 1, 'PortalSettings Contract-Version fehlt.'); assert(empty.config.enabled === true, 'Branding muss standardmaessig aktiviert sein.'); var legacy = normalize({ elements: [{ type: 'span', content: 'Alt' }] }, siteUrl); diff --git a/tests/validate-static-assets.js b/tests/validate-static-assets.js index c1af3b0..80e5beb 100644 --- a/tests/validate-static-assets.js +++ b/tests/validate-static-assets.js @@ -24,8 +24,8 @@ var appSource = read('src/extensions/customBranding/CustomBrandingApplicationCus var rendererSource = read('src/extensions/customBranding/BrandingDomRenderer.ts'); var classicSource = read('classic/custom-branding-classic.js'); -assert(packageJson.version === '3.0.0', 'package.json hat nicht Version 3.0.0.'); -assert(solution.version === '3.0.0.0', 'Solution-Version ist inkonsistent.'); +assert(packageJson.version === '3.0.1', 'package.json hat nicht Version 3.0.1.'); +assert(solution.version === '3.0.1.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.');