dev-3.0 #1

Merged
Torsten merged 10 commits from dev-3.0 into main 2026-08-18 20:49:15 +00:00
12 changed files with 49 additions and 16 deletions
Showing only changes of commit 64a157f7a8 - Show all commits
+2 -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.1 | | Version | 3.0.2 |
| 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 |
@@ -155,7 +155,7 @@ sharepoint/solution/portal-settings.sppkg
## Installation ## Installation
1. `portal-settings.sppkg` im App Catalog durch Version `3.0.1.0` ersetzen. 1. `portal-settings.sppkg` im App Catalog durch Version `3.0.2.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.
+1 -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.1 Zielversion: 3.0.2
## Architektur ## Architektur
+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.1.0", "version": "3.0.2.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.1", "version": "3.0.2",
"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.1", "version": "3.0.2",
"private": true, "private": true,
"main": "lib/index.js", "main": "lib/index.js",
"engines": { "engines": {
+28 -1
View File
@@ -1,6 +1,12 @@
import { ISettingsProvider } from '../core/ProviderContracts'; import { ISettingsProvider } from '../core/ProviderContracts';
export interface IMegaMenuSettings { export interface IMegaMenuSettings {
schemaVersion: number;
portalSettings: {
providerKey: string;
contractVersion: number;
minimumPortalSettingsVersion: string;
};
termSetId: string; termSetId: string;
termSetName: string; termSetName: string;
cacheMinutes: number; cacheMinutes: number;
@@ -21,7 +27,16 @@ export const MegaMenuSettingsProvider: ISettingsProvider<IMegaMenuSettings> = {
'c0abbffb-355d-4d4e-bd38-9e15fb811506', 'c0abbffb-355d-4d4e-bd38-9e15fb811506',
'abc3361f-bb2d-491f-aba3-cd51c19a299b' 'abc3361f-bb2d-491f-aba3-cd51c19a299b'
], ],
storage: { kind: 'siteUserCustomAction' }, storage: {
kind: 'siteUserCustomAction',
action: {
name: 'MegaMenu',
title: 'MegaMenu Application Customizer',
description: 'MSFT-Custom-Solution:MegaMenu',
location: 'ClientSideExtension.ApplicationCustomizer',
sequence: 100
}
},
sections: [ sections: [
{ {
key: 'navigation', key: 'navigation',
@@ -89,6 +104,12 @@ export const MegaMenuSettingsProvider: ISettingsProvider<IMegaMenuSettings> = {
} }
], ],
createDefault: (): IMegaMenuSettings => ({ createDefault: (): IMegaMenuSettings => ({
schemaVersion: 1,
portalSettings: {
providerKey: 'megamenu',
contractVersion: 1,
minimumPortalSettingsVersion: '3.0.0'
},
termSetId: '', termSetName: '', cacheMinutes: 15, cacheVersion: '1', menuMode: 'megaMenu', debug: false termSetId: '', termSetName: '', cacheMinutes: 15, cacheVersion: '1', menuMode: 'megaMenu', debug: false
}), }),
normalize: (value: any): IMegaMenuSettings => { // tslint:disable-line:no-any normalize: (value: any): IMegaMenuSettings => { // tslint:disable-line:no-any
@@ -96,6 +117,12 @@ export const MegaMenuSettingsProvider: ISettingsProvider<IMegaMenuSettings> = {
const minutes: number = Number(source.cacheMinutes); const minutes: number = Number(source.cacheMinutes);
const result: any = {}; // tslint:disable-line:no-any const result: any = {}; // tslint:disable-line:no-any
Object.keys(source).forEach((key: string): void => { result[key] = source[key]; }); Object.keys(source).forEach((key: string): void => { result[key] = source[key]; });
result.schemaVersion = 1;
result.portalSettings = {
providerKey: 'megamenu',
contractVersion: 1,
minimumPortalSettingsVersion: '3.0.0'
};
result.termSetId = typeof source.termSetId === 'string' ? source.termSetId.trim() : ''; result.termSetId = typeof source.termSetId === 'string' ? source.termSetId.trim() : '';
result.termSetName = typeof source.termSetName === 'string' ? source.termSetName.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.cacheMinutes = isFinite(minutes) ? Math.min(Math.max(Math.floor(minutes), 1), 1440) : 15;
+1 -1
View File
@@ -267,7 +267,7 @@ export class PortalSettingsDataService {
return this.context.spHttpClient.post(this.siteUrl + '/_api/site/UserCustomActions', SPHttpClient.configurations.v1, { return this.context.spHttpClient.post(this.siteUrl + '/_api/site/UserCustomActions', SPHttpClient.configurations.v1, {
headers: this.writeHeaders(), body: JSON.stringify(body) headers: this.writeHeaders(), body: JSON.stringify(body)
}).then((response: SPHttpClientResponse): Promise<any> => this.ensureJson(response, }).then((response: SPHttpClientResponse): Promise<any> => this.ensureJson(response,
'CustomBranding konnte nicht für die Site Collection registriert werden.')) provider.displayName + ' konnte nicht für die Site Collection registriert werden.'))
.then((payload: any): IUserCustomActionInfo => { .then((payload: any): IUserCustomActionInfo => {
const source: any = payload.d || payload; const source: any = payload.d || payload;
return { return {
+5 -5
View File
@@ -125,7 +125,7 @@ export class PortalSettingsApp {
if (!item.registered) { if (!item.registered) {
const registrationNotice: HTMLElement = createElement('div', 'ps3-status ps3-status-warning'); const registrationNotice: HTMLElement = createElement('div', 'ps3-status ps3-status-warning');
registrationNotice.setAttribute('role', 'status'); registrationNotice.setAttribute('role', 'status');
registrationNotice.textContent = 'CustomBranding ist noch nicht zentral registriert. Beim ersten Speichern legt PortalSettings die Site-Collection-Action an. Das CustomBranding-Paket muss zuvor im App Catalog bereitgestellt sein.'; registrationNotice.textContent = item.provider.displayName + ' ist noch nicht zentral registriert. Beim ersten Speichern legt PortalSettings die Site-Collection-Action an. Das zugehörige SPFx-Paket muss zuvor im App Catalog bereitgestellt sein.';
this.contentHost.appendChild(registrationNotice); this.contentHost.appendChild(registrationNotice);
} }
@@ -140,7 +140,7 @@ export class PortalSettingsApp {
const reset: HTMLButtonElement = createElement('button', 'ps3-button ps3-button-secondary', 'Änderungen verwerfen') as HTMLButtonElement; const reset: HTMLButtonElement = createElement('button', 'ps3-button ps3-button-secondary', 'Änderungen verwerfen') as HTMLButtonElement;
reset.type = 'button'; reset.addEventListener('click', (): void => this.resetCurrent()); reset.type = 'button'; reset.addEventListener('click', (): void => this.resetCurrent());
this.saveButton = createElement('button', 'ps3-button ps3-button-primary', this.saveButton = createElement('button', 'ps3-button ps3-button-primary',
item.registered ? 'Konfiguration speichern' : 'CustomBranding aktivieren') as HTMLButtonElement; item.registered ? 'Konfiguration speichern' : item.provider.displayName + ' aktivieren') as HTMLButtonElement;
this.saveButton.type = 'button'; this.saveButton.addEventListener('click', (): void => this.saveCurrent()); this.saveButton.type = 'button'; this.saveButton.addEventListener('click', (): void => this.saveCurrent());
actions.appendChild(reset); actions.appendChild(this.saveButton); this.contentHost.appendChild(actions); actions.appendChild(reset); actions.appendChild(this.saveButton); this.contentHost.appendChild(actions);
this.updateSaveState(); this.updateSaveState();
@@ -152,7 +152,7 @@ export class PortalSettingsApp {
empty.appendChild(createElement('h2', '', failed ? 'PortalSettings konnte nicht geladen werden' : 'Keine aktiven Provider gefunden')); empty.appendChild(createElement('h2', '', failed ? 'PortalSettings konnte nicht geladen werden' : 'Keine aktiven Provider gefunden'));
empty.appendChild(createElement('p', '', failed empty.appendChild(createElement('p', '', failed
? 'Prüfen Sie die Statusmeldung und die REST-Berechtigungen.' ? 'Prüfen Sie die Statusmeldung und die REST-Berechtigungen.'
: 'MegaMenu wird über seine zentrale Site-Collection-Action erkannt. CustomBranding kann direkt aktiviert werden; ExpiryIndicator wird über seinen Descriptor oder vorhandene Bindungen erkannt.')); : 'MegaMenu und CustomBranding können direkt für die Site Collection aktiviert werden; ExpiryIndicator wird über seinen Descriptor oder vorhandene Bindungen erkannt.'));
this.contentHost.appendChild(empty); this.contentHost.appendChild(empty);
} }
@@ -164,13 +164,13 @@ export class PortalSettingsApp {
if (errors.length) { this.showStatus(errors.join(' '), 'danger', true); return; } if (errors.length) { this.showStatus(errors.join(' '), 'danger', true); return; }
const question: string = wasRegistered const question: string = wasRegistered
? 'Konfiguration für „' + item.provider.displayName + '“ speichern?' ? 'Konfiguration für „' + item.provider.displayName + '“ speichern?'
: 'CustomBranding zentral für diese Site Collection aktivieren?'; : item.provider.displayName + ' zentral für diese Site Collection aktivieren?';
if (!window.confirm(question)) { return; } if (!window.confirm(question)) { return; }
this.saveButton.disabled = true; this.showStatus('Konfiguration wird gespeichert …', 'info', true); this.saveButton.disabled = true; this.showStatus('Konfiguration wird gespeichert …', 'info', true);
this.dataService.save(item).then((): void => { this.dataService.save(item).then((): void => {
this.showStatus(wasRegistered this.showStatus(wasRegistered
? 'Die Konfiguration wurde erfolgreich gespeichert.' ? 'Die Konfiguration wurde erfolgreich gespeichert.'
: 'CustomBranding wurde zentral aktiviert und gespeichert.', 'success'); : item.provider.displayName + ' wurde zentral aktiviert und gespeichert.', 'success');
this.renderTabs(); this.renderActive(); this.renderTabs(); this.renderActive();
}).catch((error: Error): void => { }).catch((error: Error): void => {
this.showStatus(error.message || String(error), 'danger', true); this.updateSaveState(); this.showStatus(error.message || String(error), 'danger', true); this.updateSaveState();
@@ -18,7 +18,6 @@ $green: "[theme: green, default: #107c10]";
.ps3-root { .ps3-root {
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
background: $neutral-lighter-alt;
color: $neutral-primary; color: $neutral-primary;
font-family: 'Segoe UI', 'Segoe UI Web (West European)', sans-serif; font-family: 'Segoe UI', 'Segoe UI Web (West European)', sans-serif;
} }
+4
View File
@@ -21,10 +21,14 @@ providers.forEach((provider: ISettingsProvider<any>): void => { // tslint:disabl
}); });
const mega: ISettingsProvider<any> = getSettingsProvider('megamenu'); // tslint:disable-line:no-any const mega: ISettingsProvider<any> = getSettingsProvider('megamenu'); // tslint:disable-line:no-any
assert(!!mega.storage.action, 'MegaMenu-Aktivierungsdefinition fehlt.');
assert(mega.storage.action.name === 'MegaMenu', 'MegaMenu Action-Name ist nicht stabil.');
assert(mega.storage.action.location === 'ClientSideExtension.ApplicationCustomizer', 'MegaMenu Action-Location ist ungültig.');
const normalizedMega: any = mega.normalize({ cacheMinutes: 5000, cacheVersion: '', unknownFuture: 'keep', menuMode: 'other' }); // tslint:disable-line:no-any const normalizedMega: any = mega.normalize({ cacheMinutes: 5000, cacheVersion: '', unknownFuture: 'keep', menuMode: 'other' }); // tslint:disable-line:no-any
assert(normalizedMega.cacheMinutes === 1440, 'MegaMenu Cache-Maximum greift nicht.'); assert(normalizedMega.cacheMinutes === 1440, 'MegaMenu Cache-Maximum greift nicht.');
assert(normalizedMega.cacheVersion === '1', 'MegaMenu Cache-Fallback fehlt.'); assert(normalizedMega.cacheVersion === '1', 'MegaMenu Cache-Fallback fehlt.');
assert(normalizedMega.menuMode === 'megaMenu', 'MegaMenu Modus-Fallback fehlt.'); assert(normalizedMega.menuMode === 'megaMenu', 'MegaMenu Modus-Fallback 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 branding: ISettingsProvider<any> = getSettingsProvider('custombranding'); // tslint:disable-line:no-any const branding: ISettingsProvider<any> = getSettingsProvider('custombranding'); // tslint:disable-line:no-any
+1
View File
@@ -15,6 +15,7 @@ if ($errors.Count -gt 0) {
$jsonFiles = @( $jsonFiles = @(
'package.json', 'package.json',
'config\config.json', 'config\config.json',
'config\copy-assets.json',
'config\package-solution.json', 'config\package-solution.json',
'config\serve.json', 'config\serve.json',
'src\webparts\portalSettings\PortalSettingsWebPart.manifest.json' 'src\webparts\portalSettings\PortalSettingsWebPart.manifest.json'
+4 -2
View File
@@ -12,6 +12,7 @@ function assert(condition, message) { if (!condition) { throw new Error(message)
var packageJson = json('package.json'); var packageJson = json('package.json');
var solution = json('config/package-solution.json').solution; var solution = json('config/package-solution.json').solution;
var config = json('config/config.json'); var config = json('config/config.json');
var copyAssets = json('config/copy-assets.json');
var manifest = json('src/webparts/portalSettings/PortalSettingsWebPart.manifest.json'); var manifest = json('src/webparts/portalSettings/PortalSettingsWebPart.manifest.json');
var styles = read('src/webparts/portalSettings/PortalSettings.scss'); var styles = read('src/webparts/portalSettings/PortalSettings.scss');
var sourceFiles = [ var sourceFiles = [
@@ -20,10 +21,11 @@ var sourceFiles = [
'src/ui/PortalSettingsApp.ts' 'src/ui/PortalSettingsApp.ts'
].map(read).join('\n'); ].map(read).join('\n');
assert(packageJson.version === '3.0.1', 'Package-Version ist nicht 3.0.1.'); assert(packageJson.version === '3.0.2', 'Package-Version ist nicht 3.0.2.');
assert(solution.version === '3.0.1.0', 'Solution-Version ist nicht 3.0.1.0.'); assert(solution.version === '3.0.2.0', 'Solution-Version ist nicht 3.0.2.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(manifest.componentType === 'WebPart', 'Komponente ist kein WebPart.'); assert(manifest.componentType === 'WebPart', 'Komponente ist kein WebPart.');
assert(!fs.existsSync(path.join(root, 'src/assets/PortalSettings.aspx')), 'Alte ASPX-Seite ist noch vorhanden.'); assert(!fs.existsSync(path.join(root, 'src/assets/PortalSettings.aspx')), 'Alte ASPX-Seite ist noch vorhanden.');
assert(!fs.existsSync(path.join(root, 'src/extensions/portalSettings/PortalSettingsApplicationCustomizer.ts')), 'Alter Application Customizer ist noch vorhanden.'); assert(!fs.existsSync(path.join(root, 'src/extensions/portalSettings/PortalSettingsApplicationCustomizer.ts')), 'Alter Application Customizer ist noch vorhanden.');