Aktualisiere Versionsnummer auf 3.0.1 in README.md, package.json, package-lock.json und package-solution.json. Füge Unterstützung für zentrale Aktivierung von CustomBranding hinzu und verbessere die Logik zur Handhabung von UserCustomActions. Aktualisiere Tests zur Validierung der neuen Struktur.
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.0 |
|
| Version | 3.0.1 |
|
||||||
| 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 |
|
||||||
@@ -29,6 +29,8 @@ Die frühere provisionierte `PortalSettings.aspx` und der leere Application Cust
|
|||||||
### CustomBranding
|
### CustomBranding
|
||||||
|
|
||||||
- zentrale `SPSite.UserCustomAction`
|
- zentrale `SPSite.UserCustomAction`
|
||||||
|
- Aktivierung der fehlenden Site-Collection-Action direkt aus PortalSettings
|
||||||
|
- versionierter `portalSettings`-Vertragsmarker mit Component-ID-Fallback
|
||||||
- Aktivierung und Debug-Modus
|
- Aktivierung und Debug-Modus
|
||||||
- CSS-Dateien inklusive `media`
|
- CSS-Dateien inklusive `media`
|
||||||
- Allowlist externer CSS-Hosts
|
- Allowlist externer CSS-Hosts
|
||||||
@@ -153,7 +155,7 @@ sharepoint/solution/portal-settings.sppkg
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. `portal-settings.sppkg` im App Catalog durch Version `3.0.0.0` ersetzen.
|
1. `portal-settings.sppkg` im App Catalog durch Version `3.0.1.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.0
|
Zielversion: 3.0.1
|
||||||
|
|
||||||
## Architektur
|
## Architektur
|
||||||
|
|
||||||
@@ -35,6 +35,8 @@ Zielversion: 3.0.0
|
|||||||
- [x] MegaMenu vollständig auf Provider-Schema umgestellt.
|
- [x] MegaMenu vollständig auf Provider-Schema umgestellt.
|
||||||
- [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] CustomBranding-Vertragsmarker und Component-ID-Fallback unterstützt.
|
||||||
- [x] Branding-Sicherheitsprüfung vor dem Speichern.
|
- [x] Branding-Sicherheitsprüfung vor dem Speichern.
|
||||||
- [x] ExpiryIndicator-Site-Collection-Standard abgebildet.
|
- [x] ExpiryIndicator-Site-Collection-Standard abgebildet.
|
||||||
- [x] Expiry-Bindungsinventar dargestellt.
|
- [x] Expiry-Bindungsinventar dargestellt.
|
||||||
|
|||||||
@@ -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.0.0",
|
"version": "3.0.1.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.0",
|
"version": "3.0.1",
|
||||||
"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.0",
|
"version": "3.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -59,6 +59,15 @@ export interface ISettingsSection {
|
|||||||
export interface IProviderStorageDefinition {
|
export interface IProviderStorageDefinition {
|
||||||
kind: SettingsStorageKind;
|
kind: SettingsStorageKind;
|
||||||
propertyKey?: string;
|
propertyKey?: string;
|
||||||
|
action?: IProviderActionDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProviderActionDefinition {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
location: 'ClientSideExtension.ApplicationCustomizer';
|
||||||
|
sequence: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISettingsProvider<TConfig> {
|
export interface ISettingsProvider<TConfig> {
|
||||||
@@ -97,6 +106,7 @@ export interface ILoadedSettingsProvider {
|
|||||||
config: any; // tslint:disable-line:no-any
|
config: any; // tslint:disable-line:no-any
|
||||||
originalConfig: any; // tslint:disable-line:no-any
|
originalConfig: any; // tslint:disable-line:no-any
|
||||||
context: IProviderLoadContext;
|
context: IProviderLoadContext;
|
||||||
|
registered: boolean;
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import { ISettingsProvider } from '../core/ProviderContracts';
|
|||||||
|
|
||||||
export interface ICustomBrandingSettings {
|
export interface ICustomBrandingSettings {
|
||||||
schemaVersion: number;
|
schemaVersion: number;
|
||||||
|
portalSettings: {
|
||||||
|
providerKey: string;
|
||||||
|
contractVersion: number;
|
||||||
|
minimumPortalSettingsVersion: string;
|
||||||
|
};
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
debug: boolean;
|
debug: boolean;
|
||||||
allowedCssHosts: string[];
|
allowedCssHosts: string[];
|
||||||
@@ -19,7 +24,16 @@ export const CustomBrandingSettingsProvider: ISettingsProvider<ICustomBrandingSe
|
|||||||
schemaVersion: 2,
|
schemaVersion: 2,
|
||||||
minimumPortalSettingsVersion: '3.0.0',
|
minimumPortalSettingsVersion: '3.0.0',
|
||||||
componentIds: ['035ba968-6488-4d42-86b3-0470ffcc95b9'],
|
componentIds: ['035ba968-6488-4d42-86b3-0470ffcc95b9'],
|
||||||
storage: { kind: 'siteUserCustomAction' },
|
storage: {
|
||||||
|
kind: 'siteUserCustomAction',
|
||||||
|
action: {
|
||||||
|
name: 'CustomBranding',
|
||||||
|
title: 'Custom Branding Application Customizer',
|
||||||
|
description: 'MSFT-Custom-Solution:CustomBranding',
|
||||||
|
location: 'ClientSideExtension.ApplicationCustomizer',
|
||||||
|
sequence: 100
|
||||||
|
}
|
||||||
|
},
|
||||||
sections: [
|
sections: [
|
||||||
{
|
{
|
||||||
key: 'general', title: 'Allgemein', layout: 'twoColumns', iconName: 'Settings',
|
key: 'general', title: 'Allgemein', layout: 'twoColumns', iconName: 'Settings',
|
||||||
@@ -70,6 +84,11 @@ export const CustomBrandingSettingsProvider: ISettingsProvider<ICustomBrandingSe
|
|||||||
],
|
],
|
||||||
createDefault: (): ICustomBrandingSettings => ({
|
createDefault: (): ICustomBrandingSettings => ({
|
||||||
schemaVersion: 2,
|
schemaVersion: 2,
|
||||||
|
portalSettings: {
|
||||||
|
providerKey: 'custombranding',
|
||||||
|
contractVersion: 1,
|
||||||
|
minimumPortalSettingsVersion: '3.0.0'
|
||||||
|
},
|
||||||
enabled: true,
|
enabled: true,
|
||||||
debug: false,
|
debug: false,
|
||||||
allowedCssHosts: [],
|
allowedCssHosts: [],
|
||||||
@@ -82,6 +101,11 @@ export const CustomBrandingSettingsProvider: ISettingsProvider<ICustomBrandingSe
|
|||||||
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 = 2;
|
result.schemaVersion = 2;
|
||||||
|
result.portalSettings = {
|
||||||
|
providerKey: 'custombranding',
|
||||||
|
contractVersion: 1,
|
||||||
|
minimumPortalSettingsVersion: '3.0.0'
|
||||||
|
};
|
||||||
result.enabled = source.enabled !== false;
|
result.enabled = source.enabled !== false;
|
||||||
result.debug = source.debug === true;
|
result.debug = source.debug === true;
|
||||||
result.allowedCssHosts = Array.isArray(source.allowedCssHosts) ? source.allowedCssHosts : [];
|
result.allowedCssHosts = Array.isArray(source.allowedCssHosts) ? source.allowedCssHosts : [];
|
||||||
|
|||||||
@@ -50,10 +50,14 @@ export class PortalSettingsDataService {
|
|||||||
if (errors.length > 0) { return Promise.reject(new Error(errors.join(' '))); }
|
if (errors.length > 0) { return Promise.reject(new Error(errors.join(' '))); }
|
||||||
const merged: any = mergePreservingUnknown(item.originalConfig, item.provider.normalize(item.config));
|
const merged: any = mergePreservingUnknown(item.originalConfig, item.provider.normalize(item.config));
|
||||||
if (item.provider.storage.kind === 'siteUserCustomAction') {
|
if (item.provider.storage.kind === 'siteUserCustomAction') {
|
||||||
if (!item.context.action) { return Promise.reject(new Error('Die zentrale UserCustomAction wurde nicht gefunden.')); }
|
const persist: Promise<IUserCustomActionInfo> = item.context.action
|
||||||
return this.saveUserCustomAction(item.context.action, merged).then((): void => {
|
? this.saveUserCustomAction(item.context.action, merged).then((): IUserCustomActionInfo => item.context.action)
|
||||||
|
: this.createUserCustomAction(item.provider, merged);
|
||||||
|
return persist.then((action: IUserCustomActionInfo): void => {
|
||||||
|
item.context.action = action;
|
||||||
item.config = item.provider.normalize(merged);
|
item.config = item.provider.normalize(merged);
|
||||||
item.originalConfig = cloneValue(merged);
|
item.originalConfig = cloneValue(merged);
|
||||||
|
item.registered = true;
|
||||||
item.dirty = false;
|
item.dirty = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -181,9 +185,13 @@ export class PortalSettingsDataService {
|
|||||||
const context: IProviderLoadContext = {};
|
const context: IProviderLoadContext = {};
|
||||||
if (provider.storage.kind === 'siteUserCustomAction') {
|
if (provider.storage.kind === 'siteUserCustomAction') {
|
||||||
const action: IUserCustomActionInfo = this.findAction(provider, actions);
|
const action: IUserCustomActionInfo = this.findAction(provider, actions);
|
||||||
if (!action) { return undefined; }
|
if (!action && !provider.storage.action) { return undefined; }
|
||||||
context.action = action;
|
if (action) {
|
||||||
raw = this.parseJson(action.properties, {});
|
context.action = action;
|
||||||
|
raw = this.parseJson(action.properties, {});
|
||||||
|
} else {
|
||||||
|
raw = provider.createDefault();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const propertyKey: string = provider.storage.propertyKey || '';
|
const propertyKey: string = provider.storage.propertyKey || '';
|
||||||
const stored: any = this.getProperty(properties, propertyKey);
|
const stored: any = this.getProperty(properties, propertyKey);
|
||||||
@@ -202,6 +210,7 @@ export class PortalSettingsDataService {
|
|||||||
config: cloneValue(normalized),
|
config: cloneValue(normalized),
|
||||||
originalConfig: cloneValue(raw || normalized),
|
originalConfig: cloneValue(raw || normalized),
|
||||||
context: context,
|
context: context,
|
||||||
|
registered: !!context.action || provider.storage.kind !== 'siteUserCustomAction',
|
||||||
dirty: false
|
dirty: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -240,6 +249,39 @@ export class PortalSettingsDataService {
|
|||||||
}).then((response: SPHttpClientResponse): Promise<void> => this.ensureOk(response, 'Konfiguration konnte nicht gespeichert werden.'));
|
}).then((response: SPHttpClientResponse): Promise<void> => this.ensureOk(response, 'Konfiguration konnte nicht gespeichert werden.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createUserCustomAction(provider: ISettingsProvider<any>, config: any): Promise<IUserCustomActionInfo> {
|
||||||
|
const definition: any = provider.storage.action;
|
||||||
|
if (!definition || !provider.componentIds.length) {
|
||||||
|
return Promise.reject(new Error('Für diesen Provider ist keine zentrale Registrierung definiert.'));
|
||||||
|
}
|
||||||
|
const body: any = {
|
||||||
|
'__metadata': { 'type': 'SP.UserCustomAction' },
|
||||||
|
'Name': definition.name,
|
||||||
|
'Title': definition.title,
|
||||||
|
'Description': definition.description,
|
||||||
|
'Location': definition.location,
|
||||||
|
'Sequence': definition.sequence,
|
||||||
|
'ClientSideComponentId': provider.componentIds[0],
|
||||||
|
'ClientSideComponentProperties': JSON.stringify(config)
|
||||||
|
};
|
||||||
|
return this.context.spHttpClient.post(this.siteUrl + '/_api/site/UserCustomActions', SPHttpClient.configurations.v1, {
|
||||||
|
headers: this.writeHeaders(), body: JSON.stringify(body)
|
||||||
|
}).then((response: SPHttpClientResponse): Promise<any> => this.ensureJson(response,
|
||||||
|
'CustomBranding konnte nicht für die Site Collection registriert werden.'))
|
||||||
|
.then((payload: any): IUserCustomActionInfo => {
|
||||||
|
const source: any = payload.d || payload;
|
||||||
|
return {
|
||||||
|
id: this.normalizeGuid(source.Id),
|
||||||
|
title: source.Title || definition.title,
|
||||||
|
name: source.Name || definition.name,
|
||||||
|
location: source.Location || definition.location,
|
||||||
|
componentId: this.normalizeGuid(source.ClientSideComponentId || provider.componentIds[0]),
|
||||||
|
properties: source.ClientSideComponentProperties || JSON.stringify(config),
|
||||||
|
entityType: source.__metadata && source.__metadata.type ? source.__metadata.type : 'SP.UserCustomAction'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private saveRootProperty(key: string, value: string): Promise<void> {
|
private saveRootProperty(key: string, value: string): Promise<void> {
|
||||||
if (!key) { return Promise.reject(new Error('Der Property-Bag-Schlüssel fehlt.')); }
|
if (!key) { return Promise.reject(new Error('Der Property-Bag-Schlüssel fehlt.')); }
|
||||||
const body: any = { '__metadata': { 'type': 'SP.PropertyValues' } };
|
const body: any = { '__metadata': { 'type': 'SP.PropertyValues' } };
|
||||||
@@ -251,7 +293,11 @@ export class PortalSettingsDataService {
|
|||||||
|
|
||||||
private findAction(provider: ISettingsProvider<any>, actions: IUserCustomActionInfo[]): IUserCustomActionInfo | undefined {
|
private findAction(provider: ISettingsProvider<any>, actions: IUserCustomActionInfo[]): IUserCustomActionInfo | undefined {
|
||||||
for (let i: number = 0; i < actions.length; i++) {
|
for (let i: number = 0; i < actions.length; i++) {
|
||||||
if (provider.componentIds.indexOf(actions[i].componentId) >= 0 &&
|
const properties: any = this.parseJson(actions[i].properties, {});
|
||||||
|
const providerKey: string = properties && properties.portalSettings
|
||||||
|
? String(properties.portalSettings.providerKey || '').toLowerCase()
|
||||||
|
: '';
|
||||||
|
if ((provider.componentIds.indexOf(actions[i].componentId) >= 0 || providerKey === provider.key.toLowerCase()) &&
|
||||||
actions[i].location.indexOf('ClientSideExtension.ApplicationCustomizer') >= 0) {
|
actions[i].location.indexOf('ClientSideExtension.ApplicationCustomizer') >= 0) {
|
||||||
return actions[i];
|
return actions[i];
|
||||||
}
|
}
|
||||||
@@ -321,12 +367,14 @@ export class PortalSettingsDataService {
|
|||||||
private encodePropertyName(value: string): string { return value.replace(/\./g, '_x002e_'); }
|
private encodePropertyName(value: string): string { return value.replace(/\./g, '_x002e_'); }
|
||||||
|
|
||||||
private readHeaders(): any { return { 'Accept': 'application/json;odata=verbose', 'OData-Version': '3.0' }; }
|
private readHeaders(): any { return { 'Accept': 'application/json;odata=verbose', 'OData-Version': '3.0' }; }
|
||||||
private writeHeaders(method: string): any {
|
private writeHeaders(method?: string): any {
|
||||||
return {
|
const headers: any = {
|
||||||
'Accept': 'application/json;odata=verbose',
|
'Accept': 'application/json;odata=verbose',
|
||||||
'Content-Type': 'application/json;odata=verbose',
|
'Content-Type': 'application/json;odata=verbose',
|
||||||
'OData-Version': '3.0', 'IF-MATCH': '*', 'X-HTTP-Method': method
|
'OData-Version': '3.0'
|
||||||
};
|
};
|
||||||
|
if (method) { headers['IF-MATCH'] = '*'; headers['X-HTTP-Method'] = method; }
|
||||||
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureJson(response: SPHttpClientResponse, message: string): Promise<any> {
|
private ensureJson(response: SPHttpClientResponse, message: string): Promise<any> {
|
||||||
|
|||||||
+31
-10
@@ -58,7 +58,7 @@ export class PortalSettingsApp {
|
|||||||
const hero: HTMLElement = createElement('header', 'ps3-hero');
|
const hero: HTMLElement = createElement('header', 'ps3-hero');
|
||||||
hero.appendChild(createElement('span', 'ps3-eyebrow', 'PORTAL SETTINGS V3'));
|
hero.appendChild(createElement('span', 'ps3-eyebrow', 'PORTAL SETTINGS V3'));
|
||||||
hero.appendChild(createElement('h1', 'ps3-title', this.configuredTitle || 'Zentrale Verwaltung der Portal-Erweiterungen'));
|
hero.appendChild(createElement('h1', 'ps3-title', this.configuredTitle || 'Zentrale Verwaltung der Portal-Erweiterungen'));
|
||||||
hero.appendChild(createElement('p', 'ps3-lead', 'Installierte Solutions werden automatisch erkannt. Ihre versionierten Provider erzeugen strukturierte und validierte Einstellungsseiten.'));
|
hero.appendChild(createElement('p', 'ps3-lead', 'Registrierte Solutions werden automatisch erkannt. Unterstützte Provider können zentral aktiviert und über strukturierte, validierte Einstellungsseiten verwaltet werden.'));
|
||||||
const meta: HTMLElement = createElement('div', 'ps3-heroMeta');
|
const meta: HTMLElement = createElement('div', 'ps3-heroMeta');
|
||||||
meta.appendChild(this.metric('Site Collection', this.dataService.getSiteUrl()));
|
meta.appendChild(this.metric('Site Collection', this.dataService.getSiteUrl()));
|
||||||
meta.appendChild(this.metric('Konfigurationsmodell', 'Providerbasiert'));
|
meta.appendChild(this.metric('Konfigurationsmodell', 'Providerbasiert'));
|
||||||
@@ -78,7 +78,11 @@ export class PortalSettingsApp {
|
|||||||
this.items = items;
|
this.items = items;
|
||||||
this.activeKey = preferredKey && this.findItem(preferredKey) ? preferredKey : (items.length ? items[0].provider.key : '');
|
this.activeKey = preferredKey && this.findItem(preferredKey) ? preferredKey : (items.length ? items[0].provider.key : '');
|
||||||
this.renderTabs(); this.renderActive();
|
this.renderTabs(); this.renderActive();
|
||||||
this.showStatus(items.length ? items.length + ' Erweiterung(en) wurden erkannt.' : 'Keine unterstützte Erweiterung wurde aktiv registriert.', items.length ? 'success' : 'warning');
|
let registeredCount: number = 0;
|
||||||
|
for (let i: number = 0; i < items.length; i++) { if (items[i].registered) { registeredCount++; } }
|
||||||
|
this.showStatus(items.length
|
||||||
|
? registeredCount + ' Erweiterung(en) registriert, ' + (items.length - registeredCount) + ' zur Aktivierung verfügbar.'
|
||||||
|
: 'Keine unterstützte Erweiterung wurde aktiv registriert.', registeredCount ? 'success' : 'warning');
|
||||||
}).catch((error: Error): void => {
|
}).catch((error: Error): void => {
|
||||||
if (!this.disposed) { this.showStatus(error.message || String(error), 'danger', true); this.renderEmpty(true); }
|
if (!this.disposed) { this.showStatus(error.message || String(error), 'danger', true); this.renderEmpty(true); }
|
||||||
});
|
});
|
||||||
@@ -113,9 +117,18 @@ export class PortalSettingsApp {
|
|||||||
heading.appendChild(createElement('p', 'ps3-providerDescription', item.provider.description));
|
heading.appendChild(createElement('p', 'ps3-providerDescription', item.provider.description));
|
||||||
header.appendChild(heading);
|
header.appendChild(heading);
|
||||||
const storage: HTMLElement = createElement('div', 'ps3-storageBadge',
|
const storage: HTMLElement = createElement('div', 'ps3-storageBadge',
|
||||||
item.provider.storage.kind === 'siteUserCustomAction' ? 'Site-Collection-Action' : 'Root-Web-Standard');
|
item.provider.storage.kind === 'siteUserCustomAction'
|
||||||
|
? (item.registered ? 'Site-Collection-Action' : 'Bereit zur Aktivierung')
|
||||||
|
: 'Root-Web-Standard');
|
||||||
header.appendChild(storage); this.contentHost.appendChild(header);
|
header.appendChild(storage); this.contentHost.appendChild(header);
|
||||||
|
|
||||||
|
if (!item.registered) {
|
||||||
|
const registrationNotice: HTMLElement = createElement('div', 'ps3-status ps3-status-warning');
|
||||||
|
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.';
|
||||||
|
this.contentHost.appendChild(registrationNotice);
|
||||||
|
}
|
||||||
|
|
||||||
const form: HTMLElement = createElement('div', 'ps3-form'); this.contentHost.appendChild(form);
|
const form: HTMLElement = createElement('div', 'ps3-form'); this.contentHost.appendChild(form);
|
||||||
this.formRenderer.render(form, item, {
|
this.formRenderer.render(form, item, {
|
||||||
changed: (): void => { this.updateSaveState(); this.renderTabs(); },
|
changed: (): void => { this.updateSaveState(); this.renderTabs(); },
|
||||||
@@ -126,7 +139,8 @@ export class PortalSettingsApp {
|
|||||||
const actions: HTMLElement = createElement('div', 'ps3-actionBar');
|
const actions: HTMLElement = createElement('div', 'ps3-actionBar');
|
||||||
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', 'Konfiguration speichern') as HTMLButtonElement;
|
this.saveButton = createElement('button', 'ps3-button ps3-button-primary',
|
||||||
|
item.registered ? 'Konfiguration speichern' : 'CustomBranding 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();
|
||||||
@@ -138,19 +152,26 @@ 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 und CustomBranding benötigen eine zentrale Site-Collection-Action. ExpiryIndicator wird über seinen Descriptor oder vorhandene Bindungen erkannt.'));
|
: 'MegaMenu wird über seine zentrale Site-Collection-Action erkannt. CustomBranding kann direkt aktiviert werden; ExpiryIndicator wird über seinen Descriptor oder vorhandene Bindungen erkannt.'));
|
||||||
this.contentHost.appendChild(empty);
|
this.contentHost.appendChild(empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveCurrent(): void {
|
private saveCurrent(): void {
|
||||||
const item: ILoadedSettingsProvider = this.currentItem();
|
const item: ILoadedSettingsProvider = this.currentItem();
|
||||||
if (!item || !item.dirty) { return; }
|
if (!item || (item.registered && !item.dirty)) { return; }
|
||||||
|
const wasRegistered: boolean = item.registered;
|
||||||
const errors: string[] = item.provider.validate(item.config);
|
const errors: string[] = item.provider.validate(item.config);
|
||||||
if (errors.length) { this.showStatus(errors.join(' '), 'danger', true); return; }
|
if (errors.length) { this.showStatus(errors.join(' '), 'danger', true); return; }
|
||||||
if (!window.confirm('Konfiguration für „' + item.provider.displayName + '“ speichern?')) { return; }
|
const question: string = wasRegistered
|
||||||
|
? 'Konfiguration für „' + item.provider.displayName + '“ speichern?'
|
||||||
|
: 'CustomBranding zentral für diese Site Collection aktivieren?';
|
||||||
|
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('Die Konfiguration wurde erfolgreich gespeichert.', 'success'); this.renderTabs(); this.renderActive();
|
this.showStatus(wasRegistered
|
||||||
|
? 'Die Konfiguration wurde erfolgreich gespeichert.'
|
||||||
|
: 'CustomBranding wurde zentral aktiviert und gespeichert.', 'success');
|
||||||
|
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();
|
||||||
});
|
});
|
||||||
@@ -200,7 +221,7 @@ export class PortalSettingsApp {
|
|||||||
|
|
||||||
private updateSaveState(): void {
|
private updateSaveState(): void {
|
||||||
const item: ILoadedSettingsProvider = this.currentItem();
|
const item: ILoadedSettingsProvider = this.currentItem();
|
||||||
if (this.saveButton) { this.saveButton.disabled = !item || !item.dirty; }
|
if (this.saveButton) { this.saveButton.disabled = !item || (item.registered && !item.dirty); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private openExpiryBindingDialog(item: ILoadedSettingsProvider): void {
|
private openExpiryBindingDialog(item: ILoadedSettingsProvider): void {
|
||||||
@@ -284,7 +305,7 @@ export class PortalSettingsApp {
|
|||||||
provider.sections = siteItem.provider.sections.filter(section => section.key === 'dates' || section.key === 'rules' || section.key === 'preview');
|
provider.sections = siteItem.provider.sections.filter(section => section.key === 'dates' || section.key === 'rules' || section.key === 'preview');
|
||||||
const editItem: ILoadedSettingsProvider = {
|
const editItem: ILoadedSettingsProvider = {
|
||||||
provider: provider, config: provider.normalize(loaded.config), originalConfig: loaded.config,
|
provider: provider, config: provider.normalize(loaded.config), originalConfig: loaded.config,
|
||||||
context: {}, dirty: false
|
context: {}, registered: true, dirty: false
|
||||||
};
|
};
|
||||||
const editor: DynamicFormRenderer = new DynamicFormRenderer();
|
const editor: DynamicFormRenderer = new DynamicFormRenderer();
|
||||||
editor.render(form, editItem, {
|
editor.render(form, editItem, {
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ assert(normalizedMega.menuMode === 'megaMenu', 'MegaMenu Modus-Fallback 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
|
||||||
|
assert(!!branding.storage.action, 'CustomBranding-Aktivierungsdefinition fehlt.');
|
||||||
|
assert(branding.storage.action.name === 'CustomBranding', 'CustomBranding Action-Name ist nicht stabil.');
|
||||||
|
assert(branding.storage.action.location === 'ClientSideExtension.ApplicationCustomizer', 'CustomBranding Action-Location ist ungültig.');
|
||||||
const normalizedBranding: any = branding.normalize({ elements: [{ type: 'span' }], future: 7 }); // tslint:disable-line:no-any
|
const normalizedBranding: any = branding.normalize({ elements: [{ type: 'span' }], future: 7 }); // tslint:disable-line:no-any
|
||||||
assert(normalizedBranding.placeholdertop.elements.length === 1, 'Legacy-Branding-Elemente werden nicht übernommen.');
|
assert(normalizedBranding.placeholdertop.elements.length === 1, 'Legacy-Branding-Elemente werden nicht übernommen.');
|
||||||
assert(normalizedBranding.future === 7, 'Unbekannte Branding-Properties gehen verloren.');
|
assert(normalizedBranding.future === 7, 'Unbekannte Branding-Properties gehen verloren.');
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ var sourceFiles = [
|
|||||||
'src/ui/PortalSettingsApp.ts'
|
'src/ui/PortalSettingsApp.ts'
|
||||||
].map(read).join('\n');
|
].map(read).join('\n');
|
||||||
|
|
||||||
assert(packageJson.version === '3.0.0', 'Package-Version ist nicht 3.0.0.');
|
assert(packageJson.version === '3.0.1', 'Package-Version ist nicht 3.0.1.');
|
||||||
assert(solution.version === '3.0.0.0', 'Solution-Version ist nicht 3.0.0.0.');
|
assert(solution.version === '3.0.1.0', 'Solution-Version ist nicht 3.0.1.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(manifest.componentType === 'WebPart', 'Komponente ist kein WebPart.');
|
assert(manifest.componentType === 'WebPart', 'Komponente ist kein WebPart.');
|
||||||
|
|||||||
Reference in New Issue
Block a user