dev-3.0 #1

Merged
Torsten merged 10 commits from dev-3.0 into main 2026-08-18 20:49:15 +00:00
9 changed files with 173 additions and 9 deletions
Showing only changes of commit c0e7c316e3 - Show all commits
+11 -2
View File
@@ -6,7 +6,7 @@ PortalSettings V3 ist ein providerbasiertes SPFx-WebPart für SharePoint Server
| Eigenschaft | Wert | | Eigenschaft | Wert |
|---|---| |---|---|
| Version | 3.0.4 | | Version | 3.1.0 |
| SharePoint Framework | 1.4.1 | | SharePoint Framework | 1.4.1 |
| Build-Node | 8.17.0 | | Build-Node | 8.17.0 |
| npm | 6.x | | npm | 6.x |
@@ -26,6 +26,14 @@ Die frühere provisionierte `PortalSettings.aspx` und der leere Application Cust
- Debug-Modus - Debug-Modus
- schemaerhaltendes Speichern unbekannter Properties - schemaerhaltendes Speichern unbekannter Properties
### CurrentNavigation
- Auswahl des Navigationstermsets mit demselben Termset-Picker wie beim MegaMenu
- Cache-Dauer, Cache-Version und Debug-Modus
- maximale Navigationstiefe und optionale Anzeige des Root-Terms
- direkte Aktivierung als zentrale SPSite.UserCustomAction
- Hinweise zu CurrentNavigation.Root und CurrentNavigation.Hidden
### CustomBranding ### CustomBranding
- zentrale `SPSite.UserCustomAction` - zentrale `SPSite.UserCustomAction`
@@ -56,6 +64,7 @@ PortalSettingsWebPart
│ │
├── ProviderRegistry ├── ProviderRegistry
│ ├── MegaMenuSettingsProvider │ ├── MegaMenuSettingsProvider
│ ├── CurrentNavigationSettingsProvider
│ ├── CustomBrandingSettingsProvider │ ├── CustomBrandingSettingsProvider
│ └── ExpiryIndicatorSettingsProvider │ └── ExpiryIndicatorSettingsProvider
│ │
@@ -155,7 +164,7 @@ sharepoint/solution/portal-settings.sppkg
## Installation ## Installation
1. `portal-settings.sppkg` im App Catalog durch Version `3.0.4.0` ersetzen. 1. `portal-settings.sppkg` im App Catalog durch Version `3.1.0.0` ersetzen.
2. Die App im Root Web der gewünschten Site Collection installieren oder aktualisieren. 2. Die App im Root Web der gewünschten Site Collection installieren oder aktualisieren.
3. Eine moderne Seite erstellen. 3. Eine moderne Seite erstellen.
4. Das WebPart **Portal Settings** einmal auf der Seite hinzufügen. 4. Das WebPart **Portal Settings** einmal auf der Seite hinzufügen.
+2 -1
View File
@@ -2,7 +2,7 @@
Stand: 21.07.2026 Stand: 21.07.2026
Branch: `dev-3.0` Branch: `dev-3.0`
Zielversion: 3.0.4 Zielversion: 3.1.0
## Architektur ## Architektur
@@ -33,6 +33,7 @@ Zielversion: 3.0.4
## Provider ## Provider
- [x] MegaMenu vollständig auf Provider-Schema umgestellt. - [x] MegaMenu vollständig auf Provider-Schema umgestellt.
- [x] CurrentNavigation mit Termset-Picker, Cache, Debug und Aktivierung integriert.
- [x] Termset, Modus, Cache, Cache-Version und Debug abgedeckt. - [x] Termset, Modus, Cache, Cache-Version und Debug abgedeckt.
- [x] CustomBranding vollständig auf Schema 2 abgebildet. - [x] CustomBranding vollständig auf Schema 2 abgebildet.
- [x] Fehlende CustomBranding-Site-Action kann beim ersten Speichern angelegt werden. - [x] Fehlende CustomBranding-Site-Action kann beim ersten Speichern angelegt werden.
+1 -1
View File
@@ -3,7 +3,7 @@
"solution": { "solution": {
"name": "portal-settings-client-side-solution", "name": "portal-settings-client-side-solution",
"id": "1c98e32d-bb7b-43b7-9625-074d6e4ea286", "id": "1c98e32d-bb7b-43b7-9625-074d6e4ea286",
"version": "3.0.4.0", "version": "3.1.0.0",
"includeClientSideAssets": true, "includeClientSideAssets": true,
"skipFeatureDeployment": false "skipFeatureDeployment": false
}, },
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "portal-settings", "name": "portal-settings",
"version": "3.0.4", "version": "3.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "portal-settings", "name": "portal-settings",
"version": "3.0.4", "version": "3.1.0",
"private": true, "private": true,
"main": "lib/index.js", "main": "lib/index.js",
"engines": { "engines": {
@@ -0,0 +1,135 @@
import { ISettingsProvider } from '../core/ProviderContracts';
export interface ICurrentNavigationSettings {
schemaVersion: number;
portalSettings: {
providerKey: string;
contractVersion: number;
minimumPortalSettingsVersion: string;
};
termSetId: string;
termSetName: string;
cacheMinutes: number;
cacheVersion: string;
maxDepth: number;
showRoot: boolean;
debug: boolean;
[key: string]: any; // tslint:disable-line:no-any
}
export const CurrentNavigationSettingsProvider: ISettingsProvider<ICurrentNavigationSettings> = {
key: 'currentnavigation',
displayName: 'Current Navigation',
description: 'Navigation für das jeweils aktuelle Root Web oder Subweb.',
iconName: 'BulletedTreeList',
schemaVersion: 1,
minimumPortalSettingsVersion: '3.1.0',
componentIds: ['e37b828f-4315-461a-b1b0-fcf991deda3d'],
storage: {
kind: 'siteUserCustomAction',
action: {
name: 'CurrentNavigation',
title: 'Current Navigation Application Customizer',
description: 'MSFT-Custom-Solution:CurrentNavigation',
location: 'ClientSideExtension.ApplicationCustomizer',
sequence: 110
}
},
sections: [
{
key: 'navigation', title: 'Navigation', iconName: 'BranchFork', layout: 'twoColumns',
description: 'Termset und Darstellung der Navigation für die aktuelle Website.',
elements: [
{
type: 'termSet', key: 'termSetId', path: 'termSetId', companionNamePath: 'termSetName',
label: 'Navigations-Termset', description: 'Das Termset enthält die Root- und Navigationsterms.',
required: true, columnSpan: 12
},
{
type: 'number', key: 'maxDepth', path: 'maxDepth', label: 'Maximale Tiefe',
description: 'Anzahl der gerenderten Ebenen ab dem Root-Term.', minimum: 1, maximum: 8,
suffix: 'Ebenen', columnSpan: 6
},
{
type: 'boolean', key: 'showRoot', path: 'showRoot', label: 'Root-Term anzeigen',
description: 'Zeigt den zugeordneten Root-Term als erste Ebene an.', columnSpan: 6
},
{
type: 'notice', appearance: 'info', title: 'Local Custom Properties', columnSpan: 12,
text: 'CurrentNavigation.Root = true ordnet einen Term über seine URL einer Website zu. CurrentNavigation.Hidden = true blendet einen Zweig aus.'
}
]
},
{
key: 'performance', title: 'Performance und Cache', iconName: 'SpeedHigh', layout: 'twoColumns',
elements: [
{
type: 'number', key: 'cacheMinutes', path: 'cacheMinutes', label: 'Cache-Dauer',
description: 'Gültigkeitsdauer des Browser-Caches.', minimum: 1, maximum: 1440,
suffix: 'Minuten', columnSpan: 6
},
{
type: 'textField', key: 'cacheVersion', path: 'cacheVersion', label: 'Cache-Version',
description: 'Eine Änderung verwirft vorhandene Browser-Caches.', placeholder: '1', columnSpan: 6
}
]
},
{
key: 'diagnostics', title: 'Diagnose', iconName: 'Diagnostic', layout: 'singleColumn',
collapsible: true, collapsedByDefault: true,
elements: [{
type: 'boolean', key: 'debug', path: 'debug', label: 'Debug-Ausgaben aktivieren',
description: 'Schreibt technische Meldungen in die Browserkonsole.', columnSpan: 12
}]
},
{
key: 'preview', title: 'Zusammenfassung', layout: 'singleColumn',
elements: [
{ type: 'propertySummary', title: 'Aktuelle Werte', columnSpan: 12 },
{ type: 'jsonPreview', title: 'ClientSideComponentProperties', collapsedByDefault: true, columnSpan: 12 }
]
}
],
createDefault: (): ICurrentNavigationSettings => ({
schemaVersion: 1,
portalSettings: {
providerKey: 'currentnavigation', contractVersion: 1, minimumPortalSettingsVersion: '3.1.0'
},
termSetId: '', termSetName: '', cacheMinutes: 15, cacheVersion: '1',
maxDepth: 3, showRoot: false, debug: false
}),
normalize: (value: any): ICurrentNavigationSettings => { // tslint:disable-line:no-any
const source: any = value && typeof value === 'object' ? value : {}; // tslint:disable-line:no-any
const result: any = {}; // tslint:disable-line:no-any
Object.keys(source).forEach((key: string): void => { result[key] = source[key]; });
const minutes: number = Number(source.cacheMinutes);
const depth: number = Number(source.maxDepth);
result.schemaVersion = 1;
result.portalSettings = {
providerKey: 'currentnavigation', contractVersion: 1, minimumPortalSettingsVersion: '3.1.0'
};
result.termSetId = typeof source.termSetId === 'string' ? source.termSetId.trim() : '';
result.termSetName = typeof source.termSetName === 'string' ? source.termSetName.trim() : '';
result.cacheMinutes = isFinite(minutes) ? Math.min(Math.max(Math.floor(minutes), 1), 1440) : 15;
result.cacheVersion = typeof source.cacheVersion === 'string' && source.cacheVersion.trim()
? source.cacheVersion.trim() : '1';
result.maxDepth = isFinite(depth) ? Math.min(Math.max(Math.floor(depth), 1), 8) : 3;
result.showRoot = source.showRoot === true;
result.debug = source.debug === true;
return result as ICurrentNavigationSettings;
},
validate: (value: ICurrentNavigationSettings): string[] => {
const errors: string[] = [];
if (!value.termSetId) { errors.push('Bitte ein Navigations-Termset auswählen.'); }
if (value.termSetId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value.termSetId)) {
errors.push('Die Termset-ID ist keine gültige GUID.');
}
if (value.cacheMinutes < 1 || value.cacheMinutes > 1440) {
errors.push('Die Cache-Dauer muss zwischen 1 und 1440 liegen.');
}
if (value.maxDepth < 1 || value.maxDepth > 8) {
errors.push('Die maximale Tiefe muss zwischen 1 und 8 liegen.');
}
return errors;
}
};
+2
View File
@@ -1,10 +1,12 @@
import { ISettingsProvider } from '../core/ProviderContracts'; import { ISettingsProvider } from '../core/ProviderContracts';
import { CustomBrandingSettingsProvider } from './CustomBrandingSettingsProvider'; import { CustomBrandingSettingsProvider } from './CustomBrandingSettingsProvider';
import { CurrentNavigationSettingsProvider } from './CurrentNavigationSettingsProvider';
import { ExpiryIndicatorSettingsProvider } from './ExpiryIndicatorSettingsProvider'; import { ExpiryIndicatorSettingsProvider } from './ExpiryIndicatorSettingsProvider';
import { MegaMenuSettingsProvider } from './MegaMenuSettingsProvider'; import { MegaMenuSettingsProvider } from './MegaMenuSettingsProvider';
const Providers: Array<ISettingsProvider<any>> = [ // tslint:disable-line:no-any const Providers: Array<ISettingsProvider<any>> = [ // tslint:disable-line:no-any
MegaMenuSettingsProvider, MegaMenuSettingsProvider,
CurrentNavigationSettingsProvider,
CustomBrandingSettingsProvider, CustomBrandingSettingsProvider,
ExpiryIndicatorSettingsProvider ExpiryIndicatorSettingsProvider
]; ];
+18 -1
View File
@@ -9,8 +9,9 @@ function assert(condition: boolean, message: string): void {
} }
const providers: Array<ISettingsProvider<any>> = getSettingsProviders(); // tslint:disable-line:no-any const providers: Array<ISettingsProvider<any>> = getSettingsProviders(); // tslint:disable-line:no-any
assert(providers.length === 3, 'Genau drei Provider werden erwartet.'); assert(providers.length === 4, 'Genau vier Provider werden erwartet.');
assert(!!getSettingsProvider('megamenu'), 'MegaMenu-Provider fehlt.'); assert(!!getSettingsProvider('megamenu'), 'MegaMenu-Provider fehlt.');
assert(!!getSettingsProvider('currentnavigation'), 'CurrentNavigation-Provider fehlt.');
assert(!!getSettingsProvider('custombranding'), 'CustomBranding-Provider fehlt.'); assert(!!getSettingsProvider('custombranding'), 'CustomBranding-Provider fehlt.');
assert(!!getSettingsProvider('expiryindicator'), 'ExpiryIndicator-Provider fehlt.'); assert(!!getSettingsProvider('expiryindicator'), 'ExpiryIndicator-Provider fehlt.');
@@ -31,6 +32,22 @@ assert(normalizedMega.menuMode === 'megaMenu', 'MegaMenu Modus-Fallback fehlt.')
assert(normalizedMega.portalSettings.providerKey === 'megamenu', 'MegaMenu-Vertragsmarker fehlt.'); assert(normalizedMega.portalSettings.providerKey === 'megamenu', 'MegaMenu-Vertragsmarker fehlt.');
assert(normalizedMega.unknownFuture === 'keep', 'Unbekannte MegaMenu-Properties gehen verloren.'); assert(normalizedMega.unknownFuture === 'keep', 'Unbekannte MegaMenu-Properties gehen verloren.');
const current: ISettingsProvider<any> = getSettingsProvider('currentnavigation'); // tslint:disable-line:no-any
assert(!!current.storage.action, 'CurrentNavigation-Aktivierungsdefinition fehlt.');
assert(current.storage.action.name === 'CurrentNavigation', 'CurrentNavigation Action-Name ist nicht stabil.');
assert(current.storage.action.sequence === 110, 'CurrentNavigation Action-Sequenz ist nicht stabil.');
const normalizedCurrent: any = current.normalize({ // tslint:disable-line:no-any
cacheMinutes: 5000, cacheVersion: '', maxDepth: 99, showRoot: true, future: 'keep'
});
assert(normalizedCurrent.cacheMinutes === 1440, 'CurrentNavigation Cache-Maximum greift nicht.');
assert(normalizedCurrent.cacheVersion === '1', 'CurrentNavigation Cache-Fallback fehlt.');
assert(normalizedCurrent.maxDepth === 8, 'CurrentNavigation Tiefenlimit greift nicht.');
assert(normalizedCurrent.showRoot === true, 'CurrentNavigation Root-Anzeige geht verloren.');
assert(normalizedCurrent.future === 'keep', 'Unbekannte CurrentNavigation-Properties gehen verloren.');
const invalidCurrent: any = current.createDefault(); // tslint:disable-line:no-any
invalidCurrent.termSetName = 'Nur ein Name';
assert(current.validate(invalidCurrent).length > 0, 'CurrentNavigation muss eine Termset-ID verlangen.');
const branding: ISettingsProvider<any> = getSettingsProvider('custombranding'); // tslint:disable-line:no-any const branding: ISettingsProvider<any> = getSettingsProvider('custombranding'); // tslint:disable-line:no-any
assert(!!branding.storage.action, 'CustomBranding-Aktivierungsdefinition fehlt.'); assert(!!branding.storage.action, 'CustomBranding-Aktivierungsdefinition fehlt.');
assert(branding.storage.action.name === 'CustomBranding', 'CustomBranding Action-Name ist nicht stabil.'); assert(branding.storage.action.name === 'CustomBranding', 'CustomBranding Action-Name ist nicht stabil.');
+2 -2
View File
@@ -21,8 +21,8 @@ var sourceFiles = [
'src/ui/PortalSettingsApp.ts' 'src/ui/PortalSettingsApp.ts'
].map(read).join('\n'); ].map(read).join('\n');
assert(packageJson.version === '3.0.4', 'Package-Version ist nicht 3.0.4.'); assert(packageJson.version === '3.1.0', 'Package-Version ist nicht 3.1.0.');
assert(solution.version === '3.0.4.0', 'Solution-Version ist nicht 3.0.4.0.'); assert(solution.version === '3.1.0.0', 'Solution-Version ist nicht 3.1.0.0.');
assert(!solution.features, 'Alte ASPX-Feature-Assets sind noch registriert.'); assert(!solution.features, 'Alte ASPX-Feature-Assets sind noch registriert.');
assert(!!config.bundles['portal-settings-web-part'], 'WebPart-Bundle fehlt.'); assert(!!config.bundles['portal-settings-web-part'], 'WebPart-Bundle fehlt.');
assert(copyAssets.deployCdnPath === 'temp/deploy', 'Ship-Assets werden nicht nach temp/deploy geschrieben.'); assert(copyAssets.deployCdnPath === 'temp/deploy', 'Ship-Assets werden nicht nach temp/deploy geschrieben.');