From 85c56a1153a9061b21ae0ac6faff8d3bec416b09 Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Sun, 19 Jul 2026 23:15:38 +0200 Subject: [PATCH] Bump version to 2.0.0, update component ID, and enhance central configuration for MegaMenu --- README.md | 17 ++- build-classic.ps1 | 4 +- classic/megamenu-classic.js | 4 +- config/package-solution.json | 4 +- config/serve.json | 4 +- deployment/add-megamenu.ps1 | 143 ++++++++++-------- package-lock.json | 2 +- package.json | 2 +- sharepoint/assets/elements.xml | 2 +- ...egaMenuApplicationCustomizer.manifest.json | 2 +- .../megaMenu/MegaMenuApplicationCustomizer.ts | 4 +- 11 files changed, 102 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 4b4e63e..05c9296 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Barrierearme, dreistufige Portalnavigation auf Basis von SharePoint Managed Meta | Eigenschaft | Wert | |---|---| -| Version | 1.1.4 | +| Version | 2.0.0 | | SharePoint Framework | SPFx 1.4.1 | | Node.js | 8.17.0 | | Zieloberfläche | Moderne SharePoint-Seiten | @@ -43,7 +43,7 @@ MegaMenuApplicationCustomizer └── responsive Darstellung ``` -Das MegaMenu liest seine Laufzeitkonfiguration aus der site-scoped UserCustomAction. Es enthält keine eigene Administrationsoberfläche. Die zentrale Pflege soll über die separate Solution **PortalSettings** erfolgen. +Das MegaMenu wird einmal pro Site Collection konfiguriert. PortalSettings speichert die zentrale Konfiguration im Property Bag des Root Webs und synchronisiert eine identische, web-scoped Runtime-Bindung auf das Root Web und alle vorhandenen Unterwebs. Das MegaMenu selbst enthält keine eigene Administrationsoberfläche. ## Voraussetzungen @@ -126,10 +126,10 @@ sharepoint/solution/mega-menu.sppkg 1. `mega-menu.sppkg` in den App Catalog laden. 2. Die Solution freigeben. -3. Die App im Root Web der gewünschten Site Collection installieren. Der MegaMenu Application Customizer wird dabei automatisch web-scoped registriert und von PortalSettings erkannt. +3. Die App im Root Web der gewünschten Site Collection installieren. Der MegaMenu Application Customizer wird dort zunächst web-scoped registriert und von PortalSettings erkannt. 4. Das Termset in PortalSettings konfigurieren. -Das folgende Skript ist nur erforderlich, wenn ausdrücklich eine site-scoped Registrierung oder eine vollständig skriptbasierte Vorkonfiguration gewünscht ist. +Das folgende Skript ist nur erforderlich, wenn PortalSettings nicht verwendet wird oder eine vollständig skriptbasierte Vorkonfiguration gewünscht ist. Empfohlen mit Termset-GUID: @@ -149,7 +149,7 @@ Abwärtskompatibel nur mit Namen: -TermSetName 'Global Navigation' ``` -Das Skript entfernt ältere web-scoped Registrierungen derselben Komponente und erstellt beziehungsweise aktualisiert eine site-scoped UserCustomAction mit dem Description-Tag `MSFT-Custom-Solution:MegaMenu`. Dieses Tag wird von PortalSettings zur Erkennung verwendet. +Das Skript speichert die Konfiguration zentral unter `PortalSettings.MegaMenu.Configuration`, entfernt alte beziehungsweise doppelte Component IDs und synchronisiert die neue web-scoped Runtime-Bindung idempotent auf alle vorhandenen Webs. Fuer spaeter angelegte Unterwebs kann es ohne Nebenwirkungen erneut ausgefuehrt werden. ## Individuelles Branding @@ -261,6 +261,13 @@ MegaMenu/ ## Versionshistorie +### 2.0.0 + +- Neue Application-Customizer-ID `c0abbffb-355d-4d4e-bd38-9e15fb811506`, um alte On-Premises-Manifest- und Loader-Zuordnungen sicher zu umgehen. +- Eine zentrale MegaMenu-Konfiguration pro Site Collection im Property Bag des Root Webs. +- Web-scoped Runtime-Bindungen werden auf Root Web und alle vorhandenen Unterwebs synchronisiert. +- Aktivierungsskript entfernt alte Component IDs und kann fuer neue Unterwebs idempotent erneut ausgefuehrt werden. + ### 1.1.4 - Unnötige externe Abhängigkeit zu `@microsoft/sp-lodash-subset` entfernt. diff --git a/build-classic.ps1 b/build-classic.ps1 index d1f13e1..9e055cc 100644 --- a/build-classic.ps1 +++ b/build-classic.ps1 @@ -113,7 +113,7 @@ Use one of these methods: ````powershell # Connect to SharePoint site Add-PnPCustomAction -Name "MegaMenuConfig" -Location "ClientSideExtension.ApplicationCustomizer" ` - -ClientSideComponentId "abc3361f-bb2d-491f-aba3-cd51c19a299b" ` + -ClientSideComponentId "c0abbffb-355d-4d4e-bd38-9e15fb811506" ` -ClientSideComponentProperties '{"termSetName":"Your Navigation Termset","cssUrl":"/SiteAssets/custom-menu.css"}' ```` @@ -269,4 +269,4 @@ Write-Host "✓ Same CSS as SPFx version" -ForegroundColor Green Write-Host "✓ Same service logic (extracted to standalone)" -ForegroundColor Green Write-Host "✓ Same configuration system" -ForegroundColor Green Write-Host "✓ Same menu rendering output" -ForegroundColor Green -Write-Host "✓ No SPFx dependencies in classic version" -ForegroundColor Green \ No newline at end of file +Write-Host "✓ No SPFx dependencies in classic version" -ForegroundColor Green diff --git a/classic/megamenu-classic.js b/classic/megamenu-classic.js index 4c6373c..4497ee2 100644 --- a/classic/megamenu-classic.js +++ b/classic/megamenu-classic.js @@ -19,7 +19,7 @@ (function() { 'use strict'; - var MEGAMENU_UCA_ID = 'abc3361f-bb2d-491f-aba3-cd51c19a299b'; // Same as in MegaMenuApplicationCustomizer.ts + var MEGAMENU_UCA_ID = 'c0abbffb-355d-4d4e-bd38-9e15fb811506'; // Same as in MegaMenuApplicationCustomizer.ts var config = window.MegaMenuConfig || { containerId: 's4-titlerow', @@ -262,4 +262,4 @@ log('MegaMenu Classic wrapper loaded. Waiting for dependencies...'); -})(); \ No newline at end of file +})(); diff --git a/config/package-solution.json b/config/package-solution.json index 0f089a1..c8f1dee 100644 --- a/config/package-solution.json +++ b/config/package-solution.json @@ -3,7 +3,7 @@ "solution": { "name": "mega-menu-client-side-solution", "id": "f4660e06-ce08-43ee-bfb7-5c4464e01133", - "version": "1.1.4.0", + "version": "2.0.0.0", "includeClientSideAssets": true, "skipFeatureDeployment": false, "features": [ @@ -11,7 +11,7 @@ "title": "MegaMenu extension registration", "description": "Registers the MegaMenu Application Customizer in the host web.", "id": "1e4fdc74-8053-48b2-a15b-e975d61eb71f", - "version": "1.1.4.0", + "version": "2.0.0.0", "assets": { "elementManifests": [ "elements.xml" diff --git a/config/serve.json b/config/serve.json index 3d9f288..6b3fb0c 100644 --- a/config/serve.json +++ b/config/serve.json @@ -6,7 +6,7 @@ "default": { "pageUrl": "https://sharepoint.contoso.local/SitePages/Neues-Zuhause.aspx", "customActions": { - "abc3361f-bb2d-491f-aba3-cd51c19a299b": { + "c0abbffb-355d-4d4e-bd38-9e15fb811506": { "location": "ClientSideExtension.ApplicationCustomizer", "properties": { "termSetName": "Horizontale Navigation", @@ -18,7 +18,7 @@ "megaMenu": { "pageUrl": "https://contoso.sharepoint.com/sites/mySite/SitePages/myPage.aspx", "customActions": { - "abc3361f-bb2d-491f-aba3-cd51c19a299b": { + "c0abbffb-355d-4d4e-bd38-9e15fb811506": { "location": "ClientSideExtension.ApplicationCustomizer", "properties": { "testMessage": "Test message" diff --git a/deployment/add-megamenu.ps1 b/deployment/add-megamenu.ps1 index bfc3d8e..b0da6d8 100644 --- a/deployment/add-megamenu.ps1 +++ b/deployment/add-megamenu.ps1 @@ -17,90 +17,99 @@ param( Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null -$componentId = 'abc3361f-bb2d-491f-aba3-cd51c19a299b' +$componentId = [Guid]'c0abbffb-355d-4d4e-bd38-9e15fb811506' +$legacyComponentId = [Guid]'abc3361f-bb2d-491f-aba3-cd51c19a299b' $location = 'ClientSideExtension.ApplicationCustomizer' -$name = 'MegaMenu' -$title = 'Mega Menu' +$configurationProperty = 'PortalSettings.MegaMenu.Configuration' -function Get-MegaMenuPropertiesJson { - param( - [string]$CurrentTermSetName, - [string]$CurrentTermSetId, - [int]$CurrentCacheMinutes, - [bool]$CurrentDebug +$properties = @{ + termSetName = $TermSetName + termSetId = $TermSetId + cacheMinutes = $CacheMinutes + debug = [bool]$EnableDebug.IsPresent +} | ConvertTo-Json -Depth 10 -Compress + +$site = Get-SPSite -Identity $SiteUrl + +try { + $rootWeb = $site.RootWeb + $rootWeb.AllProperties[$configurationProperty] = $properties + $rootWeb.Update() + + $siteActions = @( + $site.UserCustomActions | + Where-Object { + $_.ClientSideComponentId -eq $componentId -or + $_.ClientSideComponentId -eq $legacyComponentId + } ) - return (@{ - termSetName = $CurrentTermSetName - termSetId = $CurrentTermSetId - cacheMinutes = $CurrentCacheMinutes - debug = $CurrentDebug - } | ConvertTo-Json -Depth 10 -Compress) -} + foreach ($siteAction in $siteActions) { + $siteAction.Delete() + } -function Remove-WebScopedComponentActions { - param( - [Microsoft.SharePoint.SPSite]$CurrentSite, - [string]$CurrentComponentId, - [string]$CurrentLocation - ) + $created = 0 + $updated = 0 + $removedLegacy = 0 - $removedCount = 0 - - foreach ($web in $CurrentSite.AllWebs) { + foreach ($web in $site.AllWebs) { try { - $webActions = @($web.UserCustomActions | Where-Object { - $_.Location -eq $CurrentLocation -and - $_.ClientSideComponentId -and - $_.ClientSideComponentId.ToString().ToLower() -eq $CurrentComponentId - }) + $actions = @( + $web.UserCustomActions | + Where-Object { + $_.ClientSideComponentId -eq $componentId -or + $_.ClientSideComponentId -eq $legacyComponentId + } + ) - foreach ($webAction in $webActions) { - $web.UserCustomActions.Delete($webAction.Id) - $removedCount++ + $currentAction = $actions | + Where-Object { + $_.ClientSideComponentId -eq $componentId + } | + Select-Object -First 1 + + foreach ($existingAction in $actions) { + if ($currentAction -and $existingAction.Id -eq $currentAction.Id) { + continue + } + + $existingAction.Delete() + $removedLegacy++ } - if ($webActions.Count -gt 0) { - $web.Update() + if (-not $currentAction) { + $currentAction = $web.UserCustomActions.Add() + $currentAction.Name = [Guid]::NewGuid().ToString('B') + $created++ } + else { + $updated++ + } + + $currentAction.Title = 'MegaMenu Application Customizer' + $currentAction.Description = $Description + $currentAction.Location = $location + $currentAction.ClientSideComponentId = $componentId + $currentAction.ClientSideComponentProperties = $properties + $currentAction.Sequence = 100 + $currentAction.Update() + $web.Update() + + Write-Host ('MegaMenu synchronisiert: ' + $web.Url) -ForegroundColor Green } finally { $web.Dispose() } } - return $removedCount -} - -$site = Get-SPSite -Identity $SiteUrl -try { - $removedWebScopedActions = Remove-WebScopedComponentActions -CurrentSite $site -CurrentComponentId $componentId -CurrentLocation $location - - $existingAction = $site.UserCustomActions | Where-Object { - $_.Location -eq $location -and $_.ClientSideComponentId -and $_.ClientSideComponentId.ToString().ToLower() -eq $componentId - } | Select-Object -First 1 - - if ($existingAction) { - $action = $existingAction - Write-Host 'Aktualisiere vorhandene site-scoped Mega Menu UserCustomAction...' -ForegroundColor Yellow - } - else { - $action = $site.UserCustomActions.Add() - Write-Host 'Erstelle neue site-scoped Mega Menu UserCustomAction...' -ForegroundColor Yellow - } - - $action.Name = $name - $action.Title = $title - $action.Description = $Description - $action.Location = $location - $action.ClientSideComponentId = [Guid]$componentId - $action.ClientSideComponentProperties = Get-MegaMenuPropertiesJson -CurrentTermSetName $TermSetName -CurrentTermSetId $TermSetId -CurrentCacheMinutes $CacheMinutes -CurrentDebug ([bool]$EnableDebug.IsPresent) - $action.Update() - - Write-Host 'Mega Menu wurde site-scoped registriert.' -ForegroundColor Green - Write-Host ('Entfernte web-scoped Eintraege: ' + $removedWebScopedActions) - Write-Host ('Beschreibung: ' + $Description) - Write-Host ('Properties: ' + $action.ClientSideComponentProperties) + Write-Host '' + Write-Host 'MegaMenu wurde fuer die gesamte Site Collection konfiguriert.' -ForegroundColor Green + Write-Host ('Zentrale Konfiguration: ' + $configurationProperty) + Write-Host ('Neu registrierte Webs: ' + $created) + Write-Host ('Aktualisierte Webs: ' + $updated) + Write-Host ('Entfernte alte oder doppelte Registrierungen: ' + $removedLegacy) + Write-Host ('Properties: ' + $properties) + Write-Host 'Neue Unterwebs koennen durch erneutes Ausfuehren idempotent synchronisiert werden.' -ForegroundColor Yellow } finally { if ($site) { diff --git a/package-lock.json b/package-lock.json index 13c06c1..88d1074 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "mega-menu", - "version": "1.1.4", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e44c777..11625bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mega-menu", - "version": "1.1.4", + "version": "2.0.0", "private": true, "main": "lib/index.js", "engines": { diff --git a/sharepoint/assets/elements.xml b/sharepoint/assets/elements.xml index f3bbefd..c4341a0 100644 --- a/sharepoint/assets/elements.xml +++ b/sharepoint/assets/elements.xml @@ -3,6 +3,6 @@ diff --git a/src/extensions/megaMenu/MegaMenuApplicationCustomizer.manifest.json b/src/extensions/megaMenu/MegaMenuApplicationCustomizer.manifest.json index fd24ca2..e725db0 100644 --- a/src/extensions/megaMenu/MegaMenuApplicationCustomizer.manifest.json +++ b/src/extensions/megaMenu/MegaMenuApplicationCustomizer.manifest.json @@ -1,7 +1,7 @@ { "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-extension-manifest.schema.json", - "id": "abc3361f-bb2d-491f-aba3-cd51c19a299b", + "id": "c0abbffb-355d-4d4e-bd38-9e15fb811506", "alias": "MegaMenuApplicationCustomizer", "componentType": "Extension", "extensionType": "ApplicationCustomizer", diff --git a/src/extensions/megaMenu/MegaMenuApplicationCustomizer.ts b/src/extensions/megaMenu/MegaMenuApplicationCustomizer.ts index 50675c8..42acdcd 100644 --- a/src/extensions/megaMenu/MegaMenuApplicationCustomizer.ts +++ b/src/extensions/megaMenu/MegaMenuApplicationCustomizer.ts @@ -19,7 +19,7 @@ import { debugError, debugLog, debugWarn } from '../../services/MegaMenuDebug'; import './MegaMenu.module.scss'; const LOG_SOURCE: string = 'MegaMenuApplicationCustomizer'; -export const UserCustomActionMegaMenuId: string = 'abc3361f-bb2d-491f-aba3-cd51c19a299b'; +export const UserCustomActionMegaMenuId: string = 'c0abbffb-355d-4d4e-bd38-9e15fb811506'; export interface IMegaMenuApplicationCustomizerProperties { termSetName?: string; @@ -40,7 +40,7 @@ export default class MegaMenuApplicationCustomizer const debug: boolean = this._isDebugEnabled(); debugLog(debug, LOG_SOURCE, 'onInit started.', { componentId: UserCustomActionMegaMenuId, - version: '1.1.4', + version: '2.0.0', properties: this.properties || {}, pageUrl: window.location.href, webUrl: this.context.pageContext.web.absoluteUrl,