react-feedback commit.submit feedback on sitepages
This commit is contained in:
commit
f9606eeed8
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"msjsdiag.debugger-for-chrome"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Install Chrome Debugger Extension for Visual Studio Code to debug your components with the
|
||||||
|
* Chrome browser: https://aka.ms/spfx-debugger-extensions
|
||||||
|
*/
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [{
|
||||||
|
"name": "Local workbench",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "https://localhost:4321/temp/workbench.html",
|
||||||
|
"webRoot": "${workspaceRoot}",
|
||||||
|
"sourceMaps": true,
|
||||||
|
"sourceMapPathOverrides": {
|
||||||
|
"webpack:///.././src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||||
|
},
|
||||||
|
"runtimeArgs": [
|
||||||
|
"--remote-debugging-port=9222"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hosted workbench",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "https://enter-your-SharePoint-site/_layouts/workbench.aspx",
|
||||||
|
"webRoot": "${workspaceRoot}",
|
||||||
|
"sourceMaps": true,
|
||||||
|
"sourceMapPathOverrides": {
|
||||||
|
"webpack:///.././src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||||
|
},
|
||||||
|
"runtimeArgs": [
|
||||||
|
"--remote-debugging-port=9222",
|
||||||
|
"-incognito"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Place your settings in this file to overwrite default and user settings.
|
||||||
|
{
|
||||||
|
// Configure glob patterns for excluding files and folders in the file explorer.
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.DS_Store": true,
|
||||||
|
"**/bower_components": true,
|
||||||
|
"**/coverage": true,
|
||||||
|
"**/lib-amd": true,
|
||||||
|
"src/**/*.scss.ts": true
|
||||||
|
},
|
||||||
|
"typescript.tsdk": ".\\node_modules\\typescript\\lib"
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"isCreatingSolution": true,
|
||||||
|
"environment": "spo",
|
||||||
|
"version": "1.11.0",
|
||||||
|
"libraryName": "feedback-webpart",
|
||||||
|
"libraryId": "d5e255e1-7071-49a8-b50d-b06c80e4ac02",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"componentType": "webpart"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Feedback
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This is an application that supports Feedback through a web part that can be used directly on a Modern Sharepoint Site page. This webpart can be added to any site page or article. This allows users to send categorized feedback via email to users in the "Feedback Owners" group.
|
||||||
|
|
||||||
|
![Feedback](./assets/feedbackwebpart.gif)
|
||||||
|
|
||||||
|
## Used SharePoint Framework Version
|
||||||
|
|
||||||
|
![1.11.0](https://img.shields.io/badge/version-1.11.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)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
* Office 365 subscription with SharePoint Online
|
||||||
|
* SharePoint Framework [development environment](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment) set up
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react-feedback | Perry Kankam
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0|December 15, 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
|
||||||
|
|
||||||
|
*To really get the full experience go to the workbench on a SharePoint Site [Your site url]/_layouts/15/workbench.aspx and that's where the magic will happen but this requires that you deploy and activate features to provision the required SharePoint assets*
|
||||||
|
|
||||||
|
* Clone this repository
|
||||||
|
* in the command line run:
|
||||||
|
* `npm install`
|
||||||
|
* `gulp serve`
|
||||||
|
* Add "Feedback Owners" Sharepoint group. This is where you'll add all users who should receive this feedback.
|
||||||
|
* Run one of the following custom commands to clean, build, bundle and package the solution.
|
||||||
|
* If you want to be able to debug using your local code using gulp serve
|
||||||
|
`gulp package`
|
||||||
|
* Navigate to the output `feedback-webpart.sppkg` (found in the `/sharepoint/solution` folder)
|
||||||
|
* Upload it to an application catalog (either a tenant or site collection one)
|
||||||
|
* In your site collection go to **Site Contents** and click **New** > **App**
|
||||||
|
* Find and add the **Feedback Application** App
|
||||||
|
* wait for it to finish installing and activating features on the **Site Contents** page
|
||||||
|
* Go to a site page like home, edit the page and find and add the **Feedback** web part
|
||||||
|
* If you deployed a shippable (SharePoint Online) version you don't need to do anything else
|
||||||
|
* If you deployed a debug (http://localhost:4321) version you'll need to ensure gulp serve is running
|
||||||
|
|
||||||
|
## Features
|
||||||
|
This sample illustrates the following concepts:
|
||||||
|
- Used [@pnp/polyfill-ie11](https://pnp.github.io/pnpjs/concepts/polyfill/)
|
||||||
|
- Used [PnP](https://pnp.github.io/pnpjs/) for communication with SharePoint.
|
||||||
|
- Used [@pnp/logging](https://pnp.github.io/pnpjs/logging/)
|
Binary file not shown.
After Width: | Height: | Size: 962 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"feedback-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/feedback/FeedbackWebPart.js",
|
||||||
|
"manifest": "./src/webparts/feedback/FeedbackWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"FeedbackWebPartStrings": "lib/webparts/feedback/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -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": "feedback-webpart",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "feedback-webpart-client-side-solution",
|
||||||
|
"id": "d5e255e1-7071-49a8-b50d-b06c80e4ac02",
|
||||||
|
"version": "1.0.0.0",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"developer": {
|
||||||
|
"name": "",
|
||||||
|
"websiteUrl": "",
|
||||||
|
"privacyUrl": "",
|
||||||
|
"termsOfUseUrl": "",
|
||||||
|
"mpnId": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/feedback-webpart.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
'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);
|
||||||
|
|
||||||
|
var runSequence = require('run-sequence');
|
||||||
|
gulp.task('package', function (cb) {
|
||||||
|
runSequence('clean', 'build', 'bundle', 'package-solution', cb);
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"name": "feedback-webpart",
|
||||||
|
"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": {
|
||||||
|
"@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/polyfill-ie11": "^2.0.2",
|
||||||
|
"@pnp/sp": "^2.0.13",
|
||||||
|
"run-sequence": "^2.2.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface IArticleInfo {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { HttpRequestError } from '@pnp/odata';
|
||||||
|
import { LogHelper } from "../utilities";
|
||||||
|
|
||||||
|
|
||||||
|
export class BaseService {
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleHttpError(methodName: string, error: HttpRequestError): void {
|
||||||
|
this.logError(methodName, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public logError(methodName: string, error: Error) {
|
||||||
|
LogHelper.exception(this.constructor.name, methodName, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public logPnpError(methodName: string, error: HttpRequestError | any): string {
|
||||||
|
let msg: string;
|
||||||
|
if (error instanceof HttpRequestError) {
|
||||||
|
if (error.message) {
|
||||||
|
msg = error.message;
|
||||||
|
LogHelper.error(this.constructor.name, methodName, msg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogHelper.exception(this.constructor.name, methodName, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (error.data != null && error.data.responseBody && error.data.responseBody.error && error.data.responseBody.error.message) {
|
||||||
|
// for email exceptions they weren't coming in as "instanceof HttpRequestError"
|
||||||
|
msg = error.data.responseBody.error.message.value;
|
||||||
|
LogHelper.error(this.constructor.name, methodName, msg);
|
||||||
|
}
|
||||||
|
else if (error instanceof Error) {
|
||||||
|
if (error.message.indexOf('[412] Precondition Failed') !== -1) {
|
||||||
|
msg = 'Save Conflict. Your changes conflict with those made concurrently by another user. If you want your changes to be applied, resubmit your changes.';
|
||||||
|
LogHelper.error(this.constructor.name, methodName, msg);
|
||||||
|
}
|
||||||
|
else if (error.message !== 'Unexpected token < in JSON at position 0') {
|
||||||
|
// 'Unexpected token < in JSON at position 0' will be thrown if XML file is read; this was issue in MDF project
|
||||||
|
msg = error.message;
|
||||||
|
LogHelper.error(this.constructor.name, methodName, msg);
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { sp } from '@pnp/sp/presets/all';
|
||||||
|
import { IPrincipalInfo } from "@pnp/sp";
|
||||||
|
import "@pnp/sp/sputilities";
|
||||||
|
import "@pnp/polyfill-ie11";
|
||||||
|
import { IEmailProperties } from "@pnp/sp/sputilities";
|
||||||
|
import "@pnp/sp/webs";
|
||||||
|
import "@pnp/sp/site-users/web";
|
||||||
|
import "@pnp/sp/lists";
|
||||||
|
import "@pnp/sp/items";
|
||||||
|
import "@pnp/sp/site-groups/web";
|
||||||
|
// import { EmailProperties } from '@pnp/sp';
|
||||||
|
import { BaseService } from './base.service';
|
||||||
|
import { LogHelper } from '../utilities';
|
||||||
|
import { IArticleInfo } from '../models/IArticleInfo';
|
||||||
|
|
||||||
|
export class FeedbackService extends BaseService {
|
||||||
|
|
||||||
|
public async getTitle(listitemid): Promise<any> {
|
||||||
|
var item = await sp.web.lists.getByTitle("Site Pages").items.getById(listitemid).select("Title").get();
|
||||||
|
|
||||||
|
var itemTitle = item["Title"];
|
||||||
|
return itemTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getArticleInfo(listitemid): Promise<IArticleInfo> {
|
||||||
|
var item = await sp.web.lists.getByTitle("Site Pages").items.getById(listitemid).select("Title", "EncodedAbsUrl").get();
|
||||||
|
|
||||||
|
let articleInfo: IArticleInfo = {
|
||||||
|
title: item["Title"],
|
||||||
|
url: item["EncodedAbsUrl"]
|
||||||
|
};
|
||||||
|
|
||||||
|
return articleInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendEmailToFeedbackSender(articleInfo: IArticleInfo, feedback, listitemid, currentUserEmail): Promise<any> {
|
||||||
|
|
||||||
|
if (feedback.indexOf("\n") > -1) {
|
||||||
|
feedback = feedback.replace(/\n/g, '<br/>');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentUserEmail) {
|
||||||
|
console.log("Email sending to User: " + currentUserEmail);
|
||||||
|
const emailProps: IEmailProperties = {
|
||||||
|
To: [currentUserEmail],
|
||||||
|
Subject: (articleInfo.title == "Home" || listitemid == 1) ? "Feedback has been provided on the Homepage" : "Feedback for " + articleInfo.title,
|
||||||
|
Body: (articleInfo.title == "Home" || listitemid == 1) ? "The feedback you provided on the Homepage has successfully been submitted.</br></br>\"" + feedback + "\"<br/>"
|
||||||
|
: "The feedback you provided on \"" + articleInfo.title + "\" has been successfully submitted.</br></br>\"" + feedback + "\"<br/><br>Article can be found here: <a href=\"" + articleInfo.url + "\">" + articleInfo.url + "</a></br>"
|
||||||
|
};
|
||||||
|
await sp.utility.sendEmail(emailProps)
|
||||||
|
.catch(e => {
|
||||||
|
super.handleHttpError('sendEmail', e);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendEmailToOwnerGroup(feedback, listitemid, category, currentUserName, currentUserEmail) {
|
||||||
|
let articleInfo = await this.getArticleInfo(listitemid);
|
||||||
|
|
||||||
|
let principals: IPrincipalInfo[] = await sp.utility.expandGroupsToPrincipals(["Feedback Owners"]);
|
||||||
|
|
||||||
|
if (feedback.indexOf("\n") > -1) {
|
||||||
|
feedback = feedback.replace(/\n/g, '<br/>');
|
||||||
|
}
|
||||||
|
|
||||||
|
var emails: string[] = [];
|
||||||
|
for (var i = 0; i < principals.length; i++) {
|
||||||
|
if (principals[i].Email) {
|
||||||
|
emails.push(principals[i].Email);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogHelper.warning("FeedbackService", "sendEmailToOwnerGroup", `No email for ${principals[i].LoginName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Owner Emails: " + emails.join(";"));
|
||||||
|
|
||||||
|
if (emails && emails.length > 0) {
|
||||||
|
const emailProps: IEmailProperties = {
|
||||||
|
To: emails,
|
||||||
|
Subject: (articleInfo.title == "Home") ? "Feedback has been provided on the Homepage (" + category + ")" : "Feedback for " + articleInfo.title + " (" + category + ")",
|
||||||
|
Body: (articleInfo.title == "Home" || listitemid == 1) ? "\"" + feedback + "\"<br/><br/>Submitted by: " + currentUserName + " <a href=\"mailto:" + currentUserEmail + "\">" + currentUserEmail + "</a><br/>"
|
||||||
|
: "\"" + feedback + "\"<br/><br/>Submitted by: " + currentUserName + " <a href=\"mailto:" + currentUserEmail + "\">" + currentUserEmail + "</a><<br/><br/>Article can be found here: <a href=\"" + articleInfo.url + "\">" + articleInfo.url + "</a>"
|
||||||
|
};
|
||||||
|
|
||||||
|
await sp.utility.sendEmail(emailProps)
|
||||||
|
.catch(e => {
|
||||||
|
super.handleHttpError('sendEmail', e);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
LogHelper.info("FeedbackService", "sendEmailToOwnerGroup", `Email Sent`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendEmailToFeedbackSender(articleInfo, feedback, listitemid, currentUserEmail);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Logger, LogLevel } from "@pnp/logging";
|
||||||
|
|
||||||
|
export class LogHelper {
|
||||||
|
|
||||||
|
public static verbose(className: string, methodName: string, message: string) {
|
||||||
|
message = this.formatMessage(className, methodName, message);
|
||||||
|
Logger.write(message, LogLevel.Verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static info(className: string, methodName: string, message: string) {
|
||||||
|
message = this.formatMessage(className, methodName, message);
|
||||||
|
Logger.write(message, LogLevel.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static warning(className: string, methodName: string, message: string) {
|
||||||
|
message = this.formatMessage(className, methodName, message);
|
||||||
|
Logger.write(message, LogLevel.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static error(className: string, methodName: string, message: string) {
|
||||||
|
message = this.formatMessage(className, methodName, message);
|
||||||
|
Logger.write(message, LogLevel.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static exception(className: string, methodName: string, error: Error) {
|
||||||
|
error.message = this.formatMessage(className, methodName, error.message);
|
||||||
|
Logger.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static formatMessage(className: string, methodName: string, message: string): string {
|
||||||
|
let d = new Date();
|
||||||
|
let dateStr = d.getDate() + '-' + (d.getMonth() + 1) + '-' + d.getFullYear() + ' ' +
|
||||||
|
d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds() + '.' + d.getMilliseconds();
|
||||||
|
return `${dateStr} ${className} > ${methodName} > ${message}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
$ms-greenLight : "[theme:greenLight, default:#bad80a]";
|
||||||
|
$ms-neutralSecondaryAlt : "[theme:info, default:#767676]";
|
||||||
|
$ms-neutralLight : "[theme:infoBackground, default:#eaeaea]";
|
||||||
|
$ms-magenta : "[theme:magenta, default:#b4009e]";
|
||||||
|
$ms-magentaDark : "[theme:magentaDark, default:#5c005c]";
|
||||||
|
$ms-magentaLight : "[theme:magentaLight, default:#e3008c]";
|
||||||
|
$ms-neutralDark : "[theme:neutralDark, default:#212121]";
|
||||||
|
$ms-neutralLight : "[theme:neutralLight, default:#eaeaea]";
|
||||||
|
$ms-neutralLighter : "[theme:neutralLighter, default:#f4f4f4]";
|
||||||
|
$ms-neutralLighterAlt : "[theme:neutralLighterAlt, default:#f8f8f8]";
|
||||||
|
$ms-neutralPrimary : "[theme:neutralPrimary, default:#333333]";
|
||||||
|
$ms-neutralPrimaryAlt : "[theme:neutralPrimaryAlt, default:#3C3C3C]";
|
||||||
|
$ms-neutralQuaternary : "[theme:neutralPrimaryTranslucent50, default:#d0d0d0]";
|
||||||
|
$ms-neutralQuaternaryAlt : "[theme:neutralQuaternary, default:#dadada]";
|
||||||
|
$ms-neutralSecondary : "[theme:neutralQuaternaryAlt, default:#666666]";
|
||||||
|
$ms-neutralSecondaryAlt : "[theme:neutralSecondary, default:#767676]";
|
||||||
|
$ms-neutralTertiary : "[theme:neutralSecondaryAlt, default:#a6a6a6]";
|
||||||
|
$ms-neutralTertiaryAlt : "[theme:neutralTertiary, default:#c8c8c8]";
|
||||||
|
$ms-white : "[theme:neutralTertiaryAlt, default:#ffffff]";
|
||||||
|
$ms-orange : "[theme:orange, default:#d83b01]";
|
||||||
|
$ms-orangeLight : "[theme:orangeLight, default:#ea4300]";
|
||||||
|
$ms-orangeLighter : "[theme:orangeLighter, default:#ff8c00]";
|
||||||
|
$ms-primaryBackground : "[theme:primaryBackground, default:#0078d7]";
|
||||||
|
$ms-primaryText : "[theme:primaryText, default:#0078d7]";
|
||||||
|
$ms-purple : "[theme:purple, default:#5c2d91]";
|
||||||
|
$ms-purpleDark : "[theme:purpleDark, default:#32145a]";
|
||||||
|
$ms-purpleLight : "[theme:purpleLight, default:#b4a0ff]";
|
||||||
|
$ms-red : "[theme:red, default:#e81123]";
|
||||||
|
$ms-redDark : "[theme:redDark, default:#a80000]";
|
||||||
|
$ms-success : "[theme:success, default:#107c10]";
|
||||||
|
$ms-successBackground : "[theme:successBackground, default:#dff6dd]";
|
||||||
|
$ms-teal : "[theme:teal, default:#008272]";
|
||||||
|
$ms-tealDark : "[theme:tealDark, default:#004b50]";
|
||||||
|
$ms-tealLight : "[theme:tealLight, default:#00b294]";
|
||||||
|
$ms-themeAccent : "[theme:themeAccent, default:inherit]";
|
||||||
|
$ms-themeAccentTranslucent10 : "[theme:themeAccentTranslucent10, default:inherit]";
|
||||||
|
$ms-themeDark : "[theme:themeDark, default:#005a9e]";
|
||||||
|
$ms-themeDarkAlt : "[theme:themeDarkAlt, default:#106ebe]";
|
||||||
|
$ms-themeDarker : "[theme:themeDarker, default:#004578]";
|
||||||
|
$ms-themeLight : "[theme:themeLight, default:#b3d6f2]";
|
||||||
|
$ms-themeLightAlt : "[theme:themeLightAlt, default:inherit]";
|
||||||
|
$ms-themeLighter : "[theme:themeLighter, default:#deecf9]";
|
||||||
|
$ms-themeLighterAlt : "[theme:themeLighterAlt, default:#eff6fc]";
|
||||||
|
$ms-themePrimary : "[theme:themePrimary, default:#0078d7]";
|
||||||
|
$ms-themeSecondary : "[theme:themeSecondary, default:#2488d8]";
|
||||||
|
$ms-themeTertiary : "[theme:themeTertiary, default:#69afe5]";
|
||||||
|
$ms-themeTertiaryAlt : "[theme:themeTertiaryAlt, default:#c8c8c8]";
|
||||||
|
$ms-white : "[theme:white, default:#ffffff]";
|
||||||
|
$ms-whiteTranslucent40 : "[theme:whiteTranslucent40, default:rgba(255,255,255,.4)]";
|
||||||
|
$ms-yellow : "[theme:yellow, default:#ffb900]";
|
||||||
|
$ms-yellowLight : "[theme:yellowLight, default:#fff100]";
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './LogHelper';
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||||
|
import { sp } from "@pnp/sp";
|
||||||
|
import { Logger, ConsoleListener, LogLevel } from "@pnp/logging";
|
||||||
|
|
||||||
|
export default class BaseWebPart<TProperties> extends BaseClientSideWebPart<TProperties> {
|
||||||
|
|
||||||
|
protected async onInit(): Promise<void> {
|
||||||
|
return super.onInit().then(_ => {
|
||||||
|
sp.setup({
|
||||||
|
ie11: true,
|
||||||
|
spfxContext: this.context,
|
||||||
|
});
|
||||||
|
|
||||||
|
// subscribe a listener
|
||||||
|
Logger.subscribe(new ConsoleListener());
|
||||||
|
|
||||||
|
// set the active log level -- eventually make this a web part property
|
||||||
|
Logger.activeLogLevel = LogLevel.Error;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
export class DropdownOptions {
|
||||||
|
public static Options = [
|
||||||
|
{
|
||||||
|
key: "general",
|
||||||
|
text: "General"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "typo",
|
||||||
|
text: "Typo/Edit/Broken Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "suggestion",
|
||||||
|
text: "Suggestion"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "58f4aa4b-7256-42d7-880c-ba4f51d3f6ab",
|
||||||
|
"alias": "FeedbackWebPart",
|
||||||
|
"componentType": "WebPart",
|
||||||
|
"version": "*",
|
||||||
|
"manifestVersion": 2,
|
||||||
|
"requiresCustomScript": false,
|
||||||
|
"supportedHosts": [
|
||||||
|
"SharePointWebPart"
|
||||||
|
],
|
||||||
|
"preconfiguredEntries": [
|
||||||
|
{
|
||||||
|
"groupId": "75e22ed5-fa14-4829-850a-c890608aca2d",
|
||||||
|
"group": {
|
||||||
|
"default": "Communication and collaboration"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"default": "Feedback"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"default": "Feedback webpart to submit feedback on pages/articles."
|
||||||
|
},
|
||||||
|
"officeFabricIconFontName": "Feedback",
|
||||||
|
"properties": {
|
||||||
|
"buttonLabel": "Submit Feedback",
|
||||||
|
"feedbackCategory": "general",
|
||||||
|
"showCategory": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
.feedback {
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
|
import "@pnp/polyfill-ie11";
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import { ThemeProvider, IReadonlyTheme, ThemeChangedEventArgs } from '@microsoft/sp-component-base';
|
||||||
|
import {
|
||||||
|
IPropertyPaneConfiguration,
|
||||||
|
PropertyPaneTextField,
|
||||||
|
PropertyPaneDropdown,
|
||||||
|
PropertyPaneLabel,
|
||||||
|
PropertyPaneToggle
|
||||||
|
} from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
|
import styles from './FeedbackWebPart.module.scss';
|
||||||
|
import * as strings from 'FeedbackWebPartStrings';
|
||||||
|
import BaseWebPart from '../BaseWebPart';
|
||||||
|
import {Container} from './components/Container/Container';
|
||||||
|
import {IContainerProps} from './components/Container/IContainerProps';
|
||||||
|
import {DropdownOptions} from './DropdownOptions';
|
||||||
|
|
||||||
|
export interface IFeedbackWebPartProps {
|
||||||
|
buttonLabel: string;
|
||||||
|
feedbackCategory: string;
|
||||||
|
showCategory: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default class FeedbackWebPart extends BaseWebPart<IFeedbackWebPartProps> {
|
||||||
|
|
||||||
|
private themeProvider: ThemeProvider;
|
||||||
|
private themeVariant: IReadonlyTheme | undefined;
|
||||||
|
|
||||||
|
protected onInit(): Promise<void> {
|
||||||
|
// Consume the new ThemeProvider service
|
||||||
|
this.themeProvider = this.context.serviceScope.consume(ThemeProvider.serviceKey);
|
||||||
|
|
||||||
|
// If it exists, get the theme variant
|
||||||
|
this.themeVariant = this.themeProvider.tryGetTheme();
|
||||||
|
|
||||||
|
// Register a handler to be notified if the theme variant changes
|
||||||
|
this.themeProvider.themeChangedEvent.add(this, this.handleThemeChangedEvent);
|
||||||
|
return super.onInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async render(): Promise<void> {
|
||||||
|
var showCategory = escape(this.properties.showCategory.toString()) == "false" ? false : true;
|
||||||
|
const element: React.ReactElement<IContainerProps> = React.createElement(
|
||||||
|
Container,
|
||||||
|
{
|
||||||
|
buttonLabel: escape(this.properties.buttonLabel),
|
||||||
|
showCategory: showCategory,
|
||||||
|
themeVariant: this.themeVariant,
|
||||||
|
listitemid: this.context.pageContext.listItem.id, //Replace with "1" if you're running this in a workbench
|
||||||
|
selectedCategory: escape(this.properties.feedbackCategory),
|
||||||
|
currentUser: this.context.pageContext.user
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ReactDom.render(element, this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleThemeChangedEvent(args: ThemeChangedEventArgs): void {
|
||||||
|
this.themeVariant = args.theme;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDispose(): void {
|
||||||
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
description: strings.PropertyPane_Description
|
||||||
|
},
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupName: strings.PropertyPane_GroupName_Settings,
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('buttonLabel', {
|
||||||
|
label: strings.PropertyPane_Label_ButtonText
|
||||||
|
}),
|
||||||
|
PropertyPaneToggle('showCategory', {
|
||||||
|
label: strings.FeedbackCategoryToggle_Label,
|
||||||
|
onText: 'On',
|
||||||
|
offText: 'Off'
|
||||||
|
}),
|
||||||
|
PropertyPaneDropdown('feedbackCategory', {
|
||||||
|
label: strings.FeedbackCategory_Label,
|
||||||
|
selectedKey: "general",
|
||||||
|
options: DropdownOptions.Options})
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
groupName: strings.PropertyPane_GroupName_About,
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneLabel('versionNumber', {
|
||||||
|
text: strings.PropertyPane_Label_VersionInfo + this.manifest.version
|
||||||
|
})
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import "@pnp/polyfill-ie11";
|
||||||
|
import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
|
||||||
|
import { Dialog, DialogFooter, DialogType } from 'office-ui-fabric-react/lib/Dialog';
|
||||||
|
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
|
||||||
|
import { IContainerProps } from './IContainerProps';
|
||||||
|
import { useConstCallback } from '@uifabric/react-hooks';
|
||||||
|
import { TextField } from 'office-ui-fabric-react/lib/TextField';
|
||||||
|
import { Dropdown, DropdownMenuItemType, IDropdownStyles, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||||
|
import { FeedbackService } from '../../../../services/feedback.service';
|
||||||
|
import { DropdownOptions } from '../../DropdownOptions';
|
||||||
|
import * as strings from 'FeedbackWebPartStrings';
|
||||||
|
import { IArticleInfo } from '../../../../models/IArticleInfo';
|
||||||
|
|
||||||
|
const buttonStyles = { root: { marginRight: 8 } };
|
||||||
|
const dialogContentProps = {
|
||||||
|
type: DialogType.normal,
|
||||||
|
title: 'Feedback successfully submitted.',
|
||||||
|
};
|
||||||
|
const dialogModalProps = {
|
||||||
|
isBlocking: false,
|
||||||
|
styles: { main: { maxWidth: 450 } },
|
||||||
|
};
|
||||||
|
const options: IDropdownOption[] = DropdownOptions.Options;
|
||||||
|
|
||||||
|
|
||||||
|
export const Container: React.FunctionComponent<IContainerProps> = props => {
|
||||||
|
//This is for IE 11 "find" issue
|
||||||
|
// https://tc39.github.io/ecma262/#sec-array.prototype.find
|
||||||
|
if (!Array.prototype.find) {
|
||||||
|
Object.defineProperty(Array.prototype, 'find', {
|
||||||
|
value: function(predicate) {
|
||||||
|
// 1. Let O be ? ToObject(this value).
|
||||||
|
if (this == null) {
|
||||||
|
throw new TypeError('"this" is null or not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
var o = Object(this);
|
||||||
|
|
||||||
|
// 2. Let len be ? ToLength(? Get(O, "length")).
|
||||||
|
var len = o.length >>> 0;
|
||||||
|
|
||||||
|
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
|
||||||
|
if (typeof predicate !== 'function') {
|
||||||
|
throw new TypeError('predicate must be a function');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
||||||
|
// tslint:disable-next-line: use-named-parameter
|
||||||
|
var thisArg = arguments[1];
|
||||||
|
|
||||||
|
// 5. Let k be 0.
|
||||||
|
var k = 0;
|
||||||
|
|
||||||
|
// 6. Repeat, while k < len
|
||||||
|
while (k < len) {
|
||||||
|
// a. Let Pk be ! ToString(k).
|
||||||
|
// b. Let kValue be ? Get(O, Pk).
|
||||||
|
// c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
|
||||||
|
// d. If testResult is true, return kValue.
|
||||||
|
var kValue = o[k];
|
||||||
|
if (predicate.call(thisArg, kValue, k, o)) {
|
||||||
|
return kValue;
|
||||||
|
}
|
||||||
|
// e. Increase k by 1.
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Return undefined.
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let defaultCategoryValue = DropdownOptions.Options.find(o => o.key === props.selectedCategory);
|
||||||
|
const feedbackService = new FeedbackService();
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
const [isDialogVisible, setIsDialogVisible] = React.useState(false);
|
||||||
|
const [pageTitle, setPageTitle] = React.useState("");
|
||||||
|
const [articleInfo, setArticleInfo] = React.useState<IArticleInfo>();
|
||||||
|
const [txtValue, setTxtValue] = React.useState("");
|
||||||
|
const [categoryValue, setCategoryValue] = React.useState((defaultCategoryValue) ? defaultCategoryValue.text : "");
|
||||||
|
|
||||||
|
React.useEffect(()=>{
|
||||||
|
|
||||||
|
feedbackService.getTitle(props.listitemid).then((res) => {
|
||||||
|
setPageTitle(res);
|
||||||
|
});
|
||||||
|
feedbackService.getArticleInfo(props.listitemid).then((res) => {
|
||||||
|
setArticleInfo(res);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
const openPanel = React.useCallback(() => setIsOpen(true), [isOpen]);
|
||||||
|
const dismissPanel = React.useCallback(() => setIsOpen(false), [isOpen]);
|
||||||
|
|
||||||
|
const hideDialog = React.useCallback(() => setIsDialogVisible(false), [isDialogVisible]);
|
||||||
|
const hideDialogAndPanel = () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
setIsDialogVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
let newDefaultCategory;
|
||||||
|
if (!props.showCategory){
|
||||||
|
newDefaultCategory = DropdownOptions.Options.find(o => o.key === props.selectedCategory);
|
||||||
|
}
|
||||||
|
if (props.listitemid == null){
|
||||||
|
console.log("List item ID is null. Please run this on a site page.")
|
||||||
|
}
|
||||||
|
feedbackService.sendEmailToOwnerGroup(txtValue, props.listitemid, (newDefaultCategory) ? newDefaultCategory.text : categoryValue, props.currentUser.displayName, props.currentUser.email);
|
||||||
|
dismissPanel();
|
||||||
|
setIsDialogVisible(true);
|
||||||
|
setCategoryValue(defaultCategoryValue.text);
|
||||||
|
setTxtValue("");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<DefaultButton style={{
|
||||||
|
color: props.themeVariant.semanticColors.buttonText
|
||||||
|
}} text={unescape(props.buttonLabel)} onClick={openPanel} />
|
||||||
|
<Panel
|
||||||
|
isLightDismiss
|
||||||
|
isOpen={isOpen}
|
||||||
|
type={PanelType.medium}
|
||||||
|
onDismiss={dismissPanel}
|
||||||
|
headerText={strings.PanelHeaderText + pageTitle}
|
||||||
|
closeButtonAriaLabel="Close"
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<p>{strings.Feedback_Instructions}</p>
|
||||||
|
<Dropdown label={(props.showCategory) ? strings.FeedbackCategory_Label : ""} options={options} defaultSelectedKey={props.selectedCategory} hidden={(!props.showCategory)}
|
||||||
|
onChange={(event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number) => {setCategoryValue(option.text);}}/>
|
||||||
|
<TextField name="feedbackTxt" multiline rows={8} value={txtValue} label={strings.FeedbackBox_Label}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) =>
|
||||||
|
{setTxtValue(newValue);}}/>
|
||||||
|
<br></br>
|
||||||
|
<div>
|
||||||
|
<PrimaryButton type="submit" styles={buttonStyles} disabled={(txtValue.length > 10) ? false : true}>
|
||||||
|
{strings.ButtonText_Submit}
|
||||||
|
</PrimaryButton>
|
||||||
|
<DefaultButton onClick={dismissPanel}>{strings.ButtonText_Cancel}</DefaultButton>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Panel>
|
||||||
|
<Dialog
|
||||||
|
hidden={!isDialogVisible}
|
||||||
|
onDismiss={hideDialog}
|
||||||
|
dialogContentProps={dialogContentProps}
|
||||||
|
modalProps={dialogModalProps}
|
||||||
|
>
|
||||||
|
<DialogFooter>
|
||||||
|
<PrimaryButton onClick={hideDialogAndPanel} text={strings.ButtonText_Ok} />
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { IReadonlyTheme } from "@microsoft/sp-component-base";
|
||||||
|
|
||||||
|
export interface IContainerProps {
|
||||||
|
buttonLabel: string;
|
||||||
|
showCategory: boolean;
|
||||||
|
listitemid: number;
|
||||||
|
selectedCategory: string;
|
||||||
|
currentUser: any;
|
||||||
|
themeVariant: IReadonlyTheme;
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
define([], function() {
|
||||||
|
return {
|
||||||
|
"PropertyPane_Description": "Configure feedback properties.",
|
||||||
|
"PropertyPane_Label_ButtonText": "Button Text",
|
||||||
|
"PropertyPane_GroupName_Settings": "Settings",
|
||||||
|
"PropertyPane_GroupName_About": "About Webpart",
|
||||||
|
"PropertyPane_Label_VersionInfo": "Version: ",
|
||||||
|
|
||||||
|
"ButtonText_Cancel": "Cancel",
|
||||||
|
"ButtonText_Ok": "OK",
|
||||||
|
"ButtonText_Submit": "Submit",
|
||||||
|
"PanelHeaderText": "Provide feedback on ",
|
||||||
|
"FeedbackBox_Label": "Feedback:",
|
||||||
|
"FeedbackCategory_Label": "Feedback Category:",
|
||||||
|
"FeedbackCategoryToggle_Label": "Show Feedback Category",
|
||||||
|
"Feedback_Instructions": "Provide feedback and click the Submit button below."
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
declare interface IFeedbackWebPartStrings {
|
||||||
|
PropertyPane_Description: string;
|
||||||
|
PropertyPane_Label_ButtonText: string;
|
||||||
|
PropertyPane_GroupName_Settings: string;
|
||||||
|
PropertyPane_GroupName_About: string;
|
||||||
|
PropertyPane_Label_VersionInfo: string;
|
||||||
|
ButtonText_Cancel: string;
|
||||||
|
ButtonText_Ok: string;
|
||||||
|
ButtonText_Submit: string;
|
||||||
|
PanelHeaderText: string;
|
||||||
|
FeedbackBox_Label: string;
|
||||||
|
FeedbackCategory_Label: string;
|
||||||
|
FeedbackCategoryToggle_Label: string;
|
||||||
|
Feedback_Instructions: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'FeedbackWebPartStrings' {
|
||||||
|
const strings: IFeedbackWebPartStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 383 B |
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"es2017",
|
||||||
|
"dom",
|
||||||
|
"es2015.collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"lib"
|
||||||
|
]
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue