Initial commit
This commit is contained in:
25
.editorconfig
Normal file
25
.editorconfig
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# EditorConfig helps developers define and maintain consistent
|
||||||
|
# coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
|
||||||
|
[*]
|
||||||
|
|
||||||
|
# change these settings to your own preference
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# we recommend you to keep these unchanged
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[{package,bower}.json]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Build generated files
|
||||||
|
dist
|
||||||
|
lib
|
||||||
|
solution
|
||||||
|
temp
|
||||||
|
*.sppkg
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# OSX
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Visual Studio files
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
.vs
|
||||||
|
bin
|
||||||
|
obj
|
||||||
|
|
||||||
|
# Resx Generated Code
|
||||||
|
*.resx.ts
|
||||||
|
|
||||||
|
# Styles Generated Code
|
||||||
|
*.scss.ts
|
||||||
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"msjsdiag.debugger-for-chrome"
|
||||||
|
]
|
||||||
|
}
|
||||||
43
.vscode/launch.json
vendored
Normal file
43
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
/**
|
||||||
|
* Install Chrome Debugger Extension for Visual Studio Code to debug your components with the
|
||||||
|
* Chrome browser: https://aka.ms/spfx-debugger-extensions
|
||||||
|
*/
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [{
|
||||||
|
"name": "Local workbench",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "https://localhost:4321/temp/workbench.html",
|
||||||
|
"webRoot": "${workspaceRoot}",
|
||||||
|
"sourceMaps": true,
|
||||||
|
"sourceMapPathOverrides": {
|
||||||
|
"webpack:///.././src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||||
|
},
|
||||||
|
"runtimeArgs": [
|
||||||
|
"--remote-debugging-port=9222"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hosted workbench",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "https://enter-your-SharePoint-site/_layouts/workbench.aspx",
|
||||||
|
"webRoot": "${workspaceRoot}",
|
||||||
|
"sourceMaps": true,
|
||||||
|
"sourceMapPathOverrides": {
|
||||||
|
"webpack:///.././src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||||
|
},
|
||||||
|
"runtimeArgs": [
|
||||||
|
"--remote-debugging-port=9222",
|
||||||
|
"-incognito"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
.vscode/settings.json
vendored
Normal file
13
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Place your settings in this file to overwrite default and user settings.
|
||||||
|
{
|
||||||
|
// Configure glob patterns for excluding files and folders in the file explorer.
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.DS_Store": true,
|
||||||
|
"**/bower_components": true,
|
||||||
|
"**/coverage": true,
|
||||||
|
"**/lib-amd": true,
|
||||||
|
"src/**/*.scss.ts": true
|
||||||
|
},
|
||||||
|
"typescript.tsdk": ".\\node_modules\\typescript\\lib"
|
||||||
|
}
|
||||||
12
.yo-rc.json
Normal file
12
.yo-rc.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"isCreatingSolution": true,
|
||||||
|
"environment": "onprem19",
|
||||||
|
"version": "1.10.0",
|
||||||
|
"libraryName": "custom-branding",
|
||||||
|
"libraryId": "03a7c4de-e031-4b83-a683-5ca5c364166e",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"componentType": "extension",
|
||||||
|
"extensionType": "ApplicationCustomizer"
|
||||||
|
}
|
||||||
|
}
|
||||||
26
README.md
Normal file
26
README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
## custom-branding
|
||||||
|
|
||||||
|
This is where you include your WebPart documentation.
|
||||||
|
|
||||||
|
### Building the code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone the repo
|
||||||
|
npm i
|
||||||
|
npm i -g gulp
|
||||||
|
gulp
|
||||||
|
```
|
||||||
|
|
||||||
|
This package produces the following:
|
||||||
|
|
||||||
|
* lib/* - intermediate-stage commonjs build artifacts
|
||||||
|
* dist/* - the bundled script, along with other resources
|
||||||
|
* deploy/* - all resources which should be uploaded to a CDN.
|
||||||
|
|
||||||
|
### Build options
|
||||||
|
|
||||||
|
gulp clean - TODO
|
||||||
|
gulp test - TODO
|
||||||
|
gulp serve - TODO
|
||||||
|
gulp bundle - TODO
|
||||||
|
gulp package-solution - TODO
|
||||||
18
config/config.json
Normal file
18
config/config.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"custom-branding-application-customizer": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/extensions/customBranding/CustomBrandingApplicationCustomizer.js",
|
||||||
|
"manifest": "./src/extensions/customBranding/CustomBrandingApplicationCustomizer.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"CustomBrandingApplicationCustomizerStrings": "lib/extensions/customBranding/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
config/copy-assets.json
Normal file
4
config/copy-assets.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
||||||
7
config/deploy-azure-storage.json
Normal file
7
config/deploy-azure-storage.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||||
|
"workingDir": "./temp/deploy/",
|
||||||
|
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||||
|
"container": "custom-branding",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
||||||
13
config/package-solution.json
Normal file
13
config/package-solution.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "custom-branding-client-side-solution",
|
||||||
|
"id": "03a7c4de-e031-4b83-a683-5ca5c364166e",
|
||||||
|
"version": "1.0.3.0",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"skipFeatureDeployment": true
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/custom-branding.sppkg"
|
||||||
|
}
|
||||||
|
}
|
||||||
169
config/serve.json
Normal file
169
config/serve.json
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||||
|
"port": 4321,
|
||||||
|
"https": true,
|
||||||
|
"serveConfigurations": {
|
||||||
|
"default": {
|
||||||
|
"pageUrl": "http://clshp001/",
|
||||||
|
"customActions": {
|
||||||
|
"035ba968-6488-4d42-86b3-0470ffcc95b9": {
|
||||||
|
"location": "ClientSideExtension.ApplicationCustomizer",
|
||||||
|
"properties": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "div",
|
||||||
|
"styles": {
|
||||||
|
"background-color": "#0078d4",
|
||||||
|
"color": "white",
|
||||||
|
"padding": "15px 20px",
|
||||||
|
"text-align": "center",
|
||||||
|
"font-family": "Segoe UI, sans-serif",
|
||||||
|
"font-size": "14px"
|
||||||
|
},
|
||||||
|
"content": "Willkommen auf unserem SharePoint Portal!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warning": {
|
||||||
|
"pageUrl": "http://clshp001/",
|
||||||
|
"customActions": {
|
||||||
|
"035ba968-6488-4d42-86b3-0470ffcc95b9": {
|
||||||
|
"location": "ClientSideExtension.ApplicationCustomizer",
|
||||||
|
"properties": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "div",
|
||||||
|
"styles": {
|
||||||
|
"background-color": "#d83b01",
|
||||||
|
"color": "white",
|
||||||
|
"padding": "15px",
|
||||||
|
"text-align": "center",
|
||||||
|
"font-weight": "bold"
|
||||||
|
},
|
||||||
|
"content": "⚠️ Achtung: Wartungsarbeiten am Wochenende"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"withLink": {
|
||||||
|
"pageUrl": "http://clshp001/",
|
||||||
|
"customActions": {
|
||||||
|
"035ba968-6488-4d42-86b3-0470ffcc95b9": {
|
||||||
|
"location": "ClientSideExtension.ApplicationCustomizer",
|
||||||
|
"properties": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "div",
|
||||||
|
"styles": {
|
||||||
|
"background-color": "#0078d4",
|
||||||
|
"color": "white",
|
||||||
|
"padding": "12px 20px",
|
||||||
|
"text-align": "center"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "span",
|
||||||
|
"content": "Wichtige Mitteilung: Systemwartung geplant. ",
|
||||||
|
"styles": {
|
||||||
|
"font-weight": "bold"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "a",
|
||||||
|
"content": "Mehr Informationen",
|
||||||
|
"attributes": {
|
||||||
|
"href": "/sites/it/SitePages/Wartung.aspx",
|
||||||
|
"target": "_blank"
|
||||||
|
},
|
||||||
|
"styles": {
|
||||||
|
"color": "white",
|
||||||
|
"text-decoration": "underline",
|
||||||
|
"margin-left": "5px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gradient": {
|
||||||
|
"pageUrl": "http://clshp001/",
|
||||||
|
"customActions": {
|
||||||
|
"035ba968-6488-4d42-86b3-0470ffcc95b9": {
|
||||||
|
"location": "ClientSideExtension.ApplicationCustomizer",
|
||||||
|
"properties": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "div",
|
||||||
|
"styles": {
|
||||||
|
"background": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||||||
|
"color": "white",
|
||||||
|
"padding": "20px"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "div",
|
||||||
|
"styles": {
|
||||||
|
"display": "flex",
|
||||||
|
"justify-content": "space-between",
|
||||||
|
"align-items": "center",
|
||||||
|
"max-width": "1200px",
|
||||||
|
"margin": "0 auto"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "div",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "p",
|
||||||
|
"styles": {
|
||||||
|
"margin": "0",
|
||||||
|
"font-size": "18px",
|
||||||
|
"font-weight": "bold"
|
||||||
|
},
|
||||||
|
"content": "Neue Funktionen verfügbar!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "p",
|
||||||
|
"styles": {
|
||||||
|
"margin": "5px 0 0 0",
|
||||||
|
"font-size": "14px"
|
||||||
|
},
|
||||||
|
"content": "Entdecken Sie die neuesten Updates"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "button",
|
||||||
|
"content": "Mehr erfahren",
|
||||||
|
"attributes": {
|
||||||
|
"onclick": "window.location.href='/sites/news/SitePages/Updates.aspx'"
|
||||||
|
},
|
||||||
|
"styles": {
|
||||||
|
"background-color": "white",
|
||||||
|
"color": "#667eea",
|
||||||
|
"border": "none",
|
||||||
|
"padding": "10px 20px",
|
||||||
|
"border-radius": "5px",
|
||||||
|
"cursor": "pointer",
|
||||||
|
"font-weight": "bold"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
config/write-manifests.json
Normal file
4
config/write-manifests.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||||
|
}
|
||||||
104
deployment/add-custombranding.ps1
Normal file
104
deployment/add-custombranding.ps1
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[Alias('WebUrl')]
|
||||||
|
[string]$SiteUrl,
|
||||||
|
|
||||||
|
[string]$CssPath = '',
|
||||||
|
|
||||||
|
[string]$Description = 'MSFT-Custom-Solution:CustomBranding'
|
||||||
|
)
|
||||||
|
|
||||||
|
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
|
||||||
|
$componentId = '035ba968-6488-4d42-86b3-0470ffcc95b9'
|
||||||
|
$location = 'ClientSideExtension.ApplicationCustomizer'
|
||||||
|
$name = 'CustomBranding'
|
||||||
|
$title = 'Custom Branding'
|
||||||
|
|
||||||
|
function Get-CustomBrandingPropertiesJson {
|
||||||
|
param(
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
$config = @{
|
||||||
|
cssfiles = @()
|
||||||
|
placeholdertop = @{ elements = @() }
|
||||||
|
placeholderbottom = @{ elements = @() }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Path -and $Path.Trim().Length -gt 0) {
|
||||||
|
$config.cssfiles += @{ path = $Path.Trim() }
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($config | ConvertTo-Json -Depth 10 -Compress)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-WebScopedComponentActions {
|
||||||
|
param(
|
||||||
|
[Microsoft.SharePoint.SPSite]$CurrentSite,
|
||||||
|
[string]$CurrentComponentId,
|
||||||
|
[string]$CurrentLocation
|
||||||
|
)
|
||||||
|
|
||||||
|
$removedCount = 0
|
||||||
|
|
||||||
|
foreach ($web in $CurrentSite.AllWebs) {
|
||||||
|
try {
|
||||||
|
$webActions = @($web.UserCustomActions | Where-Object {
|
||||||
|
$_.Location -eq $CurrentLocation -and
|
||||||
|
$_.ClientSideComponentId -and
|
||||||
|
$_.ClientSideComponentId.ToString().ToLower() -eq $CurrentComponentId
|
||||||
|
})
|
||||||
|
|
||||||
|
foreach ($webAction in $webActions) {
|
||||||
|
$web.UserCustomActions.Delete($webAction.Id)
|
||||||
|
$removedCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($webActions.Count -gt 0) {
|
||||||
|
$web.Update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$web.Dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $removedCount
|
||||||
|
}
|
||||||
|
|
||||||
|
$site = Get-SPSite -Identity $SiteUrl
|
||||||
|
try {
|
||||||
|
$removedWebScopedActions = Remove-WebScopedComponentActions -CurrentSite $site -CurrentComponentId $componentId -CurrentLocation $location
|
||||||
|
|
||||||
|
$existingAction = $site.UserCustomActions | Where-Object {
|
||||||
|
$_.Location -eq $location -and $_.ClientSideComponentId -and $_.ClientSideComponentId.ToString().ToLower() -eq $componentId
|
||||||
|
} | Select-Object -First 1
|
||||||
|
|
||||||
|
if ($existingAction) {
|
||||||
|
$action = $existingAction
|
||||||
|
Write-Host 'Aktualisiere vorhandene site-scoped Custom Branding UserCustomAction...' -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$action = $site.UserCustomActions.Add()
|
||||||
|
Write-Host 'Erstelle neue site-scoped Custom Branding UserCustomAction...' -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
$action.Name = $name
|
||||||
|
$action.Title = $title
|
||||||
|
$action.Description = $Description
|
||||||
|
$action.Location = $location
|
||||||
|
$action.ClientSideComponentId = [Guid]$componentId
|
||||||
|
$action.ClientSideComponentProperties = Get-CustomBrandingPropertiesJson -Path $CssPath
|
||||||
|
$action.Update()
|
||||||
|
|
||||||
|
Write-Host 'Custom Branding wurde site-scoped registriert.' -ForegroundColor Green
|
||||||
|
Write-Host ('Entfernte web-scoped Eintraege: ' + $removedWebScopedActions)
|
||||||
|
Write-Host ('Beschreibung: ' + $Description)
|
||||||
|
Write-Host ('Properties: ' + $action.ClientSideComponentProperties)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if ($site) {
|
||||||
|
$site.Dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
7
gulpfile.js
Normal file
7
gulpfile.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const build = require('@microsoft/sp-build-web');
|
||||||
|
|
||||||
|
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||||
|
|
||||||
|
build.initialize(require('gulp'));
|
||||||
17617
package-lock.json
generated
Normal file
17617
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
package.json
Normal file
31
package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "custom-branding",
|
||||||
|
"version": "0.0.2",
|
||||||
|
"private": true,
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp bundle",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"test": "gulp test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/sp-core-library": "~1.4.0",
|
||||||
|
"@microsoft/decorators": "~1.4.0",
|
||||||
|
"@types/webpack-env": "1.13.1",
|
||||||
|
"@types/es6-promise": "0.0.33",
|
||||||
|
"@microsoft/sp-dialog": "~1.4.0",
|
||||||
|
"@microsoft/sp-application-base": "~1.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/sp-build-web": "~1.4.1",
|
||||||
|
"@microsoft/sp-module-interfaces": "~1.4.1",
|
||||||
|
"@microsoft/sp-webpart-workbench": "~1.4.1",
|
||||||
|
"gulp": "~3.9.1",
|
||||||
|
"@types/chai": "3.4.34",
|
||||||
|
"@types/mocha": "2.2.38",
|
||||||
|
"ajv": "~5.2.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx/component-manifest.schema.json",
|
||||||
|
"id": "035ba968-6488-4d42-86b3-0470ffcc95b9",
|
||||||
|
"alias": "CustomBrandingApplicationCustomizer",
|
||||||
|
"componentType": "Extension",
|
||||||
|
"extensionType": "ApplicationCustomizer",
|
||||||
|
|
||||||
|
"version": "*",
|
||||||
|
"manifestVersion": 2,
|
||||||
|
|
||||||
|
"requiresCustomScript": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
import { override } from '@microsoft/decorators';
|
||||||
|
import { Log } from '@microsoft/sp-core-library';
|
||||||
|
import {
|
||||||
|
BaseApplicationCustomizer,
|
||||||
|
PlaceholderContent,
|
||||||
|
PlaceholderName
|
||||||
|
} from '@microsoft/sp-application-base';
|
||||||
|
|
||||||
|
import * as strings from 'CustomBrandingApplicationCustomizerStrings';
|
||||||
|
|
||||||
|
const LOG_SOURCE: string = 'CustomBrandingApplicationCustomizer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS-Datei Definition
|
||||||
|
*/
|
||||||
|
export interface ICssFile {
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definition eines HTML-Elements
|
||||||
|
*/
|
||||||
|
export interface IBrandingElement {
|
||||||
|
type: 'div' | 'span' | 'p' | 'a' | 'button' | 'img' | 'h1' | 'h2' | 'h3' | 'strong' | 'em';
|
||||||
|
content?: string;
|
||||||
|
attributes?: { [key: string]: string };
|
||||||
|
styles?: { [key: string]: string };
|
||||||
|
children?: IBrandingElement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPlaceholderConfig {
|
||||||
|
elements?: IBrandingElement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hauptkonfiguration für das Branding
|
||||||
|
*/
|
||||||
|
export interface IBrandingConfig {
|
||||||
|
cssfiles?: ICssFile[];
|
||||||
|
placeholdertop?: IBrandingElement[];
|
||||||
|
placeholderbottom?: IBrandingElement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties für den CustomBranding Application Customizer
|
||||||
|
*/
|
||||||
|
export interface ICustomBrandingApplicationCustomizerProperties {
|
||||||
|
/**
|
||||||
|
* Array von CSS-Dateien die geladen werden sollen
|
||||||
|
*/
|
||||||
|
cssfiles?: ICssFile[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array von HTML-Elementen für den Placeholder Top
|
||||||
|
*/
|
||||||
|
placeholdertop?: IPlaceholderConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array von HTML-Elementen für den Placeholder Bottom
|
||||||
|
*/
|
||||||
|
placeholderbottom?: IPlaceholderConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CustomBranding Application Customizer
|
||||||
|
* Kompiliert JSON-Konfiguration zu HTML und fügt es in den Top Placeholder ein
|
||||||
|
* Lädt optional CSS-Dateien
|
||||||
|
*/
|
||||||
|
export default class CustomBrandingApplicationCustomizer
|
||||||
|
extends BaseApplicationCustomizer<ICustomBrandingApplicationCustomizerProperties> {
|
||||||
|
|
||||||
|
private _topPlaceholder: PlaceholderContent | undefined;
|
||||||
|
private _bottomPlaceholder: PlaceholderContent | undefined;
|
||||||
|
private _loadedCssFiles: string[] = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
public onInit(): Promise<void> {
|
||||||
|
Log.info(LOG_SOURCE, 'Initialized CustomBrandingApplicationCustomizer');
|
||||||
|
|
||||||
|
// CSS-Dateien laden
|
||||||
|
this._loadCssFiles();
|
||||||
|
|
||||||
|
// Auf Placeholder-Änderungen reagieren
|
||||||
|
this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
|
||||||
|
|
||||||
|
// Initial rendern
|
||||||
|
this._renderPlaceHolders();
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt CSS-Dateien aus der Konfiguration
|
||||||
|
*/
|
||||||
|
private _loadCssFiles(): void {
|
||||||
|
if (this.properties && this.properties.cssfiles && Array.isArray(this.properties.cssfiles)) {
|
||||||
|
console.log('CustomBranding: Loading CSS files...');
|
||||||
|
|
||||||
|
for (let i = 0; i < this.properties.cssfiles.length; i++) {
|
||||||
|
const cssFile = this.properties.cssfiles[i];
|
||||||
|
|
||||||
|
if (cssFile && cssFile.path) {
|
||||||
|
this._injectCssFile(cssFile.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt eine CSS-Datei in den Head ein
|
||||||
|
*/
|
||||||
|
private _injectCssFile(cssPath: string): void {
|
||||||
|
// Prüfen ob die Datei bereits geladen wurde
|
||||||
|
for (let i = 0; i < this._loadedCssFiles.length; i++) {
|
||||||
|
if (this._loadedCssFiles[i] === cssPath) {
|
||||||
|
console.log('CustomBranding: CSS file already loaded: ' + cssPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Link-Element erstellen
|
||||||
|
const linkElement: HTMLLinkElement = document.createElement('link');
|
||||||
|
linkElement.rel = 'stylesheet';
|
||||||
|
linkElement.type = 'text/css';
|
||||||
|
linkElement.href = cssPath;
|
||||||
|
linkElement.setAttribute('data-custom-branding', 'true');
|
||||||
|
|
||||||
|
// Event-Handler für erfolgreiches Laden
|
||||||
|
linkElement.onload = function () {
|
||||||
|
console.log('CustomBranding: CSS loaded successfully: ' + cssPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Event-Handler für Fehler
|
||||||
|
linkElement.onerror = function () {
|
||||||
|
console.error('CustomBranding: Failed to load CSS: ' + cssPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
// In Head einfügen
|
||||||
|
document.head.appendChild(linkElement);
|
||||||
|
|
||||||
|
// Zur Liste hinzufügen
|
||||||
|
this._loadedCssFiles.push(cssPath);
|
||||||
|
|
||||||
|
console.log('CustomBranding: CSS file injected: ' + cssPath);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('CustomBranding: Error injecting CSS file: ' + cssPath, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderPlaceHolders(): void {
|
||||||
|
console.log('CustomBrandingApplicationCustomizer._renderPlaceHolders()');
|
||||||
|
|
||||||
|
// Prüfen ob Top Placeholder verfügbar ist
|
||||||
|
if (!this._topPlaceholder && !this._bottomPlaceholder) {
|
||||||
|
this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
|
||||||
|
PlaceholderName.Top,
|
||||||
|
{ onDispose: this._onDispose }
|
||||||
|
);
|
||||||
|
|
||||||
|
this._bottomPlaceholder = this.context.placeholderProvider.tryCreateContent(
|
||||||
|
PlaceholderName.Bottom,
|
||||||
|
{ onDispose: this._onDispose }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Falls Placeholder nicht verfügbar, abbrechen
|
||||||
|
if (!this._topPlaceholder) {
|
||||||
|
console.error('CustomBranding: Top placeholder not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Falls Placeholder nicht verfügbar, abbrechen
|
||||||
|
if (!this._bottomPlaceholder) {
|
||||||
|
console.error('CustomBranding: Bottom placeholder not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._topPlaceholder.domElement && this._bottomPlaceholder.domElement) {
|
||||||
|
// Container erstellen mit hoher Priorität
|
||||||
|
this.renderPlaceHolder(this._topPlaceholder.domElement, this._bottomPlaceholder.domElement);
|
||||||
|
|
||||||
|
console.log('CustomBranding: HTML injected successfully');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderPlaceHolder(topcontainer: HTMLElement, bottomcontainer: HTMLElement) {
|
||||||
|
|
||||||
|
if (!this.properties) {
|
||||||
|
console.log('CustomBranding: No properties provided');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Top-Konfiguration unverändert übernehmen
|
||||||
|
const topConfig: IPlaceholderConfig | undefined =
|
||||||
|
this.properties.placeholdertop;
|
||||||
|
|
||||||
|
// Bottom-Konfiguration klonen oder initialisieren
|
||||||
|
const bottomConfig: IPlaceholderConfig =
|
||||||
|
this.properties.placeholderbottom
|
||||||
|
? { ...this.properties.placeholderbottom }
|
||||||
|
: { elements: [] };
|
||||||
|
|
||||||
|
// Admin-Link nur für Site Collection Admins ergänzen
|
||||||
|
if (this._isSiteAdmin()) {
|
||||||
|
bottomConfig.elements = bottomConfig.elements || [];
|
||||||
|
bottomConfig.elements.push(this._getAdminFooterElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
const config: IBrandingConfig = {
|
||||||
|
cssfiles: this.properties.cssfiles,
|
||||||
|
placeholdertop: topConfig.elements,
|
||||||
|
placeholderbottom: bottomConfig.elements
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('CustomBranding: Compiling JSON to HTML...');
|
||||||
|
const compiled = this._compileToHtml(config);
|
||||||
|
|
||||||
|
topcontainer.id = 'CustomHeader';
|
||||||
|
topcontainer.innerHTML = compiled.top;
|
||||||
|
|
||||||
|
bottomcontainer.id = 'CustomFooter';
|
||||||
|
bottomcontainer.innerHTML = compiled.bottom;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('CustomBranding: Error compiling configuration', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kompiliert die JSON-Konfiguration zu HTML
|
||||||
|
*/
|
||||||
|
private _compileToHtml(config: IBrandingConfig): { top: string; bottom: string } {
|
||||||
|
// Compile Top and Bottom separately
|
||||||
|
let topHtml: string = '';
|
||||||
|
let bottomHtml: string = '';
|
||||||
|
if (config.placeholdertop && Array.isArray(config.placeholdertop)) {
|
||||||
|
for (let i = 0; i < config.placeholdertop.length; i++) { topHtml += this._createElement(config.placeholdertop[i]); }
|
||||||
|
}
|
||||||
|
if (config.placeholderbottom && Array.isArray(config.placeholderbottom)) {
|
||||||
|
for (let i = 0; i < config.placeholderbottom.length; i++) { bottomHtml += this._createElement(config.placeholderbottom[i]); }
|
||||||
|
}
|
||||||
|
return { top: topHtml, bottom: bottomHtml };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt HTML für ein einzelnes Element
|
||||||
|
*/
|
||||||
|
private _createElement(element: IBrandingElement): string {
|
||||||
|
const tag = element.type || 'div';
|
||||||
|
let html = '<' + tag;
|
||||||
|
|
||||||
|
// Attribute hinzufügen
|
||||||
|
if (element.attributes) {
|
||||||
|
for (const key in element.attributes) {
|
||||||
|
if (element.attributes.hasOwnProperty(key)) {
|
||||||
|
const value = element.attributes[key];
|
||||||
|
html += ' ' + key + '="' + this._escapeHtml(value) + '"';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Styles hinzufügen
|
||||||
|
if (element.styles) {
|
||||||
|
const styleArray: string[] = [];
|
||||||
|
for (const key in element.styles) {
|
||||||
|
if (element.styles.hasOwnProperty(key)) {
|
||||||
|
const value = element.styles[key];
|
||||||
|
styleArray.push(key + ':' + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (styleArray.length > 0) {
|
||||||
|
const styleString = styleArray.join(';');
|
||||||
|
html += ' style="' + styleString + '"';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '>';
|
||||||
|
|
||||||
|
// Content hinzufügen
|
||||||
|
if (element.content) {
|
||||||
|
html += this._escapeHtml(element.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kinder hinzufügen
|
||||||
|
if (element.children && Array.isArray(element.children)) {
|
||||||
|
for (let i = 0; i < element.children.length; i++) {
|
||||||
|
html += this._createElement(element.children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Self-closing Tags behandeln
|
||||||
|
const selfClosingTags = ['img', 'br', 'hr', 'input'];
|
||||||
|
let isSelfClosing = false;
|
||||||
|
for (let i = 0; i < selfClosingTags.length; i++) {
|
||||||
|
if (selfClosingTags[i] === tag) {
|
||||||
|
isSelfClosing = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSelfClosing) {
|
||||||
|
html += '</' + tag + '>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isSiteAdmin(): boolean {
|
||||||
|
return this.context.pageContext.legacyPageContext.isSiteAdmin === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getAdminFooterElement(): IBrandingElement {
|
||||||
|
const siteUrl = this.context.pageContext.site.absoluteUrl;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'div',
|
||||||
|
styles: {
|
||||||
|
'text-align': 'right',
|
||||||
|
'padding': '8px 16px',
|
||||||
|
'border-top': '1px solid #e1e1e1',
|
||||||
|
'background-color': '#f8f8f8',
|
||||||
|
'font-size': '13px'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'a',
|
||||||
|
content: 'Einstellungen',
|
||||||
|
attributes: {
|
||||||
|
href: `${siteUrl}/SitePages/PortalSettings.aspx`
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
'text-decoration': 'none',
|
||||||
|
'font-weight': '600'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escaped HTML-Zeichen für Sicherheit
|
||||||
|
*/
|
||||||
|
private _escapeHtml(text: string): string {
|
||||||
|
const map: { [key: string]: string } = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": '''
|
||||||
|
};
|
||||||
|
|
||||||
|
return text.replace(/[&<>"']/g, function (m) {
|
||||||
|
return map[m];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDispose(): void {
|
||||||
|
console.log('CustomBrandingApplicationCustomizer._onDispose()');
|
||||||
|
|
||||||
|
// CSS-Dateien beim Dispose entfernen
|
||||||
|
const cssLinks = document.querySelectorAll('link[data-custom-branding="true"]');
|
||||||
|
for (let i = 0; i < cssLinks.length; i++) {
|
||||||
|
const link = cssLinks[i];
|
||||||
|
if (link.parentNode) {
|
||||||
|
link.parentNode.removeChild(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/extensions/customBranding/loc/en-us.js
Normal file
5
src/extensions/customBranding/loc/en-us.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
define([], function() {
|
||||||
|
return {
|
||||||
|
"Title": "CustomBrandingApplicationCustomizer"
|
||||||
|
}
|
||||||
|
});
|
||||||
8
src/extensions/customBranding/loc/myStrings.d.ts
vendored
Normal file
8
src/extensions/customBranding/loc/myStrings.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
declare interface ICustomBrandingApplicationCustomizerStrings {
|
||||||
|
Title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'CustomBrandingApplicationCustomizerStrings' {
|
||||||
|
const strings: ICustomBrandingApplicationCustomizerStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
||||||
1
src/index.ts
Normal file
1
src/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
||||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"typeRoots": [
|
||||||
|
"./node_modules/@types",
|
||||||
|
"./node_modules/@microsoft"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"es6-promise",
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"es5",
|
||||||
|
"dom",
|
||||||
|
"es2015.collection"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
30
tslint.json
Normal file
30
tslint.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||||
|
"rules": {
|
||||||
|
"class-name": false,
|
||||||
|
"export-name": false,
|
||||||
|
"forin": false,
|
||||||
|
"label-position": false,
|
||||||
|
"member-access": true,
|
||||||
|
"no-arg": false,
|
||||||
|
"no-console": false,
|
||||||
|
"no-construct": false,
|
||||||
|
"no-duplicate-variable": true,
|
||||||
|
"no-eval": false,
|
||||||
|
"no-function-expression": true,
|
||||||
|
"no-internal-module": true,
|
||||||
|
"no-shadowed-variable": true,
|
||||||
|
"no-switch-case-fall-through": true,
|
||||||
|
"no-unnecessary-semicolons": true,
|
||||||
|
"no-unused-expression": true,
|
||||||
|
"no-use-before-declare": true,
|
||||||
|
"no-with-statement": true,
|
||||||
|
"semicolon": true,
|
||||||
|
"trailing-comma": false,
|
||||||
|
"typedef": false,
|
||||||
|
"typedef-whitespace": false,
|
||||||
|
"use-named-parameter": true,
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user