diff --git a/README.md b/README.md index ae94563..e55a0de 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ PortalSettings V3 ist ein providerbasiertes SPFx-WebPart für SharePoint Server | Eigenschaft | Wert | |---|---| -| Version | 3.2.0 | +| Version | 3.2.1 | | SharePoint Framework | 1.4.1 | | Build-Node | 8.17.0 | | npm | 6.x | @@ -48,6 +48,7 @@ Die Oberfläche wird vollständig vom WebPart erzeugt und verwendet kein `innerH ### ExpiryIndicator +- Erkennung direkt nach der App-Installation über den Application Customizer im Root Web - Site-Collection-Standard aus dem Root-Web-Property-Bag - Übersicht registrierter Listen- und Bibliotheksbindungen - Web-, Listen- und Datumsfeldauswahl ohne Eingabe technischer IDs @@ -164,7 +165,7 @@ sharepoint/solution/portal-settings.sppkg ## Installation -1. `portal-settings.sppkg` im App Catalog durch Version `3.2.0.0` ersetzen. +1. `portal-settings.sppkg` im App Catalog durch Version `3.2.1.0` ersetzen. 2. Die App im Root Web der gewünschten Site Collection installieren oder aktualisieren. 3. Die App provisioniert automatisch die veröffentlichte Seite `SitePages/PortalSettings.aspx` mit dem WebPart. 4. Eine bereits vorhandene `PortalSettings.aspx` wird nicht überschrieben. diff --git a/ToDo.md b/ToDo.md index f38e462..9d3dee6 100644 --- a/ToDo.md +++ b/ToDo.md @@ -2,7 +2,7 @@ Stand: 21.07.2026 Branch: `dev-3.0` -Zielversion: 3.2.0 +Zielversion: 3.2.1 ## Architektur @@ -40,6 +40,7 @@ Zielversion: 3.2.0 - [x] CustomBranding-Vertragsmarker und Component-ID-Fallback unterstützt. - [x] Branding-Sicherheitsprüfung vor dem Speichern. - [x] ExpiryIndicator-Site-Collection-Standard abgebildet. +- [x] ExpiryIndicator direkt über seine Root-Web-Application-Customizer-Action erkennen. - [x] Expiry-Bindungsinventar dargestellt. - [x] Web-, Listen- und Datumsfeld-Picker umgesetzt. - [x] Neue Field-Customizer-Bindung aus dem WebPart möglich. diff --git a/config/package-solution.json b/config/package-solution.json index 167fe7d..01c895d 100644 --- a/config/package-solution.json +++ b/config/package-solution.json @@ -3,7 +3,7 @@ "solution": { "name": "portal-settings-client-side-solution", "id": "1c98e32d-bb7b-43b7-9625-074d6e4ea286", - "version": "3.2.0.0", + "version": "3.2.1.0", "includeClientSideAssets": true, "skipFeatureDeployment": false, "features": [ diff --git a/package-lock.json b/package-lock.json index 28827f9..63f46bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "portal-settings", - "version": "3.2.0", + "version": "3.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 92ad000..5c98f66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "portal-settings", - "version": "3.2.0", + "version": "3.2.1", "private": true, "main": "lib/index.js", "engines": { diff --git a/src/services/PortalSettingsDataService.ts b/src/services/PortalSettingsDataService.ts index eb22779..e839b23 100644 --- a/src/services/PortalSettingsDataService.ts +++ b/src/services/PortalSettingsDataService.ts @@ -32,13 +32,16 @@ export class PortalSettingsDataService { } public load(providers: Array>): Promise { - return Promise.all([this.getSiteActions(), this.getRootProperties()]) + return Promise.all([this.getSiteActions(), this.getRootWebActions(), this.getRootProperties()]) .then((values: any[]): ILoadedSettingsProvider[] => { - const actions: IUserCustomActionInfo[] = values[0]; - const properties: any = values[1]; + const siteActions: IUserCustomActionInfo[] = values[0]; + const registrationActions: IUserCustomActionInfo[] = siteActions.concat(values[1]); + const properties: any = values[2]; const loaded: ILoadedSettingsProvider[] = []; for (let i: number = 0; i < providers.length; i++) { - const result: ILoadedSettingsProvider = this.loadProvider(providers[i], actions, properties); + const result: ILoadedSettingsProvider = this.loadProvider( + providers[i], siteActions, registrationActions, properties + ); if (result) { loaded.push(result); } } return loaded; @@ -178,13 +181,14 @@ export class PortalSettingsDataService { private loadProvider( provider: ISettingsProvider, - actions: IUserCustomActionInfo[], + siteActions: IUserCustomActionInfo[], + registrationActions: IUserCustomActionInfo[], properties: any ): ILoadedSettingsProvider | undefined { let raw: any; const context: IProviderLoadContext = {}; if (provider.storage.kind === 'siteUserCustomAction') { - const action: IUserCustomActionInfo = this.findAction(provider, actions); + const action: IUserCustomActionInfo = this.findAction(provider, siteActions); if (!action && !provider.storage.action) { return undefined; } if (action) { context.action = action; @@ -197,7 +201,7 @@ export class PortalSettingsDataService { const stored: any = this.getProperty(properties, propertyKey); const descriptor: any = this.getProperty(properties, DescriptorProperty); const bindingsValue: any = this.getProperty(properties, BindingsProperty); - const registration: IUserCustomActionInfo = this.findAnyAction(provider, actions); + const registration: IUserCustomActionInfo = this.findAnyAction(provider, registrationActions); if (!stored && !descriptor && !bindingsValue && !registration) { return undefined; } if (registration) { context.action = registration; } raw = this.parseJson(stored, provider.createDefault()); @@ -218,8 +222,18 @@ export class PortalSettingsDataService { private getSiteActions(): Promise { const url: string = this.siteUrl + '/_api/site/UserCustomActions' + '?$select=Id,Title,Name,Location,ClientSideComponentId,ClientSideComponentProperties'; + return this.getActions(url, 'Site-Collection-UserCustomActions konnten nicht gelesen werden.'); + } + + private getRootWebActions(): Promise { + const url: string = this.siteUrl + '/_api/site/rootweb/UserCustomActions' + + '?$select=Id,Title,Name,Location,ClientSideComponentId,ClientSideComponentProperties'; + return this.getActions(url, 'Root-Web-UserCustomActions konnten nicht gelesen werden.'); + } + + private getActions(url: string, errorMessage: string): Promise { return this.context.spHttpClient.get(url, SPHttpClient.configurations.v1, { headers: this.readHeaders() }) - .then((response: SPHttpClientResponse): Promise => this.ensureJson(response, 'UserCustomActions konnten nicht gelesen werden.')) + .then((response: SPHttpClientResponse): Promise => this.ensureJson(response, errorMessage)) .then((payload: any): IUserCustomActionInfo[] => this.results(payload).map((source: any): IUserCustomActionInfo => ({ id: this.normalizeGuid(source.Id), title: source.Title || '', diff --git a/tests/validate-static-assets.js b/tests/validate-static-assets.js index 611dbec..6fc5901 100644 --- a/tests/validate-static-assets.js +++ b/tests/validate-static-assets.js @@ -23,9 +23,10 @@ var sourceFiles = [ 'src/ui/DynamicFormRenderer.ts', 'src/ui/PortalSettingsApp.ts' ].map(read).join('\n'); +var dataService = read('src/services/PortalSettingsDataService.ts'); -assert(packageJson.version === '3.2.0', 'Package-Version ist nicht 3.2.0.'); -assert(solution.version === '3.2.0.0', 'Solution-Version ist nicht 3.2.0.0.'); +assert(packageJson.version === '3.2.1', 'Package-Version ist nicht 3.2.1.'); +assert(solution.version === '3.2.1.0', 'Solution-Version ist nicht 3.2.1.0.'); assert(solution.features && solution.features.length === 1, 'Das Seiten-Provisioning-Feature fehlt.'); assert(solution.features[0].assets.elementManifests.indexOf('elements.xml') >= 0 && solution.features[0].assets.elementFiles.indexOf('PortalSettings.aspx') >= 0 && @@ -47,6 +48,9 @@ assert(upgradeActions.indexOf('71841e33-8bbd-4007-83a6-d950453226a2\\elements.xm 'Die Upgrade-Provisionierung fehlt.'); assert(!fs.existsSync(path.join(root, 'src/extensions/portalSettings/PortalSettingsApplicationCustomizer.ts')), 'Alter Application Customizer ist noch vorhanden.'); assert(sourceFiles.indexOf('innerHTML') < 0, 'UI darf innerHTML nicht verwenden.'); +assert(dataService.indexOf('/_api/site/rootweb/UserCustomActions') >= 0 && + dataService.indexOf('siteActions.concat(values[1])') >= 0, + 'Root-Web-Actions werden nicht für die Provider-Erkennung berücksichtigt.'); ['themePrimary', 'neutralPrimary', 'neutralLight', 'white'].forEach(function (slot) { assert(styles.indexOf('[theme: ' + slot + ', default:') >= 0, 'Themeslot fehlt: ' + slot); });