Merge pull request #1 from pnp/master

Pull
This commit is contained in:
Qiong Wu (qiowu) 2020-12-16 11:17:49 +08:00 committed by GitHub
commit 189760f230
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
581 changed files with 275088 additions and 39261 deletions

4
.gitignore vendored
View File

@ -32,4 +32,6 @@ obj
# Styles Generated Code
*.scss.ts
packages/
packages/
samples/react-teams-send-notification/MyNotes.md
samples/react-teams-send-notification/teams/teams.zip

View File

@ -1,4 +1,7 @@
# title of the sample
# Title of the sample
> This is how you want the sample to appear in the samples browser.
> When naming your sample, try to give it a friendly name that describes what it does. Avoid using terms like `SharePoint` and `WebPart` -- because that's what all the samples in this repo is all about. Also, don't use `React`, `Angular`, `JavaScript`, etc. in your sample title -- unless that's what the sample is about.
## Summary
@ -6,16 +9,15 @@ Short summary on functionality and used technologies.
> Please provide a high-quality screenshot of your web parts below. It should be stored in a folder called `assets`.
> If possible, use a resolution of 1920x1080.
> If your web part uses a placeholder screen and requires the user to configure it, please use a screenshot of the web part as it appears after it has been configured.
> If your web part uses a placeholder screen and requires the user to configure it, please use a screenshot of the web part as it appears **after** it has been configured.
> You can add as many screen shots as you'd like to help users understand your web part without having to download it and install it.
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
![picture of the web part in action](assets/preview.png)
## Used SharePoint Framework Version
![1.11.0](https://img.shields.io/badge/version-1.11.0-green.svg)
![SPFx 1.11.0](https://img.shields.io/badge/version-1.11.0-green.svg)
## Applies to

View File

@ -3450,8 +3450,8 @@ inherits@2.0.1:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
ini@^1.3.4, ini@~1.3.0:
version "1.3.4"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
version "1.3.7"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
inpath@~1.0.2:
version "1.0.2"

View File

@ -15,7 +15,7 @@ extensions:
createdDate: 2/15/2020 12:00:00 AM
---
# AngularJS Greeting client-side web part
# AngularJS Greeting
## Summary

View File

@ -17,7 +17,7 @@ extensions:
- AngularJS
createdDate: 2/16/2017 12:00:00 AM
---
## Angular MS Graph Web Part Built with Angular v1.x
# Angular MS Graph Web Part Built with Angular v1.x
## Summary
This is a sample MS Graph web part that connects to Microsoft Graph and pulls SharePoint information from your

View File

@ -20,7 +20,7 @@ extensions:
Set of sample web parts illustrating how to use Angular Elements in the SharePoint Framework.
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/drop-1.4.1-green.svg)
![SPFx 1.4.1](https://img.shields.io/badge/spfx-1.4.1-green.svg)
## Applies to
@ -67,4 +67,4 @@ This web part illustrates the following concepts on top of the SharePoint Framew
* calling the SharePoint REST API from an Angular Element using PnPjs
* calling the Microsoft Graph from an Angular Element using PnPjs
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/angularelements-helloworld" />
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/angularelements-helloworld" />

View File

@ -14,7 +14,7 @@ extensions:
- jQuery
createdDate: 5/1/2017 12:00:00 AM
---
## Bootstrap Slider Built with jQuery v1.x and Boostrap v3.x
# Bootstrap Slider Built with jQuery v1.x and Boostrap v3.x
## Summary
Sample bootstrap slider which pulls the slides from a list inside the SharePoint site. The list is automatically deployed once the app is installed in the SharePoint site.

View File

@ -13,7 +13,7 @@ extensions:
- Handlebars
createdDate: 3/5/2017 12:00:00 AM
---
## SPFx Sample with Handlebars.js
# SPFx Sample with Handlebars.js
This sample demonstrate how to set up SPFx to use [Handlebars](http://handlebarsjs.com) through [webpack loader](https://webpack.github.io/docs/loaders.html).

View File

@ -15,7 +15,7 @@ extensions:
- JQuery
createdDate: 1/1/2016 12:00:00 AM
---
# JQuery, Photopile.JS & Office UI Fabric Client-Side Web Part
# JQuery, Photopile.JS & Office UI Fabric Client-Side Web Part
## Summary
@ -83,4 +83,4 @@ This web part illustrates the following concepts on top of the SharePoint Framew
* Etc.
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/jquery-photopile" />
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/jquery-photopile" />

View File

@ -11,7 +11,7 @@ extensions:
- SharePoint Framework
createdDate: 5/15/2017 12:00:00 AM
---
# Display List JavaScript Client-Side Web Part
# Display List
## Summary

View File

@ -13,7 +13,7 @@ extensions:
- react
createdDate: 8/1/2017 12:00:00 AM
---
# Display Employee Spotlight JavaScript Client-Side Web Part
# Employee Spotlight
## Summary
Simple Web Part that demonstrates the use of SharePoint Framework for show casing Employee Spotlight. The web part pulls data from a configured list and User Profile service.

View File

@ -4348,8 +4348,8 @@ inherits@^2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
ini@^1.3.4, ini@~1.3.0:
version "1.3.4"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
version "1.3.7"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
inline-style-prefixer@^3.0.6:
version "3.0.8"

View File

@ -1,11 +1,11 @@
# GitHub Badge
# GitHub Badge (JavaScript version)
## Summary
Displays information from GitHub for a specified user.
![drop](./assets/1.png)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-GA-green.svg)
![SPFx 1.8.0](https://img.shields.io/badge/version-1.8.0-green.svg)
## Applies to
@ -50,4 +50,4 @@ Future samples will refactor this into something more professional that follows
Ultimately, this sample along with its yet-to-be-created future samples could be a useful teaching tool to developers new to SPFx as well as developers who aren't well-versed in SPFx coding patterns or the various frameworks available.
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-gitHubBadge" />
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-gitHubBadge" />

View File

@ -1,4 +1,4 @@
# SPFx My Flows Web Part
# My Flows
## Summary

View File

@ -14,54 +14,54 @@ extensions:
- JQuery
createdDate: 1/1/2016 12:00:00 AM
---
# Embed a PowerBI report in a Client-Side Web Part
## Summary
This sample SharePoint Framework client-side web part embedding a PowerBI report using PowerBI Embedded without any server-side code.
![PowerBI Embedded Client-SideWeb Part in the SharePoint Workbench](./assets/screenshot_powerbi_embedded_spfx.png)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/drop-drop2-red.svg)
## Solution
Solution|Author(s)
--------|---------
powerbi-embedded|Roland Oldengarm (Provoke Solutions, @rolandoldengarm)
## Version history
Version|Date|Comments
-------|----|--------
1.0|September 13, 2016|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Prerequisites
- Created a Workspace and a Workspace collection in Azure
- PowerBI report saved as PBIX
- PBIX uploaded to the Workspace
- Report Access Token generated
Please refer to [this blog post](http://rolandoldengarm.com/index.php/2016/09/13/part-3-how-to-embed-a-power-bi-report-in-sharepoint-with-the-sharepoint-framework/) for detailed instructions how to do this.
## Minimal Path to Awesome
- clone this repo
- `$ npm i`
- `$ gulp serve`
## Features
The _PowerBI Embedded_ Client-Side Web Part is built on the SharePoint Framework using React and uses [PowerBI Embedded](https://azure.microsoft.com/en-us/services/power-bi-embedded/) to securely display a report.
All authentication and rendering happens client-side, there is no server-side component required.
It uses the [PowerBI Client](https://www.npmjs.com/package/powerbi-client) for rendering the PowerBI report.
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-powerbi-embedded" />
# Embed a PowerBI Report
## Summary
This sample SharePoint Framework client-side web part embedding a PowerBI report using PowerBI Embedded without any server-side code.
![PowerBI Embedded Client-SideWeb Part in the SharePoint Workbench](./assets/screenshot_powerbi_embedded_spfx.png)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/drop-drop2-red.svg)
## Solution
Solution|Author(s)
--------|---------
powerbi-embedded|Roland Oldengarm (Provoke Solutions, @rolandoldengarm)
## Version history
Version|Date|Comments
-------|----|--------
1.0|September 13, 2016|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Prerequisites
- Created a Workspace and a Workspace collection in Azure
- PowerBI report saved as PBIX
- PBIX uploaded to the Workspace
- Report Access Token generated
Please refer to [this blog post](http://rolandoldengarm.com/index.php/2016/09/13/part-3-how-to-embed-a-power-bi-report-in-sharepoint-with-the-sharepoint-framework/) for detailed instructions how to do this.
## Minimal Path to Awesome
- clone this repo
- `$ npm i`
- `$ gulp serve`
## Features
The _PowerBI Embedded_ Client-Side Web Part is built on the SharePoint Framework using React and uses [PowerBI Embedded](https://azure.microsoft.com/en-us/services/power-bi-embedded/) to securely display a report.
All authentication and rendering happens client-side, there is no server-side component required.
It uses the [PowerBI Client](https://www.npmjs.com/package/powerbi-client) for rendering the PowerBI report.
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-powerbi-embedded" />

View File

@ -1,4 +1,4 @@
# JS Property Controls SVG
# Dynamic Scalable Vector Graphics (SVG) image using propertie
## Summary
An SPFx webpart that displays a Scalable Vector Graphics (SVG) image using properties to customize how it is rendered. The webpart utilizes the PnP SPFx Property Controls package (specifially the SpinButton and ColorPicker) to set these properties.
@ -54,4 +54,4 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
![Screenshot](./assets/js-propertycontrols-svg.png)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-propertycontrols-svg" />
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-propertycontrols-svg" />

View File

@ -5,7 +5,14 @@
"id": "5d6f4a5a-9d2b-4a93-a283-16b8f5ea75d6",
"version": "1.5.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true
"skipFeatureDeployment": true,
"developer": {
"name": "Contoso",
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
}
},
"paths": {
"zippedPackage": "solution/workbench-customizer.sppkg"

View File

@ -13,7 +13,7 @@ extensions:
- Knockout
createdDate: 1/1/2016 12:00:00 AM
---
# Sample Web Part implementing dependent properties in Property Pane
# Dependent Property Pane Properties
## Summary
Sample Web Part illustrating

View File

@ -13,7 +13,7 @@ extensions:
- Knockout
createdDate: 3/1/2017 12:00:00 AM
---
# Sample showing the use of @pnp/sp library with Knockoutjs
# Using @pnp/sp with Knockoutjs
## Summary
@ -62,4 +62,4 @@ Version|Date|Comments
Demonstrates integration of @pnp/sp js with the SharePoint Framework GA release. Also shows the use of mock data in the local workbench.
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/knockout-sp-pnp-js" />
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/knockout-sp-pnp-js" />

View File

@ -6342,12 +6342,6 @@
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
"bundled": true,
"dev": true,
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
@ -8083,9 +8077,9 @@
"dev": true
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true
},
"inpath": {

View File

@ -13,7 +13,7 @@ extensions:
- knockout
createdDate: 1/1/2016 12:00:00 AM
---
# Taxonomy Web Part
# Taxonomy
## Summary
Sample Web Part illustrating

15278
samples/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,75 @@
# Edit Application Customizers
## Summary
This web part will allow users to view/update application customizers properties across any web where the current user has access to. This web part can be helpful when we require to update the properties for application customizer without using any PowerShell script or cli tool.
![Web part in action](assets/react-all-applicationcustomizers.gif?raw=true "Webpart in action")
## Idea behind this web part
- SPFx Application customizer can be used to add scripts, and add custom html to well known placeholder(header and footer)
- We can use properties to pass data to Application customizers to make solution customizable.
- To update properties of application customizer there is no UI based solution.
- To update the title, details and other information of application customizer we use either PowerShell script or cli tool.
- This webpart can be used at a central location where all the users have access and if they require to update title, description and properties.
## Features
- Webpart to view/update Application Customizers registered for a selected web
- Provides two different UI Accordion or List based(configurable)
- Provides a dropdown to select the web from where we would require to fetch application customizers
- Allows to update application customizer properties which makes it easy to make re-useable application customizers
## Used SharePoint Framework Version
![SPFx 1.11](https://img.shields.io/badge/version-1.11-green.svg)
## Applies to
- [SharePoint Framework](https://aka.ms/spfx)
- [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram)
## Solution
Solution|Author(s)
--------|---------
react-edit-applicationcustomizers | [Kunj Sangani](https://www.linkedin.com/in/kunj-sangani/) and [Siddharth Vaghasia](https://www.linkedin.com/in/siddharthvaghasia)
## Version history
Version|Date|Comments
-------|----|--------
1.0|October 16, 2020|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
- Clone this repository
- Ensure that you are at the solution folder
- in the command-line run:
- **npm install**
- **gulp serve**
For any issue or help, Buzz us on twitter:([sanganikunj](https://twitter.com/sanganikunj)) or ([siddh_me](https://twitter.com/siddh_me/))
> Sharing is caring!
## References
- [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
- [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview)
- [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis)
- [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview)
- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-edit-applicationcustomizers" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@ -0,0 +1,18 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"application-customizers-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/applicationCustomizers/ApplicationCustomizersWebPart.js",
"manifest": "./src/webparts/applicationCustomizers/ApplicationCustomizersWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"ApplicationCustomizersWebPartStrings": "lib/webparts/applicationCustomizers/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View 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": "react-all-application-customizers",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,20 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-all-application-customizers-client-side-solution",
"id": "f560c809-b5a8-4320-abef-224f27d2d0f0",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false,
"developer": {
"name": "",
"websiteUrl": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"mpnId": ""
}
},
"paths": {
"zippedPackage": "solution/react-all-application-customizers.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View 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'));

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
{
"name": "react-all-application-customizers",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@material-ui/core": "^4.11.0",
"@microsoft/sp-core-library": "1.11.0",
"@microsoft/sp-lodash-subset": "1.11.0",
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
"@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.11.0",
"@pnp/sp": "^2.0.10",
"ace-builds": "^1.4.12",
"office-ui-fabric-react": "6.214.0",
"react": "16.8.5",
"react-ace": "^9.1.3",
"react-dom": "16.8.5"
},
"devDependencies": {
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@microsoft/sp-build-web": "1.11.0",
"@microsoft/sp-tslint-rules": "1.11.0",
"@microsoft/sp-module-interfaces": "1.11.0",
"@microsoft/sp-webpart-workbench": "1.11.0",
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",
"@types/webpack-env": "1.13.1",
"@types/es6-promise": "0.0.33"
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,35 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "05567ead-69c5-4c36-958c-613a21cce18c",
"alias": "ApplicationCustomizersWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": [
"SharePointWebPart"
],
"preconfiguredEntries": [
{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": {
"default": "Other"
},
"title": {
"default": "ApplicationCustomizers"
},
"description": {
"default": "ApplicationCustomizers description"
},
"officeFabricIconFontName": "CustomizeToolbar",
"properties": {
"description": "ApplicationCustomizers",
"designType": "List"
}
}
]
}

View File

@ -0,0 +1,85 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneChoiceGroup
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import * as strings from 'ApplicationCustomizersWebPartStrings';
import ApplicationCustomizers from './components/ApplicationCustomizers';
import { IApplicationCustomizersProps } from './components/IApplicationCustomizersProps';
import { sp } from "@pnp/sp/presets/all";
export interface IApplicationCustomizersWebPartProps {
description: string;
designType: string;
}
export default class ApplicationCustomizersWebPart extends BaseClientSideWebPart<IApplicationCustomizersWebPartProps> {
protected onInit(): Promise<void> {
return super.onInit().then(_ => {
// other init code may be present
sp.setup({
spfxContext: this.context
});
});
}
public render(): void {
const element: React.ReactElement<IApplicationCustomizersProps> = React.createElement(
ApplicationCustomizers,
{
description: this.properties.description,
context: this.context,
designType: this.properties.designType
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
}),
PropertyPaneChoiceGroup('designType', {
label: strings.DesignFieldLabel,
options: [
{ key: 'Accordion', checked: true, text: 'Accordion', iconProps: { officeFabricIconFontName: 'AutoFillTemplate' } },
{ key: 'List', text: 'List', iconProps: { officeFabricIconFontName: 'GroupedList' } }
]
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,84 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.applicationCustomizers {
.container {
max-width: 1200px;
margin: 0px auto;
}
.row {
@include ms-Grid-row;
padding: 20px;
}
.column2 {
@include ms-Grid-col;
@include ms-lg2;
@include ms-xl2;
margin-top: 5px;
padding: 10px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl10;
margin-top: 5px;
padding: 10px;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
margin-top: 10px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}
:global{
#workbenchPageContent{
max-width: 1200px;
}
}

View File

@ -0,0 +1,446 @@
import * as React from 'react';
import styles from './ApplicationCustomizers.module.scss';
import { IApplicationCustomizersProps } from './IApplicationCustomizersProps';
import { assign } from '@microsoft/sp-lodash-subset';
import ApplicationCustomizersService from "../service/ApplicationCustomizersService";
import { Dropdown, DropdownMenuItemType, IDropdownOption, IDropdownStyles } from 'office-ui-fabric-react/lib/Dropdown';
import MuiAccordion from '@material-ui/core/Accordion';
import MuiAccordionSummary from '@material-ui/core/AccordionSummary';
import MuiAccordionDetails from '@material-ui/core/AccordionDetails';
import { withStyles } from '@material-ui/core/styles';
import { DefaultButton, TextField, thProperties, Dialog, DialogFooter, DialogType, List, mergeStyleSets, getFocusStyle, ITheme, getTheme, IconButton, Panel, PanelType } from 'office-ui-fabric-react';
import AceEditor from "react-ace";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-github";
const Accordion = withStyles({
root: {
border: '1px solid rgba(0, 0, 0, .125)',
boxShadow: 'none',
'&:not(:last-child)': {
borderBottom: 0,
},
'&:before': {
display: 'none',
},
'&$expanded': {
margin: 'auto',
},
},
expanded: {},
})(MuiAccordion);
const AccordionSummary = withStyles({
root: {
backgroundColor: 'rgba(0, 0, 0, .03)',
borderBottom: '1px solid rgba(0, 0, 0, .125)',
marginBottom: -1,
minHeight: 56,
'&$expanded': {
minHeight: 56,
},
},
content: {
'&$expanded': {
margin: '12px 0',
},
},
expanded: {},
})(MuiAccordionSummary);
const AccordionDetails = withStyles((themes) => ({
root: {
padding: themes.spacing(2),
display: 'block'
},
}))(MuiAccordionDetails);
const theme: ITheme = getTheme();
const { palette, semanticColors, fonts } = theme;
const classNames = mergeStyleSets({
container: {
overflow: 'auto',
maxHeight: 500,
},
itemCell: [
getFocusStyle(theme, { inset: -1 }),
{
minHeight: 54,
padding: 10,
boxSizing: 'border-box',
borderBottom: `1px solid ${semanticColors.bodyDivider}`,
display: 'flex',
selectors: {
'&:hover': { background: palette.neutralLight },
},
},
],
itemImage: {
flexShrink: 0,
},
itemContent: {
marginLeft: 10,
overflow: 'hidden',
flexGrow: 1,
},
itemName: [
fonts.xLarge,
{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
color: 'black'
},
],
itemIndex: {
fontSize: fonts.small.fontSize,
marginBottom: 10,
color: 'black'
},
chevron: {
alignSelf: 'center',
marginLeft: 10,
color: palette.neutralTertiary,
fontSize: fonts.large.fontSize,
flexShrink: 0,
},
});
const applicationCustomizersService = new ApplicationCustomizersService();
export interface IApplicationCustomizersState {
selectedItem: IDropdownOption;
dropdownSites: IDropdownOption[];
expanded: string | false;
allCustomizers: any;
previousEditIndex?: number;
editJSON?: { Title: string; Description: string; ClientSideComponentProperties: any };
hideDialog: boolean;
dialogContentProps?: any;
isPanelOpen: boolean;
itemInEdit?: number;
isViewPanelOpen: boolean;
viewJSON?: {
Title: string; Description: string; ClientSideComponentId: any;
ClientSideComponentProperties: any; Id: any;
};
}
export default class ApplicationCustomizers extends React.Component<IApplicationCustomizersProps, IApplicationCustomizersState> {
constructor(props: IApplicationCustomizersProps) {
super(props);
this.state = {
selectedItem: undefined,
dropdownSites: undefined,
expanded: 'panel1',
allCustomizers: [],
previousEditIndex: undefined,
hideDialog: true,
isPanelOpen: false,
isViewPanelOpen: false
};
}
public componentDidMount() {
applicationCustomizersService.fetchAllApplictionCustomizers(this.props.context.pageContext.web.absoluteUrl)
.then((allCustomizers) => {
allCustomizers = allCustomizers.map((cus) => { return assign(cus, { inEdit: false }); });
this.setState({ allCustomizers: allCustomizers });
}).catch((err) => {
console.log(err);
});
applicationCustomizersService.getAllSiteCollection()
.then((allSites) => {
let dropdownSites = allSites.PrimarySearchResults.map((val) => {
val['key'] = val['SPSiteUrl'];
val['text'] = `${val['Title']} - ${val['SPSiteUrl']}`;
return val;
});
this.setState({ dropdownSites: dropdownSites });
});
}
private onChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
this.setState({ selectedItem: item });
applicationCustomizersService.fetchAllApplictionCustomizers(item.key as string)
.then((allCustomizers) => {
allCustomizers = allCustomizers.map((cus) => { return assign(cus, { inEdit: false }); });
this.setState({ allCustomizers: allCustomizers });
}).catch((err) => {
console.log(err);
});
}
public handleChange = (panel: string) => (event: React.ChangeEvent<{}>, newExpanded: boolean) => {
this.setState({ expanded: newExpanded ? panel : false });
}
public editCustomApplication = (index: number, inEdit: boolean) => {
let allCustomizers = this.state.allCustomizers;
allCustomizers[index].inEdit = inEdit;
if (this.state.previousEditIndex !== undefined && inEdit) {
allCustomizers[this.state.previousEditIndex].inEdit = false;
}
if (inEdit) {
this.setState({
isPanelOpen: this.props.designType === "List" ? true : false,
itemInEdit: index,
editJSON: {
Title: allCustomizers[index].Title,
Description: allCustomizers[index].Description,
ClientSideComponentProperties: allCustomizers[index].ClientSideComponentProperties
}
});
}
if (!inEdit) {
this.setState({ isPanelOpen: false });
}
this.setState({ allCustomizers: allCustomizers, previousEditIndex: index });
}
public onChangeJSON = (obj: string, newValue: string) => {
let { editJSON } = this.state;
editJSON[obj] = newValue;
this.setState({ editJSON });
}
public updateCustomizer = (index: number) => {
let webURL = this.state.selectedItem ? this.state.selectedItem.key : this.props.context.pageContext.web.absoluteUrl;
let { allCustomizers } = this.state;
applicationCustomizersService.updateApplicationCustomizer(webURL, this.state.allCustomizers[index].Id, this.state.editJSON)
.then(() => {
allCustomizers[index].inEdit = false;
this.setState({
allCustomizers: allCustomizers,
hideDialog: false,
isPanelOpen: false,
dialogContentProps: {
type: DialogType.normal,
title: 'Updated Successfully',
closeButtonAriaLabel: 'Close',
subText: 'Your Customizer is updated. Please refresh the page to look at the changes?'
}
});
}).catch((err) => {
this.setState({
hideDialog: false,
dialogContentProps: {
type: DialogType.normal,
title: 'Updat Error',
closeButtonAriaLabel: 'Close',
subText: 'There was some error while updating you customizer. Please try again'
}
});
});
}
private toggleHideDialog = () => {
applicationCustomizersService.fetchAllApplictionCustomizers(this.state.selectedItem ?
this.state.selectedItem.key as string : this.props.context.pageContext.web.absoluteUrl)
.then((allCustomizers) => {
allCustomizers = allCustomizers.map((cus) => { return assign(cus, { inEdit: false }); });
this.setState({ allCustomizers: allCustomizers, hideDialog: true });
}).catch((err) => {
console.log(err);
});
}
private viewCustomApplication = (index: number) => {
let { allCustomizers } = this.state;
this.setState({
isViewPanelOpen: true,
viewJSON: {
Title: allCustomizers[index].Title,
ClientSideComponentId: allCustomizers[index].ClientSideComponentId,
ClientSideComponentProperties: allCustomizers[index].ClientSideComponentProperties,
Description: allCustomizers[index].Description,
Id: allCustomizers[index].Id
}
});
}
private onRenderCell = (item: any, index: number, isScrolling: boolean): JSX.Element => {
return (
<div className={classNames.itemCell} data-is-focusable={true}>
<div className={classNames.itemContent}>
<div className={classNames.itemName}>{item.Title}</div>
<div className={classNames.itemIndex}>{item.Description}</div>
</div>
<IconButton iconProps={{ iconName: 'View' }} onClick={() => { this.viewCustomApplication(index); }} title="View" ariaLabel="View"></IconButton>
<IconButton iconProps={{ iconName: 'Edit' }} onClick={() => { this.editCustomApplication(index, true); }} title="Edit" ariaLabel="Edit"></IconButton>
</div>
);
}
public render(): React.ReactElement<IApplicationCustomizersProps> {
return (
<div className={styles.applicationCustomizers}>
<div className={styles.container}>
<div className={styles.row}>
<h1>{this.props.description}</h1>
</div>
<div className={styles.row}>
<Dropdown
label="Select Web"
selectedKey={this.state.selectedItem ? this.state.selectedItem.key : undefined}
onChange={this.onChange}
placeholder="Select an option"
options={this.state.dropdownSites}
/>
</div>
{this.props.designType === "Accordion" && this.state.allCustomizers.length !== 0 &&
<div className={styles.row}>
{this.state.allCustomizers.map((customizer, index) => {
return (
<Accordion square expanded={this.state.expanded === `panel${index + 1}`} onChange={this.handleChange(`panel${index + 1}`)}>
<AccordionSummary aria-controls="panel1d-content" id="panel1d-header">
<div>{customizer.Title}{customizer.Description && ` - ${customizer.Description}`}</div>
</AccordionSummary>
<AccordionDetails>
{!customizer.inEdit ?
<div>
<div className={styles.column2}>Component ID</div>
<div className={styles.column}>{customizer.ClientSideComponentId}</div>
<div className={styles.column2}>ID</div>
<div className={styles.column}>{customizer.Id}</div>
<div className={styles.column2}>Properties</div>
<div className={styles.column}>{customizer.ClientSideComponentProperties}</div>
</div> :
<div>
<div className={styles.column2}>Title</div>
<div className={styles.column}><TextField value={this.state.editJSON.Title}
onChange={(ev, newVal) => {
this.onChangeJSON("Title", newVal);
}} />
</div>
<div className={styles.column2}>Description</div>
<div className={styles.column}><TextField value={this.state.editJSON.Description} multiline rows={3}
onChange={(ev, newVal) => {
this.onChangeJSON("Description", newVal);
}} /></div>
<div className={styles.column2}>Properties</div>
<div className={styles.column}>
<AceEditor
placeholder="Placeholder Text"
mode="json"
theme="github"
onChange={(val) => { this.onChangeJSON("ClientSideComponentProperties", val); }}
fontSize={14}
style={{ height: 200, width: 790 }}
showPrintMargin={true}
showGutter={true}
highlightActiveLine={false}
value={this.state.editJSON.ClientSideComponentProperties}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: false,
enableSnippets: false,
showLineNumbers: true,
tabSize: 2,
}} />
</div>
</div>
}
{!customizer.inEdit ?
<DefaultButton className={styles.button} text="Edit" onClick={() => { this.editCustomApplication(index, true); }} /> :
[<DefaultButton className={styles.button} text="Update" onClick={() => { this.updateCustomizer(index); }} />,
<DefaultButton style={{ marginLeft: 10, marginTop: 10 }} text="Cancel" onClick={() => { this.editCustomApplication(index, false); }} />]
}
</AccordionDetails>
</Accordion>
);
})}
</div>
}
{this.props.designType === "List" && this.state.allCustomizers.length !== 0 &&
<div className={styles.row}>
<List items={this.state.allCustomizers} onRenderCell={this.onRenderCell} />
</div>
}
{this.state.allCustomizers.length === 0 &&
<div className={styles.row}>We did not find any Application Customizers for the selected web</div>
}
<Dialog
hidden={this.state.hideDialog}
onDismiss={() => this.toggleHideDialog()}
dialogContentProps={this.state.dialogContentProps}
>
<DialogFooter>
<DefaultButton onClick={() => this.toggleHideDialog()} text="Cancel" />
</DialogFooter>
</Dialog>
<Panel
headerText="Edit Application Customizer"
isOpen={this.state.isPanelOpen}
onDismiss={() => this.setState({ isPanelOpen: false })}
closeButtonAriaLabel="Close"
type={PanelType.large}
>
{this.state.editJSON &&
<div className={styles.applicationCustomizers}>
<div className={styles.column2}>Title</div>
<div className={styles.column}><TextField value={this.state.editJSON.Title}
onChange={(ev, newVal) => {
this.onChangeJSON("Title", newVal);
}} />
</div>
<div className={styles.column2}>Description</div>
<div className={styles.column}><TextField value={this.state.editJSON.Description} multiline rows={3}
onChange={(ev, newVal) => {
this.onChangeJSON("Description", newVal);
}} /></div>
<div className={styles.column2}>Properties</div>
<div className={styles.column}>
<AceEditor
placeholder="Placeholder Text"
mode="json"
theme="github"
onChange={(val) => { this.onChangeJSON("ClientSideComponentProperties", val); }}
fontSize={14}
style={{ height: 200, width: 800 }}
showPrintMargin={true}
showGutter={true}
highlightActiveLine={false}
value={this.state.editJSON.ClientSideComponentProperties}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: false,
enableSnippets: false,
showLineNumbers: true,
tabSize: 2,
}} />
</div>
<DefaultButton style={{ marginLeft: 10, marginTop: 10 }} className={styles.button} text="Update" onClick={() => { this.updateCustomizer(this.state.itemInEdit); }} />
<DefaultButton style={{ marginLeft: 10, marginTop: 10 }} text="Cancel" onClick={() => { this.editCustomApplication(this.state.itemInEdit, false); }} />
</div>}
</Panel>
<Panel
headerText="View Application Customizer"
isOpen={this.state.isViewPanelOpen}
onDismiss={() => this.setState({ isViewPanelOpen: false })}
closeButtonAriaLabel="Close"
type={PanelType.medium}
>{this.state.viewJSON &&
<div className={styles.applicationCustomizers}>
<div className={styles.column2}>Title</div>
<div className={styles.column}>{this.state.viewJSON.Title}</div>
<div className={styles.column2}>Description</div>
<div className={styles.column}>{this.state.viewJSON.Description ? this.state.viewJSON.Description : 'null'}</div>
<div className={styles.column2}>ComponentID</div>
<div className={styles.column}>{this.state.viewJSON.ClientSideComponentId}</div>
<div className={styles.column2}>ID</div>
<div className={styles.column}>{this.state.viewJSON.Id}</div>
<div className={styles.column2}>Properties</div>
<div className={styles.column}>{this.state.viewJSON.ClientSideComponentProperties}</div>
</div>}</Panel>
</div>
</div>
);
}
}

View File

@ -0,0 +1,7 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
export interface IApplicationCustomizersProps {
description: string;
context: WebPartContext;
designType: string;
}

View File

@ -0,0 +1,8 @@
define([], function () {
return {
"PropertyPaneDescription": "",
"BasicGroupName": "",
"DescriptionFieldLabel": "Title",
"DesignFieldLabel": "Choose design"
}
});

View File

@ -0,0 +1,11 @@
declare interface IApplicationCustomizersWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
DesignFieldLabel: string;
}
declare module 'ApplicationCustomizersWebPartStrings' {
const strings: IApplicationCustomizersWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,61 @@
import { sp } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/user-custom-actions";
import { ISearchQuery } from "@pnp/sp/search";
import { Web } from "@pnp/sp/webs";
export default class ApplicationCustomizersService {
/**
* fetchAllApplictionCustomizers
*/
public fetchAllApplictionCustomizers = async (webURL: string) => {
let web = Web(webURL);
let response;
try {
response = await web.userCustomActions();
console.log(response);
//let temp = await sp.site.userCustomActions();
//console.log(temp);
} catch (error) {
console.log(error);
response = error;
}
return response;
}
/**
* getAllSiteCollection
*/
public getAllSiteCollection = async () => {
let response;
try {
response = await sp.search(<ISearchQuery>{
Querytext: "contentclass:STS_Site",
SelectProperties: ["Title", "SPSiteUrl", "WebTemplate"],
RowLimit: 1000,
TrimDuplicates: true
});
console.log(response.PrimarySearchResults);
} catch (error) {
console.log(error);
response = error;
}
return response;
}
/**
* updateApplicationCustomizer
*/
public updateApplicationCustomizer = async (webURL: string | number, selectedID: string, updateJSON: any) => {
let web = Web(webURL as string);
let response;
try {
response = await web.userCustomActions.getById(selectedID).update(updateJSON);
} catch (error) {
console.log(error);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

View File

@ -0,0 +1,39 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"node_modules",
"lib"
]
}

View 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
}
}

View File

@ -1,4 +1,4 @@
# SPFx Dynamic Accordion - FAQ Builder web part
# Dynamic Accordion - FAQ Builder
## Summary
@ -6,7 +6,7 @@
- Adds a collapsible accordion section to an Office 365 SharePoint page or Teams Tab.
- Ideal for displaying FAQs.
- When adding the web part, you'll be prompted to select a list from a property panel dropdown (target list must be created with FAQ type Question and Answer.).
- The web part expects a choice type that will be used as Category.
- The web part expects a column called **Category** of type choice that will be used as the category.
- The web part will automatically load all the properties in two dropdowns. One for Accordion Title and One for Accordion Content that must be html type.
- This will generate an accordion with one section for each item in the list.
- Modifications/deletions/additions to the list items in the target list of an added web part are automatically reflected on the page.
@ -23,8 +23,8 @@
- When creating the columns, select "Multiple lines of text". Rich text is now supported within the Content column.
![Create list for use with the Accordion](./assets/FAQsList.png)
![FAQ list Template for use with the Accordion](./assets/FAQsList.stp)
![FAQ Site Script use with the Accordion](./assets/FAQsList.json)
[FAQ list Template for use with the Accordion](./assets/FAQsList.stp)
[FAQ Site Script use with the Accordion](./assets/FAQsList.json)
**2) Add the Dynamic Accordion Section web part to your page & select your list, category, title and content columns. Click Apply and Publish:**

View File

@ -1,4 +1,4 @@
# SPFx Accordion Section FAQ Builder web part
# Accordion Section -- FAQ Builder
## Summary

View File

@ -14,7 +14,7 @@ extensions:
createdDate: 1/8/2018 12:00:00 AM
---
## Using React Accordion plugin with SPFx
# Using React Accordion plugin with SPFx
## Summary

View File

@ -13,7 +13,7 @@ extensions:
- React
createdDate: 11/28/2018 12:00:00 AM
---
# Image Gallery Web Part Built with Adaptive Cards
# Image Gallery Built with Adaptive Cards
## Summary

View File

@ -1,4 +1,4 @@
# react-adaptivecards-hooks
# Adaptive Cards using React Hooks
## Summary

View File

@ -13,7 +13,7 @@ extensions:
- react
createdDate: 04/24/2020 12:00:00 AM
---
## SPFx webpart to add JS and CSS reference on Modern Pages via SPFx application customizer extension
# Add JS and CSS reference on Modern Pages via SPFx application customizer extension
This repo is a react based SPFx web part and extension that allows users to add/modify/delete custom js and css file references using SPFx application customizer extension all modern pages within SP online site. This web part provides an interface to JS and CSS file references so that we don't have to modify code when we need to change references or add new references in the future. As part of security measures, this actions on web part can be only accessed by users who have Manage web permission on site.

View File

@ -1,4 +1,4 @@
# react-admin-sc-catalog-pnpjs - Site Collection App Catalogs Summary View.
# Site Collection App Catalog Summary View.
## Summary

View File

@ -1,4 +1,4 @@
# React Aggregated Calendar Webpart
# Aggregated Calendar
## Summary
This is a sample webpart developed using React Framework to gather the aggregated events from the multiple calendars from multiple sites using Full Calendar from fullcalendar.io

View File

@ -13,7 +13,7 @@ extensions:
- react
createdDate: 8/1/2017 12:00:00 AM
---
# SPFx React app settings webpart #
# App Settings
## Summary

View File

@ -13,7 +13,7 @@ extensions:
- react
createdDate: 5/10/2020 12:00:00 AM
---
# React AppInsights Dashboard
# Azure Application Insights Dashboard
## Summary

View File

@ -13,7 +13,7 @@ extensions:
- react
createdDate: 5/1/2017 12:00:00 AM
---
# React sample showing the use of @pnp/js with Async / Await
# Using @pnp/js with Async / Await
## Summary
This webpart demonstrates how to use [PnPJS](https://pnp.github.io/pnpjs/) with Async functions into the SharePoint Framework as well as integrating [PnP JS and SPFx Logging systems](https://blog.josequinto.com/2017/04/30/how-to-integrate-pnp-js-core-and-sharepoint-framework-logging-systems/). Real example querying SharePoint library to show document sizes.

View File

@ -1,4 +1,4 @@
# SPFx Avatar
# Avatar Editor
## Summary

View File

@ -1,4 +1,4 @@
## Local Azure Function and SPFx Web Part Development to consume third party APIs
# Local Azure Function and SPFx Web Part Development to consume third party APIs
This sample shows how to consume third-party APIs through an Azure Functions by a Web Part. In this scenario, Vimeo is the representative third party API.
This project contains two separate project folders:

View File

@ -1,4 +1,4 @@
# React Birthdays Web Part
# Birthdays
## Summary
The Web Part Birthdays shows the upcoming birthdays in the company, the web part reads birthdays from a list located on the tenant's root site with title "Birthdays."
@ -17,7 +17,7 @@ But you can synchronize the Birthdays list with other applications HR Systems, o
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.8.2-green.svg)
![1.8.2](https://img.shields.io/badge/version-1.8.2-green.svg)
## Applies to

View File

@ -0,0 +1,214 @@
# SharePoint web part sample with bot framework - Secure
## Summary
[Web parts](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/overview-client-side-web-parts) is a special kind of SharePoint controls that can be supported by the [Bot Framework](https://dev.botframework.com). This sample will show you how to embed a Bot Framework bot into a SharePoint web site with security consideration.
There are two parts included in this sample:
1. An echo bot sample
1. A web part sample
The web part sample embeds the echo bot by using a webchat. As web part code is running on client side, [web chat security](https://blog.botframework.com/2018/09/01/using-webchat-with-azure-bot-services-authentication/) needs to be taken into consideration. This sample shows how to secure your conversation including:
- Use Direct Line token instead of Direct Line secret
- Tamper-proof user: for user id, generate it inside client side and detect if the client has changed the user ID and reject the change.
This demo does not include any threat models and is designed for educational purposes only. When you design a production system, threat-modelling is an important task to make sure your system is secure and provide a way to quickly identify potential source of data breaches. IETF [RFC 6819](https://tools.ietf.org/html/rfc6819) and [OAuth 2.0 for Browser-Based Apps](https://tools.ietf.org/html/draft-ietf-oauth-browser-based-apps-01#section-9) is a good starting point for threat-modelling when using OAuth 2.0.
![react-bot-framework-secure](./assets/sp-wp-secure.gif)
## Used SharePoint Framework Version
![1.0](https://img.shields.io/badge/version-1.0-green.svg)
## Applies to
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
* [Microsoft Bot Framework](https://dev.botframework.com/)
## Prerequisites
- [Node.js](https://nodejs.org) version 10.19 (Node.js v9.x, v11.x, and v12.x are not currently supported with SharePoint Framework development)
```bash
# determine node version
node --version
```
## Solution
Solution|Author(s)
--------|---------
webpart | STCA BF Channel and ABS (stcabfchannel@microsoft.com) <br/> Stephan Bisser (@stephanbisser, bisser.io)
bot | STCA BF Channel and ABS (stcabfchannel@microsoft.com)
## Version history
Version|Date|Comments
-------|----|--------
1.0|Nov 10, 2020|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
### Enlist
- Clone the repository
```bash
git clone https://github.com/pnp/sp-dev-fx-webparts.git
```
- In a terminal, navigate to `sp-dev-fx-webparts`
```bash
cd sp-dev-fx-webparts
```
- Navigate to the folder containing this sample
```bash
cd samples
cd react-bot-framework-secure
```
### [Setup bot](./bot/README.md)
- Go to `./bot`
- Install modules
```bash
npm install
```
- Start the bot. You can use emulator to verify whether the bot works
```bash
npm start
```
- Register connections. You can get it done by [deploy your bot to Azure](https://aka.ms/azuredeployment). Save your bot service endpoint like: "https://YOUR_BOT.azurewebsites.net". Save your AAD Id as `YOUR_APP_ID` and secret as `YOUR_APP_PSW` also.
- [Connect to direct line](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-channel-connect-directline?view=azure-bot-service-4.0), copy one of the Secret Key values as YOUR_DIRECT_LINE_SECRET and store this for later. This is your Direct Line Secret.
- Add `DirectLineSecret` to an `.env` config file under `./bot`
```bash
MicrosoftAppId=YOUR_APP_ID
MicrosoftAppPassword=YOUR_APP_PSW
DirectLineSecret=YOUR_DIRECT_LINE_SECRET
```
- Restart your bot in local or redeploy your bot with new config.
### [Setup web part](./webpart/README.md)
- Go to `./webpart`
- Install modules
```bash
npm install
```
- Start web part
```bash
gulp serve
```
Now web part is running locally in https://localhost:4321.
- (Opt.) Publish the bot: follow the steps outlined in the [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli?view=azure-bot-service-4.0&tabs=csharp) article.
- (Opt.) Config CORS \
[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) must be set on bot app service to enable SharePoint client to get resource from bot service. Follow these steps to add your workbench to bot app service CORS configuration:
1. Go to your azure portal
1. Navigate to your bot app service, search for CORS settings
1. Add https://localhost:4321 and https://<YOUR_SITE>.sharepoint.com to CORS origins
- Config bot endpoint \
Add the web part, set bot endpoint to https://localhost:4321 (local) or https://YOUR_BOT.azurewebsites.net (remote), refresh this page, then you can successfully connect bot with SharePoint.
## Features
**Web Chat integration with security consideration**
The SharePoint component will integrate bot with react Web Chat component.
```tsx
public render(): React.ReactElement<IBotFrameworkChatv4Props> {
return (
<div className={styles.botFrameworkChatv4} style={{ height: 700 }}>
<ReactWebChat directLine={directLine} styleOptions={styleSetOptions} />
</div>
);
}
```
Inside Web Chat, direct line will be used to connect to Bot Service. On Bot Service side, one more endpoint `directline/token` will be added besides `api/messages`, which will accept userId passed from client side and return back direct line token.
For production, this endpoint should also verify if the incoming request is authorized.
```tsx
server.post('/directline/token', (req, res) => {
const secret = settings.parsed.DirectLineSecret;
const authorization = `Bearer ${secret}`;
const userId = 'dl_' + GetUserId((req.body || {}).user);
const options = {
method: 'POST',
uri: 'https://directline.botframework.com/v3/directline/tokens/generate',
body: JSON.stringify({ user: { id: userId} }),
headers: { 'Authorization': authorization, 'Content-Type': 'application/json'}
};+
request.post(options, (error, response, body) => {
if (!error && response.statusCode < 300) {
res.status(response.statusCode);
if (body) { res.send(JSON.parse(body)) }
} else {
res.status(500);
res.send('Call to retrieve token from DirectLine failed');
}
res.end();
});
});
```
On web part side, it will fetch direct line token from bot service side with SharePoint `userId` then build up the web chat component. The `UserId` should be encrypted so it won't be easy to get other user's token by bot endpoint.
```tsx
useEffect(() => {
const userId = props.context.pageContext.user.loginName;
generateToken(props.botEndpoint, md5(userId)).then((token: string) => {
if (token) {
setDirectLine(createDirectLine({ token }));
}
});
}, []);
```
And enable "Enhanced authentication options" can help detect client user Id change then reject the change:
![bot framework client web part](./assets/EnhancedAuth.png)\
For how to find this option, please refer [connect to direct line](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-channel-connect-directline?view=azure-bot-service-4.0).
## Further reading
- [SharePoint Web Parts Development Basics](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/overview-client-side-web-parts)
- [Bot Framework Documentation](https://docs.botframework.com)
- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0)
- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0)
- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0)
- [Restify](https://www.npmjs.com/package/restify)
- [Using WebChat with Azure Bot Services Authentication](https://blog.botframework.com/2018/09/01/using-webchat-with-azure-bot-services-authentication/)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-bot-framework-secure" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

View File

@ -0,0 +1,35 @@
# 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
.vscode
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts
packages/

View File

@ -0,0 +1,108 @@
# Echo bot
## Summary
This bot has been created using [Bot Framework](https://dev.botframework.com). It shows how to create a simple bot that accepts input from the user and echoes it back.
## Prerequisites
- [Node.js](https://nodejs.org) version 10.14.1 or higher
```bash
# determine node version
node --version
```
## To try this sample locally
- Clone the repository
```bash
git clone [Placeholder]
```
- In a console, navigate to [Placeholder]
```bash
cd [Placeholder]
```
- Install modules
```bash
npm install
```
- Start the bot
```bash
npm start
```
## Testing the bot using Bot Framework Emulator
[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel.
- Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)
### Connect to the bot using Bot Framework Emulator
- Launch Bot Framework Emulator
- File -> Open Bot
- Enter a Bot URL of `http://localhost:3978/api/messages`
## (Opt.) Deploy the bot to Azure
To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions.
## (Opt.) Testing Direct Line token generation
- [Connect to Direct Line](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-channel-connect-directline?view=azure-bot-service-4.0)
- Add `DirectLineSecret` to `.env`
```bash
DirectLineSecret=YOUR_DIRECT_LINE_SECRET
```
- Start the bot
```bash
npm start
```
- Open [PostMan](https://www.postman.com/) and setup a post request to http://localhost:3978/directline/token
with the following json request body:
```json
{
"user": "USER_ID"
}
```
Then you can see the Direct Line token generated with `YOUR_DIRECT_LINE_SECRET` and `USER_ID`:
```json
{
"conversationId": "XXXXX",
"token": "XXXXX",
"expires_in": 3600
}
```
## Further reading
- [Bot Framework Documentation](https://docs.botframework.com)
- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0)
- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0)
- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0)
- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest)
- [Azure Portal](https://portal.azure.com)
- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/)
- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0)
- [TypeScript](https://www.typescriptlang.org)
- [Restify](https://www.npmjs.com/package/restify)
- [dotenv](https://www.npmjs.com/package/dotenv)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-bot-framework-secure/bot" />

View File

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// DO NOT MODIFY THIS CODE
// This script is run as part of the Post Deploy step when
// deploying the bot to Azure. It ensures the Azure Web App
// is configured correctly to host a TypeScript authored bot.
const fs = require('fs');
const path = require('path');
const replace = require('replace');
const WEB_CONFIG_FILE = './web.config';
if (fs.existsSync(path.resolve(WEB_CONFIG_FILE))) {
replace({
regex: "url=\"index.js\"",
replacement: "url=\"lib/index.js\"",
paths: ['./web.config'],
recursive: false,
silent: true,
})
}

View File

@ -0,0 +1,42 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"groupLocation": {
"value": ""
},
"groupName": {
"value": ""
},
"appId": {
"value": ""
},
"appSecret": {
"value": ""
},
"botId": {
"value": ""
},
"botSku": {
"value": ""
},
"newAppServicePlanName": {
"value": ""
},
"newAppServicePlanSku": {
"value": {
"name": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 1
}
},
"newAppServicePlanLocation": {
"value": ""
},
"newWebAppName": {
"value": ""
}
}
}

View File

@ -0,0 +1,39 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appId": {
"value": ""
},
"appSecret": {
"value": ""
},
"botId": {
"value": ""
},
"botSku": {
"value": ""
},
"newAppServicePlanName": {
"value": ""
},
"newAppServicePlanSku": {
"value": {
"name": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 1
}
},
"appServicePlanLocation": {
"value": ""
},
"existingAppServicePlan": {
"value": ""
},
"newWebAppName": {
"value": ""
}
}
}

View File

@ -0,0 +1,183 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"groupLocation": {
"type": "string",
"metadata": {
"description": "Specifies the location of the Resource Group."
}
},
"groupName": {
"type": "string",
"metadata": {
"description": "Specifies the name of the Resource Group."
}
},
"appId": {
"type": "string",
"metadata": {
"description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings."
}
},
"appSecret": {
"type": "string",
"metadata": {
"description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings."
}
},
"botId": {
"type": "string",
"metadata": {
"description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable."
}
},
"botSku": {
"type": "string",
"metadata": {
"description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1."
}
},
"newAppServicePlanName": {
"type": "string",
"metadata": {
"description": "The name of the App Service Plan."
}
},
"newAppServicePlanSku": {
"type": "object",
"defaultValue": {
"name": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 1
},
"metadata": {
"description": "The SKU of the App Service Plan. Defaults to Standard values."
}
},
"newAppServicePlanLocation": {
"type": "string",
"metadata": {
"description": "The location of the App Service Plan. Defaults to \"westus\"."
}
},
"newWebAppName": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"."
}
}
},
"variables": {
"appServicePlanName": "[parameters('newAppServicePlanName')]",
"resourcesLocation": "[parameters('newAppServicePlanLocation')]",
"webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]",
"siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]",
"botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]",
"resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]"
},
"resources": [
{
"name": "[parameters('groupName')]",
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"location": "[parameters('groupLocation')]",
"properties": {}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"name": "storageDeployment",
"resourceGroup": "[parameters('groupName')]",
"dependsOn": [
"[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"comments": "Create a new App Service Plan",
"type": "Microsoft.Web/serverfarms",
"name": "[variables('appServicePlanName')]",
"apiVersion": "2018-02-01",
"location": "[variables('resourcesLocation')]",
"sku": "[parameters('newAppServicePlanSku')]",
"properties": {
"name": "[variables('appServicePlanName')]"
}
},
{
"comments": "Create a Web App using the new App Service Plan",
"type": "Microsoft.Web/sites",
"apiVersion": "2015-08-01",
"location": "[variables('resourcesLocation')]",
"kind": "app",
"dependsOn": [
"[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]"
],
"name": "[variables('webAppName')]",
"properties": {
"name": "[variables('webAppName')]",
"serverFarmId": "[variables('appServicePlanName')]",
"siteConfig": {
"appSettings": [
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "10.14.1"
},
{
"name": "MicrosoftAppId",
"value": "[parameters('appId')]"
},
{
"name": "MicrosoftAppPassword",
"value": "[parameters('appSecret')]"
}
],
"cors": {
"allowedOrigins": [
"https://botservice.hosting.portal.azure.net",
"https://hosting.onecloud.azure-test.net/"
]
}
}
}
},
{
"apiVersion": "2017-12-01",
"type": "Microsoft.BotService/botServices",
"name": "[parameters('botId')]",
"location": "global",
"kind": "bot",
"sku": {
"name": "[parameters('botSku')]"
},
"properties": {
"name": "[parameters('botId')]",
"displayName": "[parameters('botId')]",
"endpoint": "[variables('botEndpoint')]",
"msaAppId": "[parameters('appId')]",
"developerAppInsightsApplicationId": null,
"developerAppInsightKey": null,
"publishingCredentials": null,
"storageResourceId": null
},
"dependsOn": [
"[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]"
]
}
],
"outputs": {}
}
}
}
]
}

View File

@ -0,0 +1,154 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appId": {
"type": "string",
"metadata": {
"description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings."
}
},
"appSecret": {
"type": "string",
"metadata": {
"description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"."
}
},
"botId": {
"type": "string",
"metadata": {
"description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable."
}
},
"botSku": {
"defaultValue": "F0",
"type": "string",
"metadata": {
"description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1."
}
},
"newAppServicePlanName": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "The name of the new App Service Plan."
}
},
"newAppServicePlanSku": {
"type": "object",
"defaultValue": {
"name": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 1
},
"metadata": {
"description": "The SKU of the App Service Plan. Defaults to Standard values."
}
},
"appServicePlanLocation": {
"type": "string",
"metadata": {
"description": "The location of the App Service Plan."
}
},
"existingAppServicePlan": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "Name of the existing App Service Plan used to create the Web App for the bot."
}
},
"newWebAppName": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"."
}
}
},
"variables": {
"defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]",
"useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]",
"servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]",
"resourcesLocation": "[parameters('appServicePlanLocation')]",
"webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]",
"siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]",
"botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]"
},
"resources": [
{
"comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.",
"type": "Microsoft.Web/serverfarms",
"condition": "[not(variables('useExistingAppServicePlan'))]",
"name": "[variables('servicePlanName')]",
"apiVersion": "2018-02-01",
"location": "[variables('resourcesLocation')]",
"sku": "[parameters('newAppServicePlanSku')]",
"properties": {
"name": "[variables('servicePlanName')]"
}
},
{
"comments": "Create a Web App using an App Service Plan",
"type": "Microsoft.Web/sites",
"apiVersion": "2015-08-01",
"location": "[variables('resourcesLocation')]",
"kind": "app",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]"
],
"name": "[variables('webAppName')]",
"properties": {
"name": "[variables('webAppName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]",
"siteConfig": {
"appSettings": [
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "10.14.1"
},
{
"name": "MicrosoftAppId",
"value": "[parameters('appId')]"
},
{
"name": "MicrosoftAppPassword",
"value": "[parameters('appSecret')]"
}
],
"cors": {
"allowedOrigins": [
"https://botservice.hosting.portal.azure.net",
"https://hosting.onecloud.azure-test.net/"
]
}
}
}
},
{
"apiVersion": "2017-12-01",
"type": "Microsoft.BotService/botServices",
"name": "[parameters('botId')]",
"location": "global",
"kind": "bot",
"sku": {
"name": "[parameters('botSku')]"
},
"properties": {
"name": "[parameters('botId')]",
"displayName": "[parameters('botId')]",
"endpoint": "[variables('botEndpoint')]",
"msaAppId": "[parameters('appId')]",
"developerAppInsightsApplicationId": null,
"developerAppInsightKey": null,
"publishingCredentials": null,
"storageResourceId": null
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', variables('webAppName'))]"
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
{
"name": "echobot",
"version": "1.0.0",
"description": "Bot Builder v4 echo bot sample",
"author": "Microsoft",
"license": "MIT",
"main": "./lib/index.js",
"scripts": {
"build": "tsc --build",
"lint": "tslint -c tslint.json 'src/**/*.ts'",
"lintfix": "tslint -c tslint.json 'src/**/*.ts' --fix",
"postinstall": "npm run build && node ./deploymentScripts/webConfigPrep.js",
"start": "tsc --build && node ./lib/index.js",
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "nodemon --watch ./src -e ts --exec \"npm run start\""
},
"repository": {
"type": "git",
"url": "https://github.com"
},
"dependencies": {
"botbuilder": "~4.9.1",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"replace": "~1.1.1",
"restify": "~8.5.1",
"restify-cors-middleware": "^1.1.1"
},
"devDependencies": {
"@types/dotenv": "6.1.1",
"@types/restify": "8.4.2",
"@types/restify-cors-middleware": "^1.0.1",
"nodemon": "~2.0.4",
"tslint": "~6.1.2",
"typescript": "~3.9.2"
}
}

View File

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { ActivityHandler, MessageFactory } from 'botbuilder';
export class EchoBot extends ActivityHandler {
constructor() {
super();
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
this.onMessage(async (context, next) => {
const replyText = `Echo: ${ context.activity.text }`;
await context.sendActivity(MessageFactory.text(replyText, replyText));
// By calling next() you ensure that the next BotHandler is run.
await next();
});
this.onMembersAdded(async (context, next) => {
const membersAdded = context.activity.membersAdded;
const welcomeText = 'Hello and welcome!';
for (const member of membersAdded) {
if (member.id !== context.activity.recipient.id) {
await context.sendActivity(MessageFactory.text(welcomeText, welcomeText));
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
}

View File

@ -0,0 +1,131 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as path from 'path';
import { config } from 'dotenv';
const ENV_FILE = path.join(__dirname, '..', '.env');
const settings = config({ path: ENV_FILE });
import * as restify from 'restify';
import * as request from 'request';
import * as express from 'express';
import * as corsMiddleware from 'restify-cors-middleware';
// Import required bot services.
// See https://aka.ms/bot-services to learn more about the different parts of a bot.
import { BotFrameworkAdapter } from 'botbuilder';
// This bot's main dialog.
import { EchoBot } from './bot';
import { Guid } from './util';
// Add '*' origins for test only. You should update with your own origins in production code.
const cors = corsMiddleware({
origins: ['*'],
allowHeaders: ['*'],
exposeHeaders: ['*']
});
// Create HTTP server.
const server = restify.createServer();
server.pre(cors.preflight);
server.use(cors.actual);
server.use(express.json());
server.listen(process.env.port || process.env.PORT || 3978, () => {
console.log(`\n${server.name} listening to ${server.url}`);
console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator');
console.log('\nTo talk to your bot, open the emulator select "Open Bot"');
});
// Create adapter.
// See https://aka.ms/about-bot-adapter to learn more about adapters.
const adapter = new BotFrameworkAdapter({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword
});
// Catch-all for errors.
const onTurnErrorHandler = async (context, error) => {
// This check writes out errors to console log .vs. app insights.
// NOTE: In production environment, you should consider logging this to Azure
// application insights.
console.error(`\n [onTurnError] unhandled error: ${ error }`);
// Send a trace activity, which will be displayed in Bot Framework Emulator
await context.sendTraceActivity(
'OnTurnError Trace',
`${ error }`,
'https://www.botframework.com/schemas/error',
'TurnError'
);
// Send a message to the user
await context.sendActivity('The bot encountered an error or bug.');
await context.sendActivity('To continue to run this bot, please fix the bot source code.');
};
// Set the onTurnError for the singleton BotFrameworkAdapter.
adapter.onTurnError = onTurnErrorHandler;
// Create the main dialog.
const myBot = new EchoBot();
// Listen for incoming requests.
server.post('/api/messages', (req, res) => {
adapter.processActivity(req, res, async (context) => {
// Route to main dialog.
await myBot.run(context);
});
});
const GetUserId = (userName : string): string => {
const userId = userName? userName : Guid.newGuid();
return userId;
};
// Listen for incoming token request.
server.post('/directline/token', (req, res) => {
const secret = settings.parsed.DirectLineSecret;
const authorization = `Bearer ${secret}`;
const userId = 'dl_' + GetUserId((req.body || {}).user);
const options = {
method: 'POST',
uri: 'https://directline.botframework.com/v3/directline/tokens/generate',
body: JSON.stringify({ user: { id: userId} }),
headers: { 'Authorization': authorization, 'Content-Type': 'application/json'}
};
request.post(options, (error, response, body) => {
if (!error && response.statusCode < 300) {
res.status(response.statusCode);
if (body) { res.send(JSON.parse(body)) }
} else {
res.status(500);
res.send('Call to retrieve token from DirectLine failed');
}
res.end();
});
});
// Listen for Upgrade requests for Streaming.
server.on('upgrade', (req, socket, head) => {
// Create an adapter scoped to this WebSocket connection to allow storing session data.
const streamingAdapter = new BotFrameworkAdapter({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword
});
// Set onTurnError for the BotFrameworkAdapter created for each connection.
streamingAdapter.onTurnError = onTurnErrorHandler;
streamingAdapter.useWebSocket(req, socket, head, async (context) => {
// After connecting via WebSocket, run this logic for every request sent over
// the WebSocket connection.
await myBot.run(context);
});
});

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
export class Guid {
static newGuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0,
v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}

View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"declaration": true,
"target": "es2016",
"module": "commonjs",
"outDir": "./lib",
"rootDir": "./src",
"sourceMap": true,
"incremental": true,
"tsBuildInfoFile": "./lib/.tsbuildinfo"
}
}

View File

@ -0,0 +1,19 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"interface-name" : [true, "never-prefix"],
"max-line-length": [false],
"no-console": [false, "log", "error"],
"no-var-requires": false,
"quotemark": [true, "single"],
"one-variable-per-declaration": false,
"curly": [true, "ignore-same-line"],
"trailing-comma": [true, {"multiline": "never", "singleline": "never"}],
"no-bitwise": [false]
},
"rulesDirectory": []
}

View 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

View File

@ -0,0 +1 @@
* text=auto

View 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

View File

@ -0,0 +1,14 @@
# Folders
.vscode
coverage
node_modules
sharepoint
src
temp
# Files
*.csproj
.git*
.yo-rc.json
gulpfile.js
tsconfig.json

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": false,
"environment": "spo",
"version": "1.10.0",
"libraryName": "react-bot-framework",
"libraryId": "1e3d14a8-d863-4a3d-b83a-dbd188d52015",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,55 @@
# Microsoft Bot Framework Web Chat
## Summary
A web part sample that uses the [botframework-webchat module](https://www.npmjs.com/package/botframework-webchat) to create a React component to render the Bot Framework v4 webchat component. This web part is able to render Text and rich attachments (Images, Cards, Adaptive Cards, ...) and has settings for branding purposes.
## Used SharePoint Framework Version
![SPFx 1.10.0](https://img.shields.io/badge/drop-1.10.0-green.svg)
## Applies to
* [SharePoint Framework Web Parts](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/overview-client-side-web-parts)
* [Office 365 developer tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
* [Microsoft Bot Framework](http://dev.botframework.com)
## Prerequisites
> You need to have this [bot](../bot/) created and registered using the Microsoft Bot Framework and registered to use the **Direct Line Channel**, which will give you the token generation endpoint needed when adding this web part to the page. For more information on creating a bot and registering the channel you can see the official web site at [dev.botframework.com](http://dev.botframework.com).
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
- Clone this repository
- in the command line run:
```bash
npm install
gulp serve
```
- Set the bot service URI as Bot EndPoint.
- Config bot endpoint \
Add the web part, set the bot endpoint to `https://YOUR_BOT.azurewebsites.net`, refresh the page, then you can successfully connect the bot with SharePoint.
## Deploy
If you want to deploy the bot follow the steps in the [Host your client-side web part from Microsoft 365 CDN](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/hosting-webpart-from-office-365-cdn) article
## Features
This Web Part illustrates the following concepts on top of the SharePoint Framework:
- Connecting and communicating with a bot built on the Microsoft Bot Framework using the Direct Line Channel
- Validating Property Pane Settings
- Office UI Fabric
- React
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-bot-framework-secure/webpart" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -0,0 +1,18 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"bot-framework-chatv-4-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/botFrameworkChatv4/BotFrameworkChatv4WebPart.js",
"manifest": "./src/webparts/botFrameworkChatv4/BotFrameworkChatv4WebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"BotFrameworkChatv4WebPartStrings": "lib/webparts/botFrameworkChatv4/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View 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": "react-bot-framework",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-bot-framework-client-side-solution",
"id": "1e3d14a8-d863-4a3d-b83a-dbd188d52015",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/react-bot-framework.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,7 @@
'use strict';
const gulp = require('gulp');
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(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
{
"name": "sp-bot-chat-webpart",
"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",
"lint": "tslint -c tslint.json 'src/**/*.{ts,tsx}'",
"lintfix": "tslint -c tslint.json 'src/**/*.{ts,tsx}' --fix"
},
"dependencies": {
"@microsoft/sp-core-library": "1.10.0",
"@microsoft/sp-lodash-subset": "1.10.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
"@microsoft/sp-property-pane": "1.10.0",
"@microsoft/sp-webpart-base": "1.10.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"blueimp-md5": "^2.16.0",
"botframework-directlinejs": "^0.11.4",
"botframework-webchat": "^4.5.2",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5",
"react-dom": "16.8.5",
"request": "^2.88.0",
"swagger-client": "^2.1.23"
},
"resolutions": {
"@types/react": "16.8.8"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.10.0",
"@microsoft/sp-module-interfaces": "1.10.0",
"@microsoft/sp-webpart-workbench": "1.10.0",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",
"gulp": "~3.9.1"
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,26 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "7aa6d70f-2a9f-4e92-ad01-a45c582e82be",
"alias": "BotFrameworkChatv4WebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "BotFrameworkChatv4" },
"description": { "default": "Use the botframework-webchat v4 React component to render the webchat with the new v4 UI" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "BotFrameworkChatv4"
}
}]
}

Some files were not shown because too many files have changed in this diff Show More