Merge from dev to master

Included PRs:

#1023
#1032
This commit is contained in:
Laura Kokkarinen 2019-12-12 09:39:54 +02:00 committed by GitHub
commit 8f71c5bf17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 20392 additions and 5 deletions

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

32
samples/react-recaptcha/.gitignore vendored Normal file
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,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.8.2",
"libraryName": "react-recaptcha",
"libraryId": "e96b3b94-3ccb-4e61-be34-e7ef7d952cbd",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,77 @@
# SPFx Google reCaptcha Sample
## Summary
This is sample webpart which showcase how to implement Google reCaptcha v2 in SPFx. CAPTCHA is used to prevent bots from automatically submitting forms with SPAM or other unwanted content. If we are building a custom input form to get feedback, newsletter subscription or contact us form using SPFx webpart. We might have to implement SPAM protection using some CAPTCHA resolving technique. This sample can come in handy to extend it for your
business requirement if you need to implement CAPTCHA in SPFx webpart.
* Please refer this [link](https://www.c-sharpcorner.com/article/google-recaptcha-in-sharepoint-framework-webpartspfx/) to know 'How to build this from Scratch'
![Webpart in action](screens/WebpartInAction.gif?raw=true "Webpart in action")
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
## Applies to
* [SharePoint Framework](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
* [Office 365 tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
## Prerequisites
We would need to register our site which wants to use reCaptcha at Google. Follow below steps to get site key.
* Browse this [link](https://www.google.com/recaptcha/admin).
* Login with valid google account.
* Provide a valid site label name.
* Select reCAPTCHA v2, Select "I'm not a robot"
* Add domain name, if you are using local workbench enter localhost.
* For using it in context of SharePoint, enter your tenant url https://yourorg.sharepoint.com
* Accept terms and condition
* Submit
![Google recaptcha registration](screens/1.png?raw=true "Google recaptcha registration")
On sucessfull submission, we get site key and secret key, copy site key somewhere we would be using it later.
![Google recaptcha registration](screens/2.png?raw=true "Google recaptcha registration")
## Solution
Solution|Author(s)
--------|---------
react-recaptcha | Siddharth Vaghasia([siddh_me](https://twitter.com/siddh_me/))
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|Oct 10, 2019|Implemented few changes
1.0.0|Oct 08, 2019|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
* in the command line run:
* `npm install`
* `gulp serve`
## Features
This Web Part illustrates the following concepts on top of the SharePoint Framework:
* Using react framework in SPFx webpart
* Using [PnP Placeholder control](https://sharepoint.github.io/sp-dev-fx-controls-react/controls/Placeholder/) to configure webpart.
* Using [react-google-recaptcha](https://github.com/dozoisch/react-google-recaptcha) npm package in SPFx webpart
* Validate if captcha is resolved before submiting data.
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-recaptcha" />

View File

@ -0,0 +1,19 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"recaptcha-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/recaptcha/RecaptchaWebPart.js",
"manifest": "./src/webparts/recaptcha/RecaptchaWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"RecaptchaWebPartStrings": "lib/webparts/recaptcha/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/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-recaptcha",
"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-recaptcha-client-side-solution",
"id": "e96b3b94-3ccb-4e61-be34-e7ef7d952cbd",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/react-recaptcha.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 -->"
}

7
samples/react-recaptcha/gulpfile.js vendored Normal file
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);

19778
samples/react-recaptcha/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
{
"name": "react-recaptcha",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.9.1",
"@microsoft/sp-lodash-subset": "1.9.1",
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
"@microsoft/sp-property-pane": "1.9.1",
"@microsoft/sp-webpart-base": "1.9.1",
"@pnp/spfx-controls-react": "1.14.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/react-google-recaptcha": "^1.1.0",
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5",
"react-async-script": "^1.1.1",
"react-dom": "16.8.5",
"react-google-recaptcha": "^2.0.1"
},
"resolutions": {
"@types/react": "16.8.8"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
"@microsoft/sp-build-web": "1.9.1",
"@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-tslint-rules": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",
"gulp": "~3.9.1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 KiB

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,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "200835ce-2963-400e-ae18-f556484c5fba",
"alias": "RecaptchaWebPart",
"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": "recaptcha" },
"description": { "default": "This is sample webpart to showcase usage of reCaptcha in spfx webpart" },
"officeFabricIconFontName": "Page",
"properties": {
"sitekey": ""
}
}]
}

View File

@ -0,0 +1,64 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import * as strings from 'RecaptchaWebPartStrings';
import Recaptcha from './components/Recaptcha';
import { IRecaptchaProps } from './components/IRecaptchaProps';
import { WebPartContext } from '@microsoft/sp-webpart-base';
export interface IRecaptchaWebPartProps {
sitekey: string;
}
export default class RecaptchaWebPart extends BaseClientSideWebPart<IRecaptchaWebPartProps> {
public render(): void {
const element: React.ReactElement<IRecaptchaProps > = React.createElement(
Recaptcha,
{
sitekey: this.properties.sitekey,
displayMode: this.displayMode,
context: this.context
}
);
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('sitekey', {
label: strings.SiteKeyFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,8 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
export interface IRecaptchaProps {
sitekey: string;
displayMode: DisplayMode;
context: WebPartContext;
}

View File

@ -0,0 +1,74 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.recaptcha {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.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;
// 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;
}
}
}

View File

@ -0,0 +1,94 @@
import * as React from 'react';
import styles from './Recaptcha.module.scss';
import { IRecaptchaProps } from './IRecaptchaProps';
import { escape } from '@microsoft/sp-lodash-subset';
import ReCAPTCHA from 'react-google-recaptcha';
import * as ReactDom from 'react-dom';
import { TextField, MaskedTextField,PrimaryButton,MessageBar, MessageBarType } from 'office-ui-fabric-react';
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder"
import { DisplayMode } from '@microsoft/sp-core-library';
import * as strings from 'RecaptchaWebPartStrings';
export default class Recaptcha extends React.Component<IRecaptchaProps, {}> {
public captcha:any;
public _messageContainer:HTMLElement = undefined;
public render(): React.ReactElement<IRecaptchaProps> {
return (
<div className={ styles.recaptcha }>
{/* Show Placeholder control, when description web part property is not set */}
{this.props.sitekey == "" &&
<Placeholder iconName='Edit'
iconText='Configure your web part'
description='Please configure the web part.'
buttonLabel='Configure'
hideButton={this.props.displayMode === DisplayMode.Read}
onConfigure={() => this._onConfigure()} />
}
{/* Show description web part property, when set */}
{this.props.sitekey &&
<div>
<p>
<TextField label="Enter your name" styles={{ fieldGroup: { width: 300 } }} />
</p>
<ReCAPTCHA ref={ (el) => { this.captcha = el; } }
sitekey={escape(this.props.sitekey)}
onChange={this.onChange} />
<p>
<p ref={(elm) => { this._messageContainer = elm; }}>
</p>
</p>
<PrimaryButton text={strings.SubmitButtonLabel} onClick={() => this.buttonClicked()} />
</div>
}
</div>
);
}
private _onConfigure() {
// Context of the web part
this.props.context.propertyPane.open();
}
public buttonClicked(): void {
if(this.captcha.getValue())
{
ReactDom.unmountComponentAtNode(this._messageContainer);
const element2 = React.createElement(
MessageBar,
{
messageBarType:MessageBarType.success,
isMultiline:false,
dismissButtonAriaLabel:"Close"
},
"You data has been saved sucessfully"
);
//const camContainer = document.getElementById("camContainer")
ReactDom.render(element2, this._messageContainer);
}
else{
const element2 = React.createElement(
MessageBar,
{
messageBarType:MessageBarType.error,
isMultiline:false,
dismissButtonAriaLabel:"Close"
},
"Please select captcha."
);
//const camContainer = document.getElementById("camContainer")
ReactDom.render(element2, this._messageContainer);
}
}
public onChange(value){
console.log("Captcha value:", value);
}
}

View File

@ -0,0 +1,8 @@
define([], function() {
return {
"PropertyPaneDescription": "Please enter Site key to use reCaptcha",
"BasicGroupName": "Basic",
"SiteKeyFieldLabel": "Site Key",
"SubmitButtonLabel" : "Submit"
}
});

View File

@ -0,0 +1,11 @@
declare interface IRecaptchaWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
SiteKeyFieldLabel: string;
SubmitButtonLabel:string;
}
declare module 'RecaptchaWebPartStrings' {
const strings: IRecaptchaWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,38 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/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"
],
"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

@ -7404,7 +7404,7 @@
"ansi-colors": "1.1.0",
"connect": "3.6.6",
"connect-livereload": "0.5.4",
"event-stream": "3.3.6",
"event-stream": "3.3.5",
"fancy-log": "1.3.2",
"send": "0.13.2",
"serve-index": "1.9.1",
@ -7428,13 +7428,12 @@
"dev": true
},
"event-stream": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz",
"integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==",
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.5.tgz",
"integrity": "sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g==",
"dev": true,
"requires": {
"duplexer": "0.1.1",
"flatmap-stream": "0.1.0",
"from": "0.1.7",
"map-stream": "0.0.7",
"pause-stream": "0.0.11",