Aktualisiere Versionsnummer auf 3.1.0 in README.md, package.json, package-lock.json und package-solution.json. Füge CurrentNavigationSettingsProvider hinzu und integriere zugehörige Tests.
This commit is contained in:
@@ -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,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.
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+1
-1
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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.');
|
||||||
|
|||||||
@@ -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.');
|
||||||
|
|||||||
Reference in New Issue
Block a user