feat: Update version to 3.0.2 and add default footer elements for portal settings
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# CustomBranding
|
# CustomBranding
|
||||||
|
|
||||||
CustomBranding 3.0.1 ist ein zentraler SPFx-1.4.1-Application-Customizer für SharePoint Server Subscription Edition. Die Solution lädt freigegebene Stylesheets und rendert eine kontrollierte Komponentenstruktur im oberen oder unteren SharePoint-Placeholder. Moderne und klassische Seiten verwenden dieselben `ClientSideComponentProperties`.
|
CustomBranding 3.0.2 ist ein zentraler SPFx-1.4.1-Application-Customizer für SharePoint Server Subscription Edition. Die Solution lädt freigegebene Stylesheets und rendert eine kontrollierte Komponentenstruktur im oberen oder unteren SharePoint-Placeholder. Moderne und klassische Seiten verwenden dieselben `ClientSideComponentProperties`.
|
||||||
|
|
||||||
Die Konfiguration liegt in genau einer `SPSite.UserCustomAction` pro Site Collection. Es werden weder eine versteckte Liste noch ein Property Bag benötigt. Dadurch gilt das Branding automatisch für das Root Web, vorhandene Subwebs und später angelegte Subwebs.
|
Die Konfiguration liegt in genau einer `SPSite.UserCustomAction` pro Site Collection. Es werden weder eine versteckte Liste noch ein Property Bag benötigt. Dadurch gilt das Branding automatisch für das Root Web, vorhandene Subwebs und später angelegte Subwebs.
|
||||||
|
|
||||||
@@ -119,6 +119,10 @@ Das aktuelle Konfigurationsschema hat die Version 2:
|
|||||||
|
|
||||||
Bestehende 1.x-Konfigurationen mit einem Root-Array `elements` werden weiterhin als Top-Inhalt gelesen. Unbekannte Properties ignoriert die Runtime. Das vollständige Beispiel liegt unter `examples/custom-branding.example.json`.
|
Bestehende 1.x-Konfigurationen mit einem Root-Array `elements` werden weiterhin als Top-Inhalt gelesen. Unbekannte Properties ignoriert die Runtime. Das vollständige Beispiel liegt unter `examples/custom-branding.example.json`.
|
||||||
|
|
||||||
|
Ist `placeholderbottom.elements` leer oder fehlt die Eigenschaft, rendert CustomBranding automatisch den bisherigen
|
||||||
|
**Portal Settings**-Link auf `~sitecollection/SitePages/PortalSettings.aspx`. Sobald eigene Footer-Elemente konfiguriert
|
||||||
|
sind, ersetzen sie diesen Standard.
|
||||||
|
|
||||||
### Sicherheitsgrenzen
|
### Sicherheitsgrenzen
|
||||||
|
|
||||||
Erlaubte Elemente:
|
Erlaubte Elemente:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Analysestand: 20.07.2026
|
Analysestand: 20.07.2026
|
||||||
Umgesetzt: 20.07.2026
|
Umgesetzt: 20.07.2026
|
||||||
Ausgangsversion: 1.0.4
|
Ausgangsversion: 1.0.4
|
||||||
Zielversion: 3.0.1
|
Zielversion: 3.0.2
|
||||||
Status: Implementierung und lokale Qualitätssicherung abgeschlossen
|
Status: Implementierung und lokale Qualitätssicherung abgeschlossen
|
||||||
|
|
||||||
## Zielbild
|
## Zielbild
|
||||||
@@ -25,7 +25,7 @@ CustomBranding wird pro Site Collection genau einmal über eine `SPSite.UserCust
|
|||||||
- [x] Bestehende 1.x-Konfigurationen mit Root-Property `elements` weiter unterstützt.
|
- [x] Bestehende 1.x-Konfigurationen mit Root-Property `elements` weiter unterstützt.
|
||||||
- [x] `enabled`, `debug`, `allowedCssHosts`, `cssfiles`, Top und Bottom konsistent typisiert.
|
- [x] `enabled`, `debug`, `allowedCssHosts`, `cssfiles`, Top und Bottom konsistent typisiert.
|
||||||
- [x] Unbekannte Properties werden ignoriert; Warnungen erscheinen nur im Debug-Modus.
|
- [x] Unbekannte Properties werden ignoriert; Warnungen erscheinen nur im Debug-Modus.
|
||||||
- [x] Keine feste Abhängigkeit oder Verlinkung zu PortalSettings mehr vorhanden.
|
- [x] Den bisherigen Portal-Settings-Link als sicheren Standard-Footer in der Runtime verankert.
|
||||||
- [x] Versionierten, rein deklarativen PortalSettings-Vertragsmarker ergänzt.
|
- [x] Versionierten, rein deklarativen PortalSettings-Vertragsmarker ergänzt.
|
||||||
- [x] Runtime, Standardkonfiguration und Deployment-Skript auf denselben Providervertrag gebracht.
|
- [x] Runtime, Standardkonfiguration und Deployment-Skript auf denselben Providervertrag gebracht.
|
||||||
- [x] Direkte Aktivierung aus PortalSettings 3.0.1 dokumentiert.
|
- [x] Direkte Aktivierung aus PortalSettings 3.0.1 dokumentiert.
|
||||||
|
|||||||
@@ -184,16 +184,35 @@
|
|||||||
|
|
||||||
function normalize(raw) {
|
function normalize(raw) {
|
||||||
raw = raw && typeof raw === 'object' ? raw : {};
|
raw = raw && typeof raw === 'object' ? raw : {};
|
||||||
|
var configuredBottom = raw.placeholderbottom && Array.isArray(raw.placeholderbottom.elements)
|
||||||
|
? raw.placeholderbottom.elements
|
||||||
|
: [];
|
||||||
return {
|
return {
|
||||||
enabled: raw.enabled !== false,
|
enabled: raw.enabled !== false,
|
||||||
debug: raw.debug === true,
|
debug: raw.debug === true,
|
||||||
allowedCssHosts: normalizeHosts(raw.allowedCssHosts),
|
allowedCssHosts: normalizeHosts(raw.allowedCssHosts),
|
||||||
cssfiles: Array.isArray(raw.cssfiles) ? raw.cssfiles : [],
|
cssfiles: Array.isArray(raw.cssfiles) ? raw.cssfiles : [],
|
||||||
top: raw.placeholdertop && Array.isArray(raw.placeholdertop.elements) ? raw.placeholdertop.elements : (Array.isArray(raw.elements) ? raw.elements : []),
|
top: raw.placeholdertop && Array.isArray(raw.placeholdertop.elements) ? raw.placeholdertop.elements : (Array.isArray(raw.elements) ? raw.elements : []),
|
||||||
bottom: raw.placeholderbottom && Array.isArray(raw.placeholderbottom.elements) ? raw.placeholderbottom.elements : []
|
bottom: configuredBottom.length > 0 ? configuredBottom : defaultFooterElements()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function defaultFooterElements() {
|
||||||
|
return [{
|
||||||
|
type: 'div',
|
||||||
|
attributes: { role: 'contentinfo', 'aria-label': 'Portalverwaltung' },
|
||||||
|
styles: {
|
||||||
|
'background-color': '#323130', color: '#ffffff', padding: '12px 16px',
|
||||||
|
'text-align': 'center', width: '100%', 'box-sizing': 'border-box'
|
||||||
|
},
|
||||||
|
children: [{
|
||||||
|
type: 'a', content: 'Portal Settings',
|
||||||
|
attributes: { href: '~sitecollection/SitePages/PortalSettings.aspx' },
|
||||||
|
styles: { color: '#ffffff', 'text-decoration': 'underline' }
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
for (var i = 0; i < state.hosts.length; i++) {
|
for (var i = 0; i < state.hosts.length; i++) {
|
||||||
if (state.hosts[i].parentNode) { state.hosts[i].parentNode.removeChild(state.hosts[i]); }
|
if (state.hosts[i].parentNode) { state.hosts[i].parentNode.removeChild(state.hosts[i]); }
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"solution": {
|
"solution": {
|
||||||
"name": "custom-branding-client-side-solution",
|
"name": "custom-branding-client-side-solution",
|
||||||
"id": "03a7c4de-e031-4b83-a683-5ca5c364166e",
|
"id": "03a7c4de-e031-4b83-a683-5ca5c364166e",
|
||||||
"version": "3.0.1.0",
|
"version": "3.0.2.0",
|
||||||
"includeClientSideAssets": true,
|
"includeClientSideAssets": true,
|
||||||
"skipFeatureDeployment": true
|
"skipFeatureDeployment": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,6 +39,22 @@ $componentName = 'CustomBranding'
|
|||||||
$componentTitle = 'Custom Branding Application Customizer'
|
$componentTitle = 'Custom Branding Application Customizer'
|
||||||
$classicActionName = 'CustomBranding.Classic.ScriptLink'
|
$classicActionName = 'CustomBranding.Classic.ScriptLink'
|
||||||
|
|
||||||
|
function New-DefaultFooterElements {
|
||||||
|
return @(@{
|
||||||
|
type = 'div'
|
||||||
|
attributes = @{ role = 'contentinfo'; 'aria-label' = 'Portalverwaltung' }
|
||||||
|
styles = @{
|
||||||
|
'background-color' = '#323130'; color = '#ffffff'; padding = '12px 16px'
|
||||||
|
'text-align' = 'center'; width = '100%'; 'box-sizing' = 'border-box'
|
||||||
|
}
|
||||||
|
children = @(@{
|
||||||
|
type = 'a'; content = 'Portal Settings'
|
||||||
|
attributes = @{ href = '~sitecollection/SitePages/PortalSettings.aspx' }
|
||||||
|
styles = @{ color = '#ffffff'; 'text-decoration' = 'underline' }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function New-DefaultCustomBrandingConfiguration {
|
function New-DefaultCustomBrandingConfiguration {
|
||||||
return @{
|
return @{
|
||||||
schemaVersion = 2
|
schemaVersion = 2
|
||||||
@@ -52,7 +68,7 @@ function New-DefaultCustomBrandingConfiguration {
|
|||||||
allowedCssHosts = @()
|
allowedCssHosts = @()
|
||||||
cssfiles = @()
|
cssfiles = @()
|
||||||
placeholdertop = @{ elements = @() }
|
placeholdertop = @{ elements = @() }
|
||||||
placeholderbottom = @{ elements = @() }
|
placeholderbottom = @{ elements = @(New-DefaultFooterElements) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +149,9 @@ try {
|
|||||||
minimumPortalSettingsVersion = '3.0.0'
|
minimumPortalSettingsVersion = '3.0.0'
|
||||||
}
|
}
|
||||||
if (-not $configuration.ContainsKey('placeholdertop')) { $configuration.placeholdertop = @{ elements = @() } }
|
if (-not $configuration.ContainsKey('placeholdertop')) { $configuration.placeholdertop = @{ elements = @() } }
|
||||||
if (-not $configuration.ContainsKey('placeholderbottom')) { $configuration.placeholderbottom = @{ elements = @() } }
|
if (-not $configuration.ContainsKey('placeholderbottom')) {
|
||||||
|
$configuration.placeholderbottom = @{ elements = @(New-DefaultFooterElements) }
|
||||||
|
}
|
||||||
if (-not $configuration.ContainsKey('cssfiles')) { $configuration.cssfiles = @() }
|
if (-not $configuration.ContainsKey('cssfiles')) { $configuration.cssfiles = @() }
|
||||||
if (-not $configuration.ContainsKey('allowedCssHosts')) { $configuration.allowedCssHosts = @() }
|
if (-not $configuration.ContainsKey('allowedCssHosts')) { $configuration.allowedCssHosts = @() }
|
||||||
if ($PSBoundParameters.ContainsKey('EnableDebug')) { $configuration.debug = [bool]$EnableDebug.IsPresent }
|
if ($PSBoundParameters.ContainsKey('EnableDebug')) { $configuration.debug = [bool]$EnableDebug.IsPresent }
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "custom-branding",
|
"name": "custom-branding",
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "custom-branding",
|
"name": "custom-branding",
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function normalizeBrandingConfig(
|
|||||||
}
|
}
|
||||||
if (serializedLength > MaxBrandingConfigurationLength) {
|
if (serializedLength > MaxBrandingConfigurationLength) {
|
||||||
warnings.push('Configuration exceeds the maximum size and was ignored.');
|
warnings.push('Configuration exceeds the maximum size and was ignored.');
|
||||||
return { config: createEmptyConfig(), warnings: warnings };
|
return { config: createEmptyConfig(siteCollectionUrl), warnings: warnings };
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowedCssHosts: string[] = normalizeHosts(raw.allowedCssHosts);
|
const allowedCssHosts: string[] = normalizeHosts(raw.allowedCssHosts);
|
||||||
@@ -54,9 +54,12 @@ export function normalizeBrandingConfig(
|
|||||||
const topSource: any[] = raw.placeholdertop && Array.isArray(raw.placeholdertop.elements)
|
const topSource: any[] = raw.placeholdertop && Array.isArray(raw.placeholdertop.elements)
|
||||||
? raw.placeholdertop.elements
|
? raw.placeholdertop.elements
|
||||||
: (Array.isArray(raw.elements) ? raw.elements : []);
|
: (Array.isArray(raw.elements) ? raw.elements : []);
|
||||||
const bottomSource: any[] = raw.placeholderbottom && Array.isArray(raw.placeholderbottom.elements)
|
const configuredBottomSource: any[] = raw.placeholderbottom && Array.isArray(raw.placeholderbottom.elements)
|
||||||
? raw.placeholderbottom.elements
|
? raw.placeholderbottom.elements
|
||||||
: [];
|
: [];
|
||||||
|
const bottomSource: any[] = configuredBottomSource.length > 0
|
||||||
|
? configuredBottomSource
|
||||||
|
: createDefaultFooterElements();
|
||||||
|
|
||||||
const config: ICustomBrandingConfig = {
|
const config: ICustomBrandingConfig = {
|
||||||
schemaVersion: BrandingSchemaVersion,
|
schemaVersion: BrandingSchemaVersion,
|
||||||
@@ -127,7 +130,8 @@ export function sanitizeStyle(propertyName: string, value: string): string | und
|
|||||||
return styleValue;
|
return styleValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEmptyConfig(): ICustomBrandingConfig {
|
function createEmptyConfig(siteCollectionUrl: string): ICustomBrandingConfig {
|
||||||
|
const state: INormalizationState = { count: 0, warnings: [], siteCollectionUrl: siteCollectionUrl || '' };
|
||||||
return {
|
return {
|
||||||
schemaVersion: BrandingSchemaVersion,
|
schemaVersion: BrandingSchemaVersion,
|
||||||
portalSettings: createPortalSettingsDescriptor(),
|
portalSettings: createPortalSettingsDescriptor(),
|
||||||
@@ -136,10 +140,26 @@ function createEmptyConfig(): ICustomBrandingConfig {
|
|||||||
allowedCssHosts: [],
|
allowedCssHosts: [],
|
||||||
cssfiles: [],
|
cssfiles: [],
|
||||||
placeholdertop: { elements: [] },
|
placeholdertop: { elements: [] },
|
||||||
placeholderbottom: { elements: [] }
|
placeholderbottom: { elements: normalizeElements(createDefaultFooterElements(), 1, state) }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createDefaultFooterElements(): any[] {
|
||||||
|
return [{
|
||||||
|
type: 'div',
|
||||||
|
attributes: { role: 'contentinfo', 'aria-label': 'Portalverwaltung' },
|
||||||
|
styles: {
|
||||||
|
'background-color': '#323130', color: '#ffffff', padding: '12px 16px',
|
||||||
|
'text-align': 'center', width: '100%', 'box-sizing': 'border-box'
|
||||||
|
},
|
||||||
|
children: [{
|
||||||
|
type: 'a', content: 'Portal Settings',
|
||||||
|
attributes: { href: '~sitecollection/SitePages/PortalSettings.aspx' },
|
||||||
|
styles: { color: '#ffffff', 'text-decoration': 'underline' }
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
function createPortalSettingsDescriptor(): IPortalSettingsDescriptor {
|
function createPortalSettingsDescriptor(): IPortalSettingsDescriptor {
|
||||||
return {
|
return {
|
||||||
providerKey: 'custombranding',
|
providerKey: 'custombranding',
|
||||||
|
|||||||
@@ -21,6 +21,22 @@ assert(empty.config.schemaVersion === 2, 'Das aktuelle Schema muss Version 2 ver
|
|||||||
assert(empty.config.portalSettings.providerKey === 'custombranding', 'PortalSettings Provider-Key fehlt.');
|
assert(empty.config.portalSettings.providerKey === 'custombranding', 'PortalSettings Provider-Key fehlt.');
|
||||||
assert(empty.config.portalSettings.contractVersion === 1, 'PortalSettings Contract-Version fehlt.');
|
assert(empty.config.portalSettings.contractVersion === 1, 'PortalSettings Contract-Version fehlt.');
|
||||||
assert(empty.config.enabled === true, 'Branding muss standardmaessig aktiviert sein.');
|
assert(empty.config.enabled === true, 'Branding muss standardmaessig aktiviert sein.');
|
||||||
|
assert(empty.config.placeholderbottom.elements.length === 1, 'Der Standard-Footer fehlt.');
|
||||||
|
assert(empty.config.placeholderbottom.elements[0].children[0].content === 'Portal Settings',
|
||||||
|
'Der Standard-Footer enthaelt nicht den Portal-Settings-Link.');
|
||||||
|
assert(empty.config.placeholderbottom.elements[0].children[0].attributes.href ===
|
||||||
|
siteUrl + '/SitePages/PortalSettings.aspx', 'Der Standard-Footer verweist nicht auf PortalSettings.');
|
||||||
|
|
||||||
|
var emptyBottom = normalize({ placeholderbottom: { elements: [] } }, siteUrl);
|
||||||
|
assert(emptyBottom.config.placeholderbottom.elements.length === 1,
|
||||||
|
'Ein geleertes Footer-Array muss auf den Standard-Footer zurueckfallen.');
|
||||||
|
|
||||||
|
var customBottom = normalize({
|
||||||
|
placeholderbottom: { elements: [{ type: 'span', content: 'Eigener Footer' }] }
|
||||||
|
}, siteUrl);
|
||||||
|
assert(customBottom.config.placeholderbottom.elements.length === 1 &&
|
||||||
|
customBottom.config.placeholderbottom.elements[0].content === 'Eigener Footer',
|
||||||
|
'Ein eigener Footer muss den Standard-Footer ersetzen.');
|
||||||
|
|
||||||
var legacy = normalize({ elements: [{ type: 'span', content: 'Alt' }] }, siteUrl);
|
var legacy = normalize({ elements: [{ type: 'span', content: 'Alt' }] }, siteUrl);
|
||||||
assert(legacy.config.placeholdertop.elements.length === 1, 'Das Legacy-Root-Array muss weiter funktionieren.');
|
assert(legacy.config.placeholdertop.elements.length === 1, 'Das Legacy-Root-Array muss weiter funktionieren.');
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ var appSource = read('src/extensions/customBranding/CustomBrandingApplicationCus
|
|||||||
var rendererSource = read('src/extensions/customBranding/BrandingDomRenderer.ts');
|
var rendererSource = read('src/extensions/customBranding/BrandingDomRenderer.ts');
|
||||||
var classicSource = read('classic/custom-branding-classic.js');
|
var classicSource = read('classic/custom-branding-classic.js');
|
||||||
|
|
||||||
assert(packageJson.version === '3.0.1', 'package.json hat nicht Version 3.0.1.');
|
assert(packageJson.version === '3.0.2', 'package.json hat nicht Version 3.0.2.');
|
||||||
assert(solution.version === '3.0.1.0', 'Solution-Version ist inkonsistent.');
|
assert(solution.version === '3.0.2.0', 'Solution-Version ist inkonsistent.');
|
||||||
assert(solution.skipFeatureDeployment === true, 'Tenantweite Bereitstellung ist nicht aktiviert.');
|
assert(solution.skipFeatureDeployment === true, 'Tenantweite Bereitstellung ist nicht aktiviert.');
|
||||||
assert(!solution.features, 'Die alte web-scoped Feature-Registrierung ist noch vorhanden.');
|
assert(!solution.features, 'Die alte web-scoped Feature-Registrierung ist noch vorhanden.');
|
||||||
assert(!fs.existsSync(path.join(root, 'sharepoint/assets/elements.xml')), 'elements.xml muss entfernt sein.');
|
assert(!fs.existsSync(path.join(root, 'sharepoint/assets/elements.xml')), 'elements.xml muss entfernt sein.');
|
||||||
@@ -33,6 +33,8 @@ assert(serveText.indexOf('onclick') < 0, 'serve.json enthaelt ein Event-Attribut
|
|||||||
assert(serveText.indexOf('placeholdertop') >= 0, 'serve.json verwendet nicht das echte Schema.');
|
assert(serveText.indexOf('placeholdertop') >= 0, 'serve.json verwendet nicht das echte Schema.');
|
||||||
assert(rendererSource.indexOf('innerHTML') < 0 && appSource.indexOf('innerHTML') < 0, 'Der moderne Renderer darf innerHTML nicht verwenden.');
|
assert(rendererSource.indexOf('innerHTML') < 0 && appSource.indexOf('innerHTML') < 0, 'Der moderne Renderer darf innerHTML nicht verwenden.');
|
||||||
assert(classicSource.indexOf('innerHTML') < 0, 'Die Classic-Runtime darf innerHTML nicht verwenden.');
|
assert(classicSource.indexOf('innerHTML') < 0, 'Die Classic-Runtime darf innerHTML nicht verwenden.');
|
||||||
|
assert(classicSource.indexOf('~sitecollection/SitePages/PortalSettings.aspx') >= 0,
|
||||||
|
'Der Standard-Footer fehlt in der Classic-Runtime.');
|
||||||
assert(appSource.indexOf('changedEvent.remove') >= 0, 'Lifecycle-Cleanup fuer changedEvent fehlt.');
|
assert(appSource.indexOf('changedEvent.remove') >= 0, 'Lifecycle-Cleanup fuer changedEvent fehlt.');
|
||||||
assert(appSource.indexOf('_onTopPlaceholderDisposed') >= 0 && appSource.indexOf('_onBottomPlaceholderDisposed') >= 0,
|
assert(appSource.indexOf('_onTopPlaceholderDisposed') >= 0 && appSource.indexOf('_onBottomPlaceholderDisposed') >= 0,
|
||||||
'Top- und Bottom-Placeholder brauchen unabhaengige Dispose-Handler.');
|
'Top- und Bottom-Placeholder brauchen unabhaengige Dispose-Handler.');
|
||||||
|
|||||||
Reference in New Issue
Block a user