mirror of
https://github.com/pnp/sp-dev-fx-webparts.git
synced 2025-02-07 21:48:24 +00:00
Merge branch 'dev' of https://github.com/SharePoint/sp-dev-fx-webparts into dev
This commit is contained in:
commit
0578d52dc0
25
samples/js-advanced-commenting/.editorconfig
Normal file
25
samples/js-advanced-commenting/.editorconfig
Normal file
@ -0,0 +1,25 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
32
samples/js-advanced-commenting/.gitignore
vendored
Normal file
32
samples/js-advanced-commenting/.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
12
samples/js-advanced-commenting/.yo-rc.json
Normal file
12
samples/js-advanced-commenting/.yo-rc.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.9.1",
|
||||
"libraryName": "modern-page-comments",
|
||||
"libraryId": "cfa8dbcf-b32d-4de0-a6ba-af486007c2f2",
|
||||
"packageManager": "npm",
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
88
samples/js-advanced-commenting/README.md
Normal file
88
samples/js-advanced-commenting/README.md
Normal file
@ -0,0 +1,88 @@
|
||||
# Advanced Comments Box
|
||||
|
||||
## Summary
|
||||
> This component is developed for the advanced usage of commenting the page or article etc. **_Page Comments_** lists will be created to store the comments. Following are some of the features of this component.
|
||||
* Can be used in the modern page with the existing comments disabled.
|
||||
* Classification of comments by **_Popular_**, **_Newest_**, **_Oldest_** and **_Attachments_**
|
||||
* Ability to refer files as a comment.
|
||||
* **_Edit_**, **_Reply_** (nested comments), **_Like_** & **_Delete_** options are available based on the configuration.
|
||||
* **_Hashtag_** & **_Ping Users_** are also available.
|
||||
* **_Document Preview_** is also available for all office documents and videos based on the configuration.
|
||||
* Display of **_New_** icon for the current day comments.
|
||||
|
||||
## Properties
|
||||
|
||||
1. **_DateTime_** format on when the comments were added or modified
|
||||
|
||||
2. **_Profile Picture_** style, whether it has to be rounded or square
|
||||
|
||||
3. Enable or Disable **_Navigation_** whether to display the comments classification
|
||||
|
||||
4. Enable or disable **_Attachments_**. Following properties are required when attachments are enabled.
|
||||
|
||||
* **_Library_** to store the files uploaded.
|
||||
* Allowed **_File Formats_** in the comments box.
|
||||
* Maximum **_File Size_** allowed.
|
||||
|
||||
5. **_Ping Users_** will allow to mention the users. The users are pulled from the **Site Users**.
|
||||
|
||||
6. **_Edit_** comments can be enabled or disabled to allow the users to edit the comments. Files added can be deleted not edited.
|
||||
* **_Delete_** option can be enabled or disabled to allow the users to delete the comments. Comments with no-replies are allowed to delete. Delete is allowed only if Edit is allowed.
|
||||
|
||||
7. **_Upvoting_** of comments to like or dislike the comments.
|
||||
|
||||
8. **_Hashtags_**
|
||||
|
||||
9. **_Document Preview_** can be enabled or disabled for the office files and videos.
|
||||
|
||||
|
||||
## Preview
|
||||
![Advanced-Comments-Box](./assets/Advanced-Comments-Box.gif)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
> **@microsoft/generator-sharepoint - 1.9.1**
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
SPFxPageComments | Sudharsan K.([@sudharsank](https://twitter.com/sudharsank), [Know More](http://windowssharepointserver.blogspot.com/))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0.0.0|Feb 05 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
|
||||
- in the command line run:
|
||||
- `npm install`
|
||||
- `gulp bundle --ship && gulp package-solution --ship`
|
||||
- Add the .sppkg file to the app catalog and add the **Page Comments** web part to the page.
|
||||
|
||||
## Features
|
||||
- Used [SharePoint Framework Property Controls](https://sharepoint.github.io/sp-dev-fx-property-controls/) to create the property pane controls(Text, ListPicker, Toggle) with callout.
|
||||
- Used [PnP](https://pnp.github.io/pnpjs/) for communication with SharePoint.
|
||||
- Used [jquery-comments](https://viima.github.io/jquery-comments/) for comments control with some customization.
|
||||
- Used [Moment.js](https://momentjs.com/) for datetime formatting.
|
||||
|
||||
#### Local Mode
|
||||
This solution doesn't work on local mode.
|
||||
|
||||
#### SharePoint Mode
|
||||
If you want to try on a real environment, open:
|
||||
[O365 Workbench](https://your-domain.sharepoint.com/_layouts/15/workbench.aspx)
|
BIN
samples/js-advanced-commenting/assets/Advanced-Comments-Box.gif
Normal file
BIN
samples/js-advanced-commenting/assets/Advanced-Comments-Box.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 MiB |
31
samples/js-advanced-commenting/config/config.json
Normal file
31
samples/js-advanced-commenting/config/config.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"page-comments-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/pageComments/PageCommentsWebPart.js",
|
||||
"manifest": "./src/webparts/pageComments/PageCommentsWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {
|
||||
"jquery": {
|
||||
"path": "node_modules/jquery/dist/jquery.min.js",
|
||||
"globalName": "jQuery"
|
||||
},
|
||||
"textcomplete": {
|
||||
"path": "https://cdnjs.cloudflare.com/ajax/libs/jquery.textcomplete/1.8.0/jquery.textcomplete.js",
|
||||
"globalName": "jQuery",
|
||||
"globalDependencies": [
|
||||
"jquery"
|
||||
]
|
||||
}
|
||||
},
|
||||
"localizedResources": {
|
||||
"PageCommentsWebPartStrings": "lib/webparts/pageComments/loc/{locale}.js",
|
||||
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
4
samples/js-advanced-commenting/config/copy-assets.json
Normal file
4
samples/js-advanced-commenting/config/copy-assets.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
@ -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": "modern-page-comments",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
13
samples/js-advanced-commenting/config/package-solution.json
Normal file
13
samples/js-advanced-commenting/config/package-solution.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "Advanced Comments Box",
|
||||
"id": "cfa8dbcf-b32d-4de0-a6ba-af486007c2f2",
|
||||
"version": "1.1.0.2",
|
||||
"includeClientSideAssets": true,
|
||||
"isDomainIsolated": false
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/advanced-comments-box.sppkg"
|
||||
}
|
||||
}
|
10
samples/js-advanced-commenting/config/serve.json
Normal file
10
samples/js-advanced-commenting/config/serve.json
Normal 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/"
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
7
samples/js-advanced-commenting/gulpfile.js
Normal file
7
samples/js-advanced-commenting/gulpfile.js
Normal 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);
|
18399
samples/js-advanced-commenting/package-lock.json
generated
Normal file
18399
samples/js-advanced-commenting/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
samples/js-advanced-commenting/package.json
Normal file
38
samples/js-advanced-commenting/package.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "modern-page-comments",
|
||||
"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.9.1",
|
||||
"@microsoft/sp-lodash-subset": "1.9.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
|
||||
"@microsoft/sp-webpart-base": "1.9.1",
|
||||
"@pnp/sp": "^2.0.0",
|
||||
"@pnp/spfx-property-controls": "1.16.0",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/jquery": "^2.0.54",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"jquery": "^2.2.4",
|
||||
"moment": "^2.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.9.1",
|
||||
"@microsoft/sp-tslint-rules": "1.9.1",
|
||||
"@microsoft/sp-module-interfaces": "1.9.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.9.1",
|
||||
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
}
|
||||
}
|
1
samples/js-advanced-commenting/src/index.ts
Normal file
1
samples/js-advanced-commenting/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "7520fa3d-1f05-4d78-bf7f-91627d9b64d3",
|
||||
"alias": "PageCommentsWebPart",
|
||||
"componentType": "WebPart",
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": ["SharePointWebPart"],
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "Advanced Comments" },
|
||||
"description": { "default": "Comments with advanced features for modern pages" },
|
||||
"officeFabricIconFontName": "FileComment",
|
||||
"properties": {
|
||||
"datetimeFormat":"DD/MM/YYYY",
|
||||
"enableNavigation": true,
|
||||
"enableReplying": true,
|
||||
"enableEditing": false,
|
||||
"enableUpvoting": true,
|
||||
"enableDeleting": false,
|
||||
"enableAttachments": false,
|
||||
"enableHashtags": false,
|
||||
"enablePinging": false,
|
||||
"enableDocumentPreview": false,
|
||||
"roundProfilePictures": true,
|
||||
"attachmentFileFormats": "audio/*,image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx",
|
||||
"attachmentFileSize": 2
|
||||
}
|
||||
}]
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.pageComments {
|
||||
.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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
.errorMessage {
|
||||
margin: 10px;
|
||||
border: 1px solid red;
|
||||
padding: 10px;
|
||||
background-color: #ead9d9;
|
||||
font-weight: 600;
|
||||
}
|
@ -0,0 +1,374 @@
|
||||
import * as React from 'react';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
import { SPComponentLoader } from '@microsoft/sp-loader';
|
||||
import { CalloutTriggers } from '@pnp/spfx-property-controls/lib/PropertyFieldHeader';
|
||||
import { PropertyFieldSliderWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldSliderWithCallout';
|
||||
import { PropertyFieldToggleWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldToggleWithCallout';
|
||||
import { PropertyFieldListPicker, PropertyFieldListPickerOrderBy } from '@pnp/spfx-property-controls/lib/PropertyFieldListPicker';
|
||||
import * as _ from "lodash";
|
||||
import * as moment from 'moment';
|
||||
|
||||
import styles from './PageCommentsWebPart.module.scss';
|
||||
import * as strings from 'PageCommentsWebPartStrings';
|
||||
|
||||
import * as $ from 'jquery';
|
||||
require('textcomplete');
|
||||
import { sp } from '@pnp/sp';
|
||||
import SPHelper from './SPHelper';
|
||||
require('./css/jquery-comments.css');
|
||||
|
||||
export interface IPageCommentsWebPartProps {
|
||||
enableNavigation: boolean;
|
||||
enableReplying: boolean;
|
||||
enableAttachments: boolean;
|
||||
enableEditing: boolean;
|
||||
enableUpvoting: boolean;
|
||||
enableDeleting: boolean;
|
||||
enableDeletingCommentWithReplies: boolean;
|
||||
enableHashtags: boolean;
|
||||
enablePinging: boolean;
|
||||
enableDocumentPreview: boolean;
|
||||
roundProfilePictures: boolean;
|
||||
datetimeFormat: string;
|
||||
attachmentFileFormats: string;
|
||||
attachmentFileSize: number;
|
||||
docLib: string;
|
||||
}
|
||||
|
||||
export default class PageCommentsWebPart extends BaseClientSideWebPart<IPageCommentsWebPartProps> {
|
||||
|
||||
private helper: SPHelper = null;
|
||||
private currentUserInfo: any = null;
|
||||
private siteUsers: any[] = [];
|
||||
private pageurl: string = '';
|
||||
private postAttachmentPath: string = '';
|
||||
private pageFolderExists: boolean = false;
|
||||
|
||||
protected async onInit(): Promise<void> {
|
||||
await super.onInit();
|
||||
sp.setup(this.context);
|
||||
}
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
SPComponentLoader.loadCss("https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css");
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
if (this.properties.enableAttachments && (this.properties.docLib === null || undefined === this.properties.docLib ||
|
||||
this.properties.docLib.toLocaleUpperCase() === "NO_LIST_SELECTED")) {
|
||||
this.domElement.innerHTML = `
|
||||
<div class="${styles.errorMessage}"><i class="fa fa-times-circle" aria-hidden="true"></i> ${strings.NoAttachmentRepoMsg}</div>
|
||||
`;
|
||||
} else {
|
||||
this.context.statusRenderer.displayLoadingIndicator(this.domElement, strings.LoadingMsg, 0);
|
||||
this.checkAndCreateList();
|
||||
}
|
||||
}
|
||||
|
||||
private async checkAndCreateList() {
|
||||
if (this.properties.enableAttachments) {
|
||||
this.helper = new SPHelper(this.properties.docLib);
|
||||
} else {
|
||||
this.helper = new SPHelper();
|
||||
}
|
||||
await this.helper.checkListExists();
|
||||
this.initializeComments();
|
||||
}
|
||||
|
||||
private initializeComments = async () => {
|
||||
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
|
||||
this.domElement.innerHTML = `
|
||||
<div class="${ styles.pageComments}">
|
||||
<div class="${ styles.container}">
|
||||
<div class="${ styles.row}">
|
||||
<div id="page-comments"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
var self = this;
|
||||
if (this.properties.enableAttachments) {
|
||||
await this.helper.getDocLibInfo();
|
||||
this.postAttachmentPath = await this.helper.getPostAttachmentFilePath(this.pageurl);
|
||||
this.pageFolderExists = await this.helper.checkForPageFolder(this.postAttachmentPath);
|
||||
}
|
||||
this.pageurl = this.context.pageContext.legacyPageContext.serverRequestPath;
|
||||
this.currentUserInfo = await this.helper.getCurrentUserInfo();
|
||||
this.siteUsers = await this.helper.getSiteUsers(self.currentUserInfo.ID);
|
||||
require(['jquery', './js/jquery-comments.min'], (jQuery, comments) => {
|
||||
jQuery('#page-comments').comments({
|
||||
profilePictureURL: self.currentUserInfo.Picture,
|
||||
currentUserId: self.currentUserInfo.ID,
|
||||
maxRepliesVisible: 3,
|
||||
textareaRows: 1,
|
||||
textareaRowsOnFocus: 2,
|
||||
textareaMaxRows: 5,
|
||||
highlightColor: '#b5121b',
|
||||
attachmentFileFormats: self.properties.attachmentFileFormats !== undefined ? self.properties.attachmentFileFormats : 'audio/*,image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx',
|
||||
attachmentFileSize: self.properties.attachmentFileSize !== undefined ? self.properties.attachmentFileSize : 5,
|
||||
siteURL: self.context.pageContext.legacyPageContext.webServerRelativeUrl,
|
||||
enableNavigation: self.properties.enableNavigation !== undefined ? self.properties.enableNavigation : true,
|
||||
enableReplying: self.properties.enableReplying !== undefined ? self.properties.enableReplying : true,
|
||||
enableEditing: self.properties.enableEditing !== undefined ? self.properties.enableEditing : false,
|
||||
enableUpvoting: self.properties.enableUpvoting !== undefined ? self.properties.enableUpvoting : true,
|
||||
enableDeleting: self.properties.enableDeleting !== undefined ? self.properties.enableDeleting : false,
|
||||
enableAttachments: self.properties.enableAttachments !== undefined ? self.properties.enableAttachments : false,
|
||||
enableHashtags: self.properties.enableHashtags !== undefined ? self.properties.enableHashtags : false,
|
||||
enablePinging: self.properties.enablePinging !== undefined ? self.properties.enablePinging : false,
|
||||
enableDocumentPreview: self.properties.enableDocumentPreview !== undefined ? self.properties.enableDocumentPreview : false,
|
||||
roundProfilePictures: self.properties.roundProfilePictures !== undefined ? self.properties.roundProfilePictures : true,
|
||||
timeFormatter: (time) => {
|
||||
try {
|
||||
if (self.properties.datetimeFormat) {
|
||||
return moment(time).format(self.properties.datetimeFormat);
|
||||
} else return moment(time).format(self.properties.datetimeFormat);
|
||||
} catch (err) {
|
||||
return moment(time).format("DD/MM/YYYY hh:mm:ss A");
|
||||
}
|
||||
},
|
||||
getComments: async (success, error) => {
|
||||
let commentsArray = await self.helper.getPostComments(self.pageurl, self.currentUserInfo);
|
||||
if (commentsArray.length > 0) {
|
||||
var fil = _.filter(commentsArray, (o) => { return moment(o.created).format("DD/MM/YYYY") === moment().format("DD/MM/YYYY"); });
|
||||
fil.map((comment) => {
|
||||
_.set(comment, 'is_new', true);
|
||||
});
|
||||
fil = _.filter(commentsArray, (o) => { return o.userid == self.currentUserInfo.ID; });
|
||||
fil.map((comment) => {
|
||||
_.set(comment, 'created_by_current_user', true);
|
||||
});
|
||||
}
|
||||
success(commentsArray);
|
||||
},
|
||||
postComment: async (commentJson, success, error) => {
|
||||
commentJson.fullname = self.currentUserInfo.DisplayName;
|
||||
commentJson.userid = self.currentUserInfo.ID;
|
||||
commentJson = self.saveComment(commentJson);
|
||||
await self.helper.postComment(self.pageurl, commentJson, self.currentUserInfo);
|
||||
if (moment(commentJson.created).format("DD/MM/YYYY") === moment().format("DD/MM/YYYY")) _.set(commentJson, 'is_new', true);
|
||||
_.set(commentJson, 'created_by_current_user', true);
|
||||
success(commentJson);
|
||||
},
|
||||
searchUsers: async (term, success, error) => {
|
||||
let res = [];
|
||||
if (self.siteUsers.length <= 0) self.siteUsers = await self.helper.getSiteUsers(self.currentUserInfo.ID);
|
||||
res = _.chain(self.siteUsers).filter((o) => { return o.fullname.toLowerCase().indexOf(term) >= 0 || o.email.toLowerCase().indexOf(term) >= 0; }).take(10).value();
|
||||
success(res);
|
||||
},
|
||||
upvoteComment: async (commentJSON, success, error) => {
|
||||
await self.helper.voteComment(self.pageurl, commentJSON, self.currentUserInfo);
|
||||
success(commentJSON);
|
||||
},
|
||||
deleteComment: async (commentJSON, success, error) => {
|
||||
await self.helper.deleteComment(self.pageurl, commentJSON);
|
||||
success();
|
||||
},
|
||||
putComment: async (commentJSON, success, error) => {
|
||||
commentJSON = self.saveComment(commentJSON);
|
||||
await self.helper.editComments(self.pageurl, commentJSON);
|
||||
success(commentJSON);
|
||||
},
|
||||
uploadAttachments: async (commentArray, success, error) => {
|
||||
let res = await self.helper.postAttachments(commentArray, self.pageFolderExists, self.postAttachmentPath);
|
||||
_.merge(res[0], { userid: self.currentUserInfo.ID, fullname: self.currentUserInfo.DisplayName });
|
||||
await self.helper.postComment(self.pageurl, res[0], self.currentUserInfo);
|
||||
if (moment(res[0].created).format("DD/MM/YYYY") === moment().format("DD/MM/YYYY")) _.set(res[0], 'is_new', true);
|
||||
_.set(res[0], 'created_by_current_user', true);
|
||||
success(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private saveComment = (data) => {
|
||||
// Convert pings to human readable format
|
||||
$(Object.keys(data.pings)).each((index, userId) => {
|
||||
var fullname = data.pings[`${userId}`];
|
||||
var pingText = '@' + fullname;
|
||||
data.content = data.content.replace(new RegExp('@' + userId, 'g'), pingText);
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
private checkForDocumentLibrary = (value: string): string => {
|
||||
if (value === null || value.trim().length === 0 || value.toLocaleUpperCase() === "NO_LIST_SELECTED") {
|
||||
return strings.AttachmentRepoPropValMsg;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
protected get disableReactivePropertyChanges(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('datetimeFormat', {
|
||||
label: strings.DateTimeFormatLabel,
|
||||
description: strings.DateTimeFormatDescription,
|
||||
multiline: false,
|
||||
resizable: false,
|
||||
value: this.properties.datetimeFormat
|
||||
}),
|
||||
PropertyFieldToggleWithCallout('roundProfilePictures', {
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: 'roundProfilePicturesFieldId',
|
||||
label: strings.RoundProfilePicLabel,
|
||||
calloutContent: React.createElement('p', {}, strings.RoundProfilePicDescription),
|
||||
onText: 'Enable',
|
||||
offText: 'Disable',
|
||||
checked: this.properties.roundProfilePictures !== undefined ? this.properties.roundProfilePictures : true
|
||||
}),
|
||||
PropertyFieldToggleWithCallout('enableNavigation', {
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: 'enableNavigationFieldId',
|
||||
label: strings.NavigationLabel,
|
||||
calloutContent: React.createElement('p', {}, strings.NavigationDescription),
|
||||
onText: 'Enable',
|
||||
offText: 'Disable',
|
||||
checked: this.properties.enableNavigation !== undefined ? this.properties.enableNavigation : true
|
||||
}),
|
||||
PropertyFieldToggleWithCallout('enableAttachments', {
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: 'enableAttachmentsFieldId',
|
||||
label: strings.AttachmentLabel,
|
||||
calloutContent: React.createElement('p', {}, strings.AttachmentDescription),
|
||||
onText: 'Enable',
|
||||
offText: 'Disable',
|
||||
checked: this.properties.enableAttachments !== undefined ? this.properties.enableAttachments : false
|
||||
}),
|
||||
PropertyFieldListPicker('docLib', {
|
||||
label: strings.AttachmentRepoLabel,
|
||||
selectedList: this.properties.docLib,
|
||||
includeHidden: false,
|
||||
orderBy: PropertyFieldListPickerOrderBy.Title,
|
||||
onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
|
||||
properties: this.properties,
|
||||
context: this.context,
|
||||
onGetErrorMessage: this.checkForDocumentLibrary.bind(this),
|
||||
deferredValidationTime: 0,
|
||||
key: 'docLibFieldId',
|
||||
baseTemplate: 101,
|
||||
disabled: !this.properties.enableAttachments
|
||||
}),
|
||||
PropertyPaneTextField('attachmentFileFormats', {
|
||||
label: strings.AttachmentFileFormatLabel,
|
||||
description: strings.AttachmentFileFormatDescription,
|
||||
multiline: false,
|
||||
resizable: false,
|
||||
value: this.properties.attachmentFileFormats,
|
||||
disabled: !this.properties.enableAttachments
|
||||
}),
|
||||
PropertyFieldSliderWithCallout('attachmentFileSize', {
|
||||
calloutContent: React.createElement('div', {}, strings.AttachmentFileSizeDescription),
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
calloutWidth: 200,
|
||||
key: 'attachmentFileSizeFieldId',
|
||||
label: strings.AttachmentFileSizeLabel,
|
||||
max: 10,
|
||||
min: 1,
|
||||
step: 1,
|
||||
showValue: true,
|
||||
value: this.properties.attachmentFileSize,
|
||||
disabled: !this.properties.enableAttachments
|
||||
}),
|
||||
PropertyFieldToggleWithCallout('enablePinging', {
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: 'enablePingingFieldId',
|
||||
label: strings.PingLabel,
|
||||
calloutContent: React.createElement('p', {}, strings.PingDescription),
|
||||
onText: 'Enable',
|
||||
offText: 'Disable',
|
||||
checked: this.properties.enablePinging !== undefined ? this.properties.enablePinging : false
|
||||
}),
|
||||
PropertyFieldToggleWithCallout('enableEditing', {
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: 'enableEditingFieldId',
|
||||
label: strings.EditingLabel,
|
||||
calloutContent: React.createElement('p', {}, strings.EditingDescription),
|
||||
onText: 'Enable',
|
||||
offText: 'Disable',
|
||||
checked: this.properties.enableEditing !== undefined ? this.properties.enableEditing : false
|
||||
}),
|
||||
PropertyFieldToggleWithCallout('enableDeleting', {
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: 'enableDeletingFieldId',
|
||||
label: strings.DeleteLabel,
|
||||
calloutContent: React.createElement('p', {}, strings.DeleteDescription),
|
||||
onText: 'Enable',
|
||||
offText: 'Disable',
|
||||
checked: this.properties.enableDeleting !== undefined ? this.properties.enableDeleting : false,
|
||||
disabled: !this.properties.enableEditing
|
||||
}),
|
||||
// PropertyFieldToggleWithCallout('enableDeletingCommentWithReplies', {
|
||||
// calloutTrigger: CalloutTriggers.Hover,
|
||||
// key: 'enableDeletingCommentWithRepliesFieldId',
|
||||
// label: strings.DeleteRepliesLabel,
|
||||
// calloutContent: React.createElement('p', {}, strings.DeleteRepliesDescription),
|
||||
// onText: 'Enable',
|
||||
// offText: 'Disable',
|
||||
// checked: this.properties.enableDeletingCommentWithReplies,
|
||||
// disabled: !this.properties.enableEditing
|
||||
// }),
|
||||
PropertyFieldToggleWithCallout('enableUpvoting', {
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: 'enableUpvotingFieldId',
|
||||
label: strings.UpVotingLabel,
|
||||
calloutContent: React.createElement('p', {}, strings.UpVotingDescription),
|
||||
onText: 'Enable',
|
||||
offText: 'Disable',
|
||||
checked: this.properties.enableUpvoting !== undefined ? this.properties.enableUpvoting : true
|
||||
}),
|
||||
PropertyFieldToggleWithCallout('enableReplying', {
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: 'enableReplyingFieldId',
|
||||
label: strings.ReplyLabel,
|
||||
calloutContent: React.createElement('p', {}, strings.ReplyDescription),
|
||||
onText: 'Enable',
|
||||
offText: 'Disable',
|
||||
checked: this.properties.enableReplying !== undefined ? this.properties.enableReplying : true
|
||||
}),
|
||||
PropertyFieldToggleWithCallout('enableHashtags', {
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: 'enableHashtagsFieldId',
|
||||
label: strings.HashtagsLabel,
|
||||
calloutContent: React.createElement('p', {}, strings.HashtagsDescription),
|
||||
onText: 'Enable',
|
||||
offText: 'Disable',
|
||||
checked: this.properties.enableHashtags !== undefined ? this.properties.enableHashtags : false
|
||||
}),
|
||||
PropertyFieldToggleWithCallout('enableDocumentPreview', {
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: 'enableDocumentPreviewFieldId',
|
||||
label: strings.DocumentPreviewLabel,
|
||||
calloutContent: React.createElement('p', {}, strings.DocumentPreviewDescription),
|
||||
onText: 'Enable',
|
||||
offText: 'Disable',
|
||||
checked: this.properties.enableDocumentPreview !== undefined ? this.properties.enableDocumentPreview : false
|
||||
}),
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,281 @@
|
||||
import { sp } from "@pnp/sp";
|
||||
import "@pnp/sp/webs";
|
||||
import "@pnp/sp/lists/web";
|
||||
import "@pnp/sp/folders/web";
|
||||
import "@pnp/sp/files/folder";
|
||||
import "@pnp/sp/items/list";
|
||||
import "@pnp/sp/fields/list";
|
||||
import "@pnp/sp/views/list";
|
||||
import "@pnp/sp/site-users/web";
|
||||
import { IList } from "@pnp/sp/lists";
|
||||
import * as _ from "lodash";
|
||||
|
||||
export default class SPHelper {
|
||||
private lst_pageComments: string = '';
|
||||
private lst_pageDocuments: string = '';
|
||||
private lst_docListName: string = '';
|
||||
private _list: IList = null;
|
||||
private _doclist: IList = null;
|
||||
private cqPostDocs: string = `<View>
|
||||
<Query>
|
||||
<Where>
|
||||
<And>
|
||||
<Eq>
|
||||
<FieldRef Name='FSObjType' />
|
||||
<Value Type='Text'>1</Value>
|
||||
</Eq>
|
||||
<Eq>
|
||||
<FieldRef Name='FileRef' />
|
||||
<Value Type='Text'>{{FilePath}}</Value>
|
||||
</Eq>
|
||||
</And>
|
||||
</Where>
|
||||
<ViewFields><FieldRef Name="ID" /></ViewFields>
|
||||
</Query>
|
||||
</View>`;
|
||||
|
||||
public constructor(lstDocLib?: string) {
|
||||
this.lst_pageComments = "Page Comments";
|
||||
this._list = sp.web.lists.getByTitle(this.lst_pageComments);
|
||||
if (lstDocLib) {
|
||||
this.lst_pageDocuments = lstDocLib;
|
||||
}
|
||||
}
|
||||
|
||||
public getDocLibInfo = async () => {
|
||||
this._doclist = sp.web.lists.getById(this.lst_pageDocuments);
|
||||
let listInfo: any = await this._doclist.select('Title').get();
|
||||
this.lst_docListName = listInfo.Title;
|
||||
}
|
||||
|
||||
public queryList = async (query, $list: IList) => {
|
||||
return await $list.getItemsByCAMLQuery(query);
|
||||
}
|
||||
|
||||
public getCurrentUserInfo = async () => {
|
||||
let currentUserInfo = await sp.web.currentUser.get();
|
||||
return ({
|
||||
ID: currentUserInfo.Id,
|
||||
Email: currentUserInfo.Email,
|
||||
LoginName: currentUserInfo.LoginName,
|
||||
DisplayName: currentUserInfo.Title,
|
||||
Picture: '/_layouts/15/userphoto.aspx?size=S&username=' + currentUserInfo.UserPrincipalName,
|
||||
});
|
||||
}
|
||||
|
||||
public getSiteUsers = async (currentUserId: number) => {
|
||||
let resusers = await sp.web.siteUsers.filter('IsHiddenInUI eq false and PrincipalType eq 1').get();
|
||||
_.remove(resusers, (o) => { return o.Id == currentUserId || o.Email == ""; });
|
||||
let userResults = [];
|
||||
resusers.map((user) => {
|
||||
userResults.push({
|
||||
id: user.Id,
|
||||
fullname: user.Title,
|
||||
email: user.Email,
|
||||
profile_picture_url: '/_layouts/15/userphoto.aspx?size=S&username=' + user.UserPrincipalName
|
||||
});
|
||||
});
|
||||
return userResults;
|
||||
}
|
||||
|
||||
public getPostAttachmentFilePath = async (pageUrl) => {
|
||||
let pageName = pageUrl.split('/')[pageUrl.split('/').length - 1].split('.').slice(0, -1).join('.');
|
||||
let res = await sp.web.select('ServerRelativeUrl').get();
|
||||
let doclistName = (this.lst_docListName.toLowerCase() === 'documents') ? "Shared Documents" : this.lst_docListName;
|
||||
return res.ServerRelativeUrl + "/" + doclistName + "/" + pageName;
|
||||
}
|
||||
|
||||
public checkForPageFolder = async (postAttachmentPath) => {
|
||||
let xml = this.cqPostDocs.replace('{{FilePath}}', postAttachmentPath);
|
||||
let q = {
|
||||
ViewXml: xml
|
||||
};
|
||||
let res = await this.queryList(q, this._doclist);
|
||||
if (res.length > 0) return true; else return false;
|
||||
}
|
||||
|
||||
public getPostComments = async (pageurl, currentUserInfo) => {
|
||||
let pagecomments = await this._list.items.select('Comments', 'Likes', 'FieldValuesAsText/Comments', 'FieldValuesAsText/Likes')
|
||||
.filter(`PageURL eq '${pageurl}'`).expand('FieldValuesAsText').get();
|
||||
if (pagecomments.length > 0) {
|
||||
var tempComments = pagecomments[0].FieldValuesAsText.Comments;
|
||||
var tempLikes = pagecomments[0].FieldValuesAsText.Likes;
|
||||
if (tempLikes != undefined && tempLikes != null && tempLikes !== "") tempLikes = JSON.parse(tempLikes);
|
||||
else tempLikes = [];
|
||||
if (tempComments != undefined && tempComments != null && tempComments !== "") {
|
||||
var jsonComments = JSON.parse(tempComments);
|
||||
if (tempLikes.length > 0) {
|
||||
tempLikes.map((liked) => {
|
||||
var fil = _.find(jsonComments, (o) => { return o.id == liked.commentID; });
|
||||
if (fil !== undefined && fil !== null) {
|
||||
fil.upvote_count = liked.userVote.length;
|
||||
var cufil = _.find(liked.userVote, (o) => { return o.userid == currentUserInfo.ID; });
|
||||
if (cufil !== undefined && cufil !== null) fil.user_has_upvoted = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
return jsonComments;
|
||||
} else return [];
|
||||
} else return [];
|
||||
}
|
||||
|
||||
public getComment = async (pageurl) => {
|
||||
let pagecomments = await this._list.items.select('Comments', 'FieldValuesAsText/Comments')
|
||||
.filter(`PageURL eq '${pageurl}'`).expand('FieldValuesAsText').get();
|
||||
if (pagecomments.length > 0) return pagecomments[0].FieldValuesAsText.Comments;
|
||||
else return null;
|
||||
}
|
||||
|
||||
public addComment = async (pageUrl, comments) => {
|
||||
let pageName = pageUrl.split('/')[pageUrl.split('/').length - 1];
|
||||
let commentsToAdd = await sp.web.lists.getByTitle(this.lst_pageComments).items.add({
|
||||
Title: pageName,
|
||||
PageURL: pageUrl,
|
||||
Comments: JSON.stringify(comments)
|
||||
});
|
||||
return commentsToAdd;
|
||||
}
|
||||
|
||||
public updateComment = async (pageurl, comments) => {
|
||||
let pageComment = await this._list.items.select('ID', 'PageURL').filter(`PageURL eq '${pageurl}'`).get();
|
||||
if (comments.length > 0) {
|
||||
if (pageComment.length > 0) {
|
||||
let pageCommentsToUpdate = await this._list.items.getById(pageComment[0].ID).update({
|
||||
Comments: JSON.stringify(comments)
|
||||
});
|
||||
return pageCommentsToUpdate;
|
||||
}
|
||||
} else {
|
||||
return await this._list.items.getById(pageComment[0].ID).delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public postComment = async (pageurl, commentJson, currentUserInfo) => {
|
||||
commentJson.created_by_current_user = false;
|
||||
let comments = await this.getPostComments(pageurl, currentUserInfo);
|
||||
if (comments.length > 0) {
|
||||
comments.push(commentJson);
|
||||
let updateComments = await this.updateComment(pageurl, comments);
|
||||
return updateComments;
|
||||
} else {
|
||||
comments.push(commentJson);
|
||||
let addComments = await this.addComment(pageurl, comments);
|
||||
return addComments;
|
||||
}
|
||||
}
|
||||
|
||||
public addVoteForComment = async (pageurl, commentJson, currentUserInfo) => {
|
||||
var tempLikes = [];
|
||||
tempLikes.push({
|
||||
commentID: commentJson.id,
|
||||
userVote: [{ userid: currentUserInfo.ID, name: currentUserInfo.DisplayName }]
|
||||
});
|
||||
let pageComment = await this._list.items.select('ID').filter(`PageURL eq '${pageurl}'`).get();
|
||||
if (pageComment.length > 0) {
|
||||
return await this._list.items.getById(pageComment[0].ID).update({ Likes: JSON.stringify(tempLikes) });
|
||||
}
|
||||
}
|
||||
|
||||
public updateVoteForComment = async (pageurl, jsonLikes) => {
|
||||
let pageComment = await this._list.items.select('ID').filter(`PageURL eq '${pageurl}'`).get();
|
||||
if (pageComment.length > 0) {
|
||||
return await this._list.items.getById(pageComment[0].ID).update({ Likes: JSON.stringify(jsonLikes) });
|
||||
}
|
||||
}
|
||||
|
||||
public voteComment = async (pageurl, commentJson, currentUserInfo) => {
|
||||
let res = await this._list.items.select('Likes', 'FieldValuesAsText/Likes').filter(`PageURL eq '${pageurl}'`).expand('FieldValuesAsText').get();
|
||||
if (res.length > 0) {
|
||||
var tempLikes = res[0].FieldValuesAsText.Likes;
|
||||
if (tempLikes != undefined && tempLikes != null && tempLikes !== "") {
|
||||
// Likes already exits so update the item
|
||||
var jsonLikes = JSON.parse(tempLikes);
|
||||
var userAlreadyVoted = _.find(jsonLikes, (o) => { return o.commentID == commentJson.id && _.find(o.userVote, (oo) => { return oo.userid == currentUserInfo.ID; }); });
|
||||
var userPresent = (userAlreadyVoted === undefined || userAlreadyVoted == null) ? false : true;
|
||||
var fil = _.find(jsonLikes, (o) => { return o.commentID == commentJson.id; });
|
||||
if (fil !== undefined && fil !== null) {
|
||||
// Found likes for the comment id
|
||||
if (commentJson.user_has_upvoted) {
|
||||
if (!userPresent) fil.userVote = _.concat(fil.userVote, { userid: currentUserInfo.ID, name: currentUserInfo.DisplayName });
|
||||
} else {
|
||||
if (userPresent) {
|
||||
if (fil !== undefined && fil !== null) _.remove(fil.userVote, (o) => { return o['userid'] == currentUserInfo.ID; });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No likes found for the comment id
|
||||
jsonLikes.push({ commentID: commentJson.id, userVote: [{ userid: currentUserInfo.ID, name: currentUserInfo.DisplayName }] });
|
||||
}
|
||||
return await this.updateVoteForComment(pageurl, jsonLikes);
|
||||
} else {
|
||||
// Likes doesn't exists so add new
|
||||
if (commentJson.user_has_upvoted) return await this.addVoteForComment(pageurl, commentJson, currentUserInfo);
|
||||
}
|
||||
} else {
|
||||
return commentJson;
|
||||
}
|
||||
}
|
||||
|
||||
public deleteComment = async (pageurl, commentJson) => {
|
||||
let comments = await this.getComment(pageurl);
|
||||
if (comments !== undefined && comments !== null) {
|
||||
var jsonComments = JSON.parse(comments);
|
||||
_.remove(jsonComments, (o) => { return o['id'] == commentJson.id; });
|
||||
return await this.updateComment(pageurl, jsonComments);
|
||||
}
|
||||
}
|
||||
|
||||
public editComments = async (pageurl, commentJson) => {
|
||||
let comment = await this.getComment(pageurl);
|
||||
if (comment !== undefined && comment !== null) {
|
||||
var jsonComments = JSON.parse(comment);
|
||||
var match = _.find(jsonComments, (o) => { return o.id == commentJson.id; });
|
||||
if (match) _.merge(match, { pings: commentJson.pings, content: commentJson.content, modified: commentJson.modified });
|
||||
return await this.updateComment(pageurl, jsonComments);
|
||||
}
|
||||
}
|
||||
|
||||
public createFolder = async (folderPath) => {
|
||||
return await sp.web.folders.add(folderPath);
|
||||
}
|
||||
|
||||
public uploadFileToFolder = async (folderpath, fileinfo) => {
|
||||
return await sp.web.getFolderByServerRelativeUrl(folderpath).files.add(fileinfo.name, fileinfo.content, true);
|
||||
}
|
||||
|
||||
public postAttachments = async (commentArray: any[], pageFolderExists, postAttachmentPath): Promise<any> => {
|
||||
var self = this;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!pageFolderExists) await this.createFolder(postAttachmentPath);
|
||||
var reader = new FileReader();
|
||||
reader.onload = async () => {
|
||||
var contentBuffer = reader.result;
|
||||
let uploadedFile = await self.uploadFileToFolder(postAttachmentPath, { name: commentArray[0].file.name, content: contentBuffer });
|
||||
_.set(commentArray[0], 'file_id', uploadedFile.data.UniqueId);
|
||||
_.set(commentArray[0], 'file_url', postAttachmentPath + "/" + commentArray[0].file.name);
|
||||
resolve(commentArray);
|
||||
};
|
||||
await reader.readAsArrayBuffer(commentArray[0].file);
|
||||
});
|
||||
}
|
||||
|
||||
public checkListExists = async (): Promise<boolean> => {
|
||||
return new Promise<boolean>(async (res, rej) => {
|
||||
sp.web.lists.getByTitle(this.lst_pageComments).get().then((listExists) => {
|
||||
res(true);
|
||||
}).catch(async err => {
|
||||
let listExists = await (await sp.web.lists.ensure(this.lst_pageComments)).list;
|
||||
await listExists.fields.addText('PageURL', 255, { Required: true, Description: '' });
|
||||
await listExists.fields.addMultilineText('Comments', 6, false, false, false, false, { Required: true, Description: '' });
|
||||
await listExists.fields.addMultilineText('Likes', 6, false, false, false, false, { Required: false, Description: '' });
|
||||
let allItemsView = await listExists.views.getByTitle('All Items');
|
||||
await allItemsView.fields.add('PageURL');
|
||||
await allItemsView.fields.add('Comments');
|
||||
await allItemsView.fields.add('Likes');
|
||||
res(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,789 @@
|
||||
/*jquery-comments.js 1.4.0
|
||||
|
||||
(c) 2017 Joona Tykkyläinen, Viima Solutions Oy
|
||||
jquery-comments may be freely distributed under the MIT license.
|
||||
For all details and documentation:
|
||||
http://viima.github.io/jquery-comments/*/
|
||||
|
||||
.jquery-comments * {
|
||||
box-sizing: border-box;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.jquery-comments a[href] {
|
||||
color: #2793e6;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.jquery-comments a[href]:hover {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
.jquery-comments a[href]:visited {
|
||||
text-decoration: none;
|
||||
color: #2793e6;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea, .jquery-comments input, .jquery-comments button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
-ms-appearance: none;
|
||||
appearance: none;
|
||||
|
||||
vertical-align: top;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.jquery-comments button {
|
||||
vertical-align: inherit;
|
||||
}
|
||||
|
||||
.jquery-comments .tag {
|
||||
color: inherit;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.2em;
|
||||
background: #ddd;
|
||||
border: 1px solid #ccc;
|
||||
padding: 0.1em 0.3em;
|
||||
cursor: pointer;
|
||||
font-weight: normal;
|
||||
border-radius: 1em;
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
|
||||
.jquery-comments .tag:hover {
|
||||
text-decoration: none;
|
||||
background-color: #d8edf8;
|
||||
border-color: #2793e6;
|
||||
}
|
||||
|
||||
.jquery-comments [contentEditable=true]:empty:not(:focus):before{
|
||||
content:attr(data-placeholder);
|
||||
color: #CCC;
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
.jquery-comments i.fa {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.jquery-comments i.fa.image:before {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.jquery-comments .spinner {
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
padding: 0.5em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.jquery-comments ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.jquery-comments .profile-picture {
|
||||
float: left;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
max-width: 50px;
|
||||
max-height: 50px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
.jquery-comments i.profile-picture {
|
||||
font-size: 3.4em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jquery-comments .profile-picture.round {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.jquery-comments .commenting-field.main{
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
|
||||
.jquery-comments .commenting-field.main .profile-picture {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper {
|
||||
overflow: hidden;
|
||||
padding-left: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper:before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
border: 5px solid #D5D5D5;
|
||||
left: 5px;
|
||||
top: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
box-sizing: border-box;
|
||||
border-bottom-color: rgba(0, 0, 0, 0);
|
||||
border-left-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper:after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
border: 7px solid #FFF;
|
||||
left: 7px;
|
||||
top: 1px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
box-sizing: border-box;
|
||||
border-bottom-color: rgba(0, 0, 0, 0);
|
||||
border-left-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .inline-button {
|
||||
cursor: pointer;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
border: .5em solid rgba(0,0,0,0);
|
||||
box-sizing: content-box;
|
||||
font-size: inherit;
|
||||
overflow: hidden;
|
||||
opacity: 0.5;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .inline-button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.jquery-comments:not(.mobile) .commenting-field-scrollable .textarea-wrapper .inline-button {
|
||||
margin-right: 15px; /* Because of scrollbar */
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .upload.inline-button i {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .upload input {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
min-width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .close {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .close span {
|
||||
background: #999;
|
||||
width: 25%;
|
||||
left: 37.5%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
-ms-transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .close .right {
|
||||
-ms-transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .textarea {
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
cursor: text;
|
||||
|
||||
border: 1px solid #CCC;;
|
||||
background: #FFF;
|
||||
font-size: 1em;
|
||||
line-height: 1.45em;
|
||||
padding: .25em .8em;
|
||||
padding-right: 2em;
|
||||
}
|
||||
|
||||
.jquery-comments:not(.mobile) .commenting-field-scrollable .textarea-wrapper .textarea {
|
||||
padding-right: calc(2em + 15px); /* Because of scrollbar */
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .control-row > span {
|
||||
float: right;
|
||||
|
||||
color: #FFF;
|
||||
padding: 0 1em;
|
||||
font-size: 1em;
|
||||
line-height: 1.6em;
|
||||
margin-top: .4em;
|
||||
border: 1px solid rgba(0, 0, 0, 0);
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .control-row > span:not(:first-child) {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .control-row > span.enabled {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .control-row > span:not(.enabled) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .control-row > span.enabled:hover {
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
.jquery-comments .textarea-wrapper .control-row > span.upload {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation {
|
||||
clear: both;
|
||||
|
||||
color: #CCC;
|
||||
border-bottom: 2px solid #CCC;
|
||||
line-height: 2em;
|
||||
font-size: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation .navigation-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation li {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0 1em;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation li.active,
|
||||
.jquery-comments ul.navigation li:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation li.active:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: #000;
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation li[data-sort-key="attachments"] {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation li[data-sort-key="attachments"] i {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation .navigation-wrapper.responsive {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.jquery-comments ul.navigation .navigation-wrapper {
|
||||
display: none;
|
||||
}
|
||||
.jquery-comments ul.navigation .navigation-wrapper.responsive {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.jquery-comments.responsive ul.navigation .navigation-wrapper {
|
||||
display: none;
|
||||
}
|
||||
.jquery-comments.responsive ul.navigation .navigation-wrapper.responsive {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation .navigation-wrapper.responsive li.title {
|
||||
padding: 0 1.5em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation .navigation-wrapper.responsive li.title header:after {
|
||||
display: inline-block;
|
||||
content: "";
|
||||
border-left: 0.3em solid rgba(0, 0, 0, 0) !important;
|
||||
border-right: 0.3em solid rgba(0, 0, 0, 0) !important;
|
||||
border-top: 0.4em solid #CCC;
|
||||
margin-left: 0.5em;
|
||||
position: relative;
|
||||
top: -0.1em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation .navigation-wrapper.responsive li.title.active header:after,
|
||||
.jquery-comments ul.navigation .navigation-wrapper.responsive li.title:hover header:after {
|
||||
border-top-color: #000;
|
||||
}
|
||||
|
||||
.jquery-comments ul.dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background: #FFF;
|
||||
z-index: 99;
|
||||
line-height: 1.2em;
|
||||
|
||||
border: 1px solid #CCC;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
-moz-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
-ms-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
}
|
||||
|
||||
.jquery-comments ul.dropdown.autocomplete {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.dropdown li {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
clear: both;
|
||||
padding: 0.6em;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jquery-comments ul.dropdown li.active {
|
||||
background: #EEE;
|
||||
}
|
||||
|
||||
.jquery-comments ul.dropdown li a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.jquery-comments ul.dropdown li .profile-picture {
|
||||
float: left;
|
||||
width: 2.4em;
|
||||
height: 2.4em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.dropdown li .details {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.jquery-comments ul.dropdown li .name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.jquery-comments ul.dropdown li .details.no-email {
|
||||
line-height: 2.4em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.dropdown li .email {
|
||||
color: #999;
|
||||
font-size: 0.95em;
|
||||
margin-top: 0.1em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation .navigation-wrapper.responsive ul.dropdown {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation .navigation-wrapper.responsive ul.dropdown li {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation .navigation-wrapper.responsive ul.dropdown li.active {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation .navigation-wrapper.responsive ul.dropdown li:hover:not(.active) {
|
||||
background: #F5F5F5;
|
||||
}
|
||||
|
||||
.jquery-comments ul.navigation .navigation-wrapper.responsive ul.dropdown li:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jquery-comments .no-data {
|
||||
display: none;
|
||||
margin: 1em;
|
||||
text-align: center;
|
||||
font-size: 1.5em;
|
||||
color: #CCC;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main:empty ~ .no-comments {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.jquery-comments ul#attachment-list:empty ~ .no-attachments {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .comment-wrapper,
|
||||
.jquery-comments ul.main li.toggle-all,
|
||||
.jquery-comments ul.main li.comment .commenting-field {
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .comment-wrapper {
|
||||
border-top: 1px solid #DDD;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main > li.comment:first-child > .comment-wrapper {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .comment-wrapper > .profile-picture {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment time {
|
||||
float: right;
|
||||
line-height: 1.4em;
|
||||
margin-left: .5em;
|
||||
font-size: 0.8em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .name {
|
||||
line-height: 1.4em;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .name a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .name .reply-to {
|
||||
color: #999;
|
||||
font-size: .8em;
|
||||
font-weight: normal;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .name .reply-to i {
|
||||
margin-left: .5em;
|
||||
margin-right: .25em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .name .new {
|
||||
margin-left: .5em;
|
||||
background: #2793e6;
|
||||
font-size: 0.8em;
|
||||
padding: 0.2em 0.5em;
|
||||
color: #fff;
|
||||
font-weight: normal;
|
||||
border-radius: 1em;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .wrapper{
|
||||
line-height: 1.4em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.jquery-comments.mobile ul.main li.comment .child-comments li.comment .wrapper{
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
|
||||
/* Content */
|
||||
.jquery-comments ul.main li.comment .wrapper .content {
|
||||
white-space: pre-line;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .wrapper .content a.attachment i {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .wrapper .content a.attachment > * {
|
||||
max-width: 100%;
|
||||
max-height: 300px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin-top: .25em;
|
||||
margin-bottom: .25em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .wrapper .content time.edited {
|
||||
float: inherit;
|
||||
margin: 0;
|
||||
font-size: .9em;
|
||||
font-style: italic;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .wrapper .content time.edited:before {
|
||||
content: " - ";
|
||||
}
|
||||
|
||||
|
||||
/* Actions */
|
||||
.jquery-comments.mobile ul.main li.comment .actions {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .actions > * {
|
||||
color: #999;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .actions .action {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
line-height: 1.5em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .actions .action:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .actions .action.upvote {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .actions .action.upvote .upvote-count {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .actions .action.upvote .upvote-count:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .actions .action.upvote i {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .actions .action:not(.upvote):hover,
|
||||
.jquery-comments ul.main li.comment .actions .action.upvote:not(.highlight-font) i:hover {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .actions .action.delete {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .actions .action.delete.enabled {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.jquery-comments ul#attachment-list li.comment .actions .action:not(.delete) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jquery-comments ul#attachment-list li.comment .actions .action.delete {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.jquery-comments ul#attachment-list li.comment .actions .separator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* Child comments */
|
||||
.jquery-comments ul.main li.comment .child-comments > *:before { /* Margin for second level content */
|
||||
content: "";
|
||||
height: 1px;
|
||||
float: left;
|
||||
|
||||
width: calc(3.6em + .5em); /* Profile picture width plus margin */
|
||||
max-width: calc(50px + .5em); /* Profile picture max width plus margin */
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .child-comments .profile-picture {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .child-comments i.profile-picture {
|
||||
font-size: 2.4em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .child-comments li.toggle-all {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .child-comments li.toggle-all span:first-child {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .child-comments li.toggle-all span:first-child:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .child-comments li.toggle-all .caret {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
margin-left: .5em;
|
||||
border: .3em solid;
|
||||
margin-top: .35em;
|
||||
|
||||
border-left-color: rgba(0, 0, 0, 0);
|
||||
border-bottom-color: rgba(0, 0, 0, 0);
|
||||
border-right-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .child-comments li.toggle-all .caret.up {
|
||||
border-top-color: rgba(0, 0, 0, 0);
|
||||
border-bottom-color: inherit;
|
||||
margin-top: -.2em;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .child-comments .togglable-reply {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment .child-comments .visible {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Editing comment */
|
||||
.jquery-comments ul.main li.comment.edit > .comment-wrapper > *:not(.commenting-field) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jquery-comments ul.main li.comment.edit > .comment-wrapper .commenting-field {
|
||||
padding: 0 !important; /* Parent element has the padding */
|
||||
}
|
||||
|
||||
/* Drag & drop attachments */
|
||||
.jquery-comments.drag-ongoing {
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
||||
.jquery-comments .droppable-overlay {
|
||||
display: table;
|
||||
position: fixed;
|
||||
z-index: 99;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.3)
|
||||
}
|
||||
|
||||
.jquery-comments .droppable-overlay .droppable-container {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jquery-comments .droppable-overlay .droppable-container .droppable {
|
||||
background: #FFF;
|
||||
color: #CCC;
|
||||
padding: 6em;
|
||||
}
|
||||
|
||||
.jquery-comments .droppable-overlay .droppable-container .droppable.drag-over {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.jquery-comments .droppable-overlay .droppable-container .droppable i {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Read-only mode */
|
||||
.jquery-comments.read-only .commenting-field {
|
||||
display: none;
|
||||
}
|
||||
.jquery-comments.read-only .actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.displayctrl {
|
||||
display: '' !important;
|
||||
}
|
||||
.hidectrl {
|
||||
display: none !important;
|
||||
}
|
||||
.greenColor {
|
||||
color: green;
|
||||
}
|
||||
.blueColor {
|
||||
color: blue;
|
||||
}
|
||||
.redColor {
|
||||
color: red;
|
||||
}
|
||||
.yellowColor {
|
||||
color: yellow;
|
||||
}
|
||||
.jquery-comments .msgContainer {
|
||||
margin-top: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
.jquery-comments .errorMsg {
|
||||
background: #f3c4c4;
|
||||
}
|
||||
.jquery-comments .msgAlert {
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
}
|
2473
samples/js-advanced-commenting/src/webparts/pageComments/js/jquery-comments.js
vendored
Normal file
2473
samples/js-advanced-commenting/src/webparts/pageComments/js/jquery-comments.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
samples/js-advanced-commenting/src/webparts/pageComments/js/jquery-comments.min.js
vendored
Normal file
1
samples/js-advanced-commenting/src/webparts/pageComments/js/jquery-comments.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,38 @@
|
||||
define([], function () {
|
||||
return {
|
||||
"PropertyPaneDescription": "",
|
||||
"BasicGroupName": "Settings",
|
||||
DateTimeFormatLabel: "Datetime format",
|
||||
DateTimeFormatDescription: "Please use the moment datetime format option.",
|
||||
RoundProfilePicLabel: "Profile Picture rounded",
|
||||
RoundProfilePicDescription: "Enable to display the user profile picture as rounded.",
|
||||
NavigationLabel: "Navigation",
|
||||
NavigationDescription: "Tabs like Newest, Oldest, Popular & Attachments will be displayed.",
|
||||
AttachmentLabel: "Attachments",
|
||||
AttachmentDescription: "Allow the users to upload files.",
|
||||
PingLabel: "Ping users",
|
||||
PingDescription: "Allow users to ping other users in the comments.",
|
||||
EditingLabel: "Edit Comments",
|
||||
EditingDescription: "Allow the users to edit their own comments.",
|
||||
UpVotingLabel: "Upvoting of comments",
|
||||
UpVotingDescription: "Allow the users to upvote the comments.",
|
||||
ReplyLabel: "Reply to comments",
|
||||
ReplyDescription: "Allow the users to reply to the comments.",
|
||||
DeleteLabel: "Delete own comments",
|
||||
DeleteDescription: "Allow the users to delete their own comments.",
|
||||
DeleteRepliesLabel: "Delete own comments with replies",
|
||||
DeleteRepliesDescription: "Allow the users to delete their own comments with replies.",
|
||||
HashtagsLabel: "Hashtags",
|
||||
HashtagsDescription: "Allow the users to use hashtags.",
|
||||
DocumentPreviewLabel: "Document Preview",
|
||||
DocumentPreviewDescription: "Allow the users to view few of the document formats inline.",
|
||||
AttachmentFileFormatLabel: "Allowed Files",
|
||||
AttachmentFileFormatDescription: "Allowed files to attach. Please enter file extensions separated by ,",
|
||||
AttachmentFileSizeLabel: "Max File Size",
|
||||
AttachmentFileSizeDescription: "Maximum file size(MB) allowed to attach.",
|
||||
AttachmentRepoLabel: "Select a libary",
|
||||
AttachmentRepoPropValMsg: "Please choose the document library",
|
||||
NoAttachmentRepoMsg: "Please select the document library to store the attachments.",
|
||||
LoadingMsg: "Please wait..."
|
||||
}
|
||||
});
|
42
samples/js-advanced-commenting/src/webparts/pageComments/loc/mystrings.d.ts
vendored
Normal file
42
samples/js-advanced-commenting/src/webparts/pageComments/loc/mystrings.d.ts
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
declare interface IPageCommentsWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
|
||||
DateTimeFormatLabel: string;
|
||||
DateTimeFormatDescription: string;
|
||||
RoundProfilePicLabel: string;
|
||||
RoundProfilePicDescription: string;
|
||||
NavigationLabel: string;
|
||||
NavigationDescription: string;
|
||||
AttachmentLabel: string;
|
||||
AttachmentDescription: string;
|
||||
PingLabel: string;
|
||||
PingDescription: string;
|
||||
EditingLabel: string;
|
||||
EditingDescription: string;
|
||||
UpVotingLabel: string;
|
||||
UpVotingDescription: string;
|
||||
ReplyLabel: string;
|
||||
ReplyDescription: string;
|
||||
DeleteLabel: string;
|
||||
DeleteDescription: string;
|
||||
DeleteRepliesLabel: string;
|
||||
DeleteRepliesDescription: string;
|
||||
HashtagsLabel: string;
|
||||
HashtagsDescription: string;
|
||||
DocumentPreviewLabel: string;
|
||||
DocumentPreviewDescription: string;
|
||||
AttachmentFileFormatLabel: string;
|
||||
AttachmentFileFormatDescription: string;
|
||||
AttachmentFileSizeLabel: string;
|
||||
AttachmentFileSizeDescription: string;
|
||||
AttachmentRepoLabel: string;
|
||||
AttachmentRepoPropValMsg: string;
|
||||
NoAttachmentRepoMsg: string;
|
||||
LoadingMsg: string;
|
||||
}
|
||||
|
||||
declare module 'PageCommentsWebPartStrings' {
|
||||
const strings: IPageCommentsWebPartStrings;
|
||||
export = strings;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
38
samples/js-advanced-commenting/tsconfig.json
Normal file
38
samples/js-advanced-commenting/tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
30
samples/js-advanced-commenting/tslint.json
Normal file
30
samples/js-advanced-commenting/tslint.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
@ -3,9 +3,9 @@
|
||||
"packageManager": "pnpm",
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.7.1",
|
||||
"version": "1.10.0",
|
||||
"libraryName": "workbench-customizer",
|
||||
"libraryId": "5d6f4a5a-9d2b-4a93-a283-16b8f5ea75d6",
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,29 @@
|
||||
|
||||
This sample shows how the Workbench page can be customized to display in a way that better mimics a modern SharePoint page.
|
||||
This is done using CSS overrides on some of the page styles, which does not cause any negative impact on your site as the web part is not intended to be consumed by final users, only developers.
|
||||
The web part also has some properties that control which customizations are applied to the workbench page (all enabled by default).
|
||||
The web part also has some properties that control which customizations are applied to the workbench page (all enabled by default). There is also an option to switch the page to Preview after the page is loaded, which gives a UI more close to what end users will see on a published page - this is very useful when doing UI work.
|
||||
|
||||
![Demo](./assets/Preview.png)
|
||||
|
||||
## Usage
|
||||
|
||||
### Deploy to tenant
|
||||
|
||||
The easiest way to use the solution is to package it up and deploy to the App Catalog. You can then add the web part to the bottom of the O365 Workbench page when developing your custom solutions.
|
||||
|
||||
### Run locally
|
||||
|
||||
Alternatively, you can add the output files for the web part to a custom SPFx project and the web part will also be served and available both from the local or hosted Workbench page:
|
||||
|
||||
* Generate the output files for the solution by executing `gulp bundle --ship`
|
||||
* Copy all files from js-workbench-customizer\dist to the dist folder of your custom solution
|
||||
* Copy the workbenchCustomizer folder from js-workbench-customizer\lib\webparts to the corresponding webparts folder of your custom solution
|
||||
|
||||
Note: This approach will not "pollute" your solution with additional resources or dependencies as the SPFx toolchain will ignore those additional files by default when you package your solution. You can also clean everything by running `gulp clean` as both the lib and dist folders are deleted and recreated again. Git will also ignore dist and lib folders by default, so the files will never be added to source control.
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![drop](https://img.shields.io/badge/drop-1.7.1-green.svg)
|
||||
![drop](https://img.shields.io/badge/drop-1.10.0-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
@ -32,6 +48,7 @@ workbench-customizer|Joel Rodrigues
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|January 24, 2019|Initial release
|
||||
1.1|February 05, 2020|Update to SPFx 1.10.0
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "workbench-customizer",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
@ -11,22 +12,24 @@
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.7.1",
|
||||
"@microsoft/sp-lodash-subset": "1.7.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.7.1",
|
||||
"@microsoft/sp-webpart-base": "1.7.1",
|
||||
"@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/webpack-env": "1.13.1",
|
||||
"npm": "^6.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.7.1",
|
||||
"@microsoft/sp-tslint-rules": "1.7.1",
|
||||
"@microsoft/sp-module-interfaces": "1.7.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.7.1",
|
||||
"gulp": "~3.9.1",
|
||||
"@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-tslint-rules": "1.10.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.10.0",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1"
|
||||
}
|
||||
}
|
||||
|
11810
samples/js-workbench-customizer/pnpm-lock.yaml
generated
Normal file
11810
samples/js-workbench-customizer/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
"supportedHosts": ["SharePointWebPart"],
|
||||
|
||||
// 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.
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneToggle
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import { IPropertyPaneConfiguration, PropertyPaneToggle } from "@microsoft/sp-property-pane";
|
||||
import styles from './WorkbenchCustomizerWebPart.module.scss';
|
||||
|
||||
import * as strings from 'WorkbenchCustomizerWebPartStrings';
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
@ -10,6 +11,9 @@
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
|
338
samples/js-workbench-customizer/upgrade-report.md
Normal file
338
samples/js-workbench-customizer/upgrade-report.md
Normal file
@ -0,0 +1,338 @@
|
||||
# Upgrade project C:\Users\joelf\dev\GitHub\sp-dev-fx-webparts\samples\js-workbench-customizer to v1.10.0
|
||||
|
||||
Date: 1/21/2020
|
||||
|
||||
## Findings
|
||||
|
||||
Following is the list of steps required to upgrade your project to SharePoint Framework version 1.10.0. [Summary](#Summary) of the modifications is included at the end of the report.
|
||||
|
||||
### FN001001 @microsoft/sp-core-library | Required
|
||||
|
||||
Upgrade SharePoint Framework dependency package @microsoft/sp-core-library
|
||||
|
||||
Execute the following command:
|
||||
|
||||
```sh
|
||||
pnpm i -E @microsoft/sp-core-library@1.10.0
|
||||
```
|
||||
|
||||
File: [./package.json](./package.json)
|
||||
|
||||
### FN001002 @microsoft/sp-lodash-subset | Required
|
||||
|
||||
Upgrade SharePoint Framework dependency package @microsoft/sp-lodash-subset
|
||||
|
||||
Execute the following command:
|
||||
|
||||
```sh
|
||||
pnpm i -E @microsoft/sp-lodash-subset@1.10.0
|
||||
```
|
||||
|
||||
File: [./package.json](./package.json)
|
||||
|
||||
### FN001003 @microsoft/sp-office-ui-fabric-core | Required
|
||||
|
||||
Upgrade SharePoint Framework dependency package @microsoft/sp-office-ui-fabric-core
|
||||
|
||||
Execute the following command:
|
||||
|
||||
```sh
|
||||
pnpm i -E @microsoft/sp-office-ui-fabric-core@1.10.0
|
||||
```
|
||||
|
||||
File: [./package.json](./package.json)
|
||||
|
||||
### FN001004 @microsoft/sp-webpart-base | Required
|
||||
|
||||
Upgrade SharePoint Framework dependency package @microsoft/sp-webpart-base
|
||||
|
||||
Execute the following command:
|
||||
|
||||
```sh
|
||||
pnpm i -E @microsoft/sp-webpart-base@1.10.0
|
||||
```
|
||||
|
||||
File: [./package.json](./package.json)
|
||||
|
||||
### FN001021 @microsoft/sp-property-pane | Required
|
||||
|
||||
Install SharePoint Framework dependency package @microsoft/sp-property-pane
|
||||
|
||||
Execute the following command:
|
||||
|
||||
```sh
|
||||
pnpm i -E @microsoft/sp-property-pane@1.10.0
|
||||
```
|
||||
|
||||
File: [./package.json](./package.json)
|
||||
|
||||
### FN002001 @microsoft/sp-build-web | Required
|
||||
|
||||
Upgrade SharePoint Framework dev dependency package @microsoft/sp-build-web
|
||||
|
||||
Execute the following command:
|
||||
|
||||
```sh
|
||||
pnpm i -DE @microsoft/sp-build-web@1.10.0
|
||||
```
|
||||
|
||||
File: [./package.json](./package.json)
|
||||
|
||||
### FN002002 @microsoft/sp-module-interfaces | Required
|
||||
|
||||
Upgrade SharePoint Framework dev dependency package @microsoft/sp-module-interfaces
|
||||
|
||||
Execute the following command:
|
||||
|
||||
```sh
|
||||
pnpm i -DE @microsoft/sp-module-interfaces@1.10.0
|
||||
```
|
||||
|
||||
File: [./package.json](./package.json)
|
||||
|
||||
### FN002003 @microsoft/sp-webpart-workbench | Required
|
||||
|
||||
Upgrade SharePoint Framework dev dependency package @microsoft/sp-webpart-workbench
|
||||
|
||||
Execute the following command:
|
||||
|
||||
```sh
|
||||
pnpm i -DE @microsoft/sp-webpart-workbench@1.10.0
|
||||
```
|
||||
|
||||
File: [./package.json](./package.json)
|
||||
|
||||
### FN002009 @microsoft/sp-tslint-rules | Required
|
||||
|
||||
Upgrade SharePoint Framework dev dependency package @microsoft/sp-tslint-rules
|
||||
|
||||
Execute the following command:
|
||||
|
||||
```sh
|
||||
pnpm i -DE @microsoft/sp-tslint-rules@1.10.0
|
||||
```
|
||||
|
||||
File: [./package.json](./package.json)
|
||||
|
||||
### FN002012 @microsoft/rush-stack-compiler-3.3 | Required
|
||||
|
||||
Install SharePoint Framework dev dependency package @microsoft/rush-stack-compiler-3.3
|
||||
|
||||
Execute the following command:
|
||||
|
||||
```sh
|
||||
pnpm i -DE @microsoft/rush-stack-compiler-3.3@0.3.5
|
||||
```
|
||||
|
||||
File: [./package.json](./package.json)
|
||||
|
||||
### FN010001 .yo-rc.json version | Recommended
|
||||
|
||||
Update version in .yo-rc.json
|
||||
|
||||
In file [./.yo-rc.json](./.yo-rc.json) update the code as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.10.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
File: [./.yo-rc.json](./.yo-rc.json)
|
||||
|
||||
### FN012017 tsconfig.json extends property | Required
|
||||
|
||||
Update tsconfig.json extends property
|
||||
|
||||
In file [./tsconfig.json](./tsconfig.json) update the code as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json"
|
||||
}
|
||||
```
|
||||
|
||||
File: [./tsconfig.json](./tsconfig.json)
|
||||
|
||||
### FN016004 Property pane property import change to @microsoft/sp-property-pane | Required
|
||||
|
||||
Refactor the code to import property pane property from the @microsoft/sp-property-pane npm package instead of the @microsoft/sp-webpart-base package
|
||||
|
||||
In file [src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.ts](src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.ts) update the code as follows:
|
||||
|
||||
```ts
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import { IPropertyPaneConfiguration, PropertyPaneToggle } from "@microsoft/sp-property-pane";
|
||||
```
|
||||
|
||||
File: [src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.ts:2:1](src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.ts)
|
||||
|
||||
### FN021001 main | Required
|
||||
|
||||
Add package.json property
|
||||
|
||||
In file [./package.json](./package.json) update the code as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"main": "lib/index.js"
|
||||
}
|
||||
```
|
||||
|
||||
File: [./package.json](./package.json)
|
||||
|
||||
### FN011011 Web part manifest supportedHosts | Required
|
||||
|
||||
Update the supportedHosts property in the manifest
|
||||
|
||||
In file [src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.manifest.json](src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.manifest.json) update the code as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"supportedHosts": ["SharePointWebPart"]
|
||||
}
|
||||
```
|
||||
|
||||
File: [src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.manifest.json](src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.manifest.json)
|
||||
|
||||
### FN012014 tsconfig.json compiler options inlineSources | Required
|
||||
|
||||
Update tsconfig.json inlineSources value
|
||||
|
||||
In file [./tsconfig.json](./tsconfig.json) update the code as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"inlineSources": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
File: [./tsconfig.json](./tsconfig.json)
|
||||
|
||||
### FN012015 tsconfig.json compiler options strictNullChecks | Required
|
||||
|
||||
Update tsconfig.json strictNullChecks value
|
||||
|
||||
In file [./tsconfig.json](./tsconfig.json) update the code as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
File: [./tsconfig.json](./tsconfig.json)
|
||||
|
||||
### FN012016 tsconfig.json compiler options noUnusedLocals | Required
|
||||
|
||||
Update tsconfig.json noUnusedLocals value
|
||||
|
||||
In file [./tsconfig.json](./tsconfig.json) update the code as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noUnusedLocals": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
File: [./tsconfig.json](./tsconfig.json)
|
||||
|
||||
## Summary
|
||||
|
||||
### Execute script
|
||||
|
||||
```sh
|
||||
pnpm i -E @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-webpart-base@1.10.0 @microsoft/sp-property-pane@1.10.0
|
||||
pnpm i -DE @microsoft/sp-build-web@1.10.0 @microsoft/sp-module-interfaces@1.10.0 @microsoft/sp-webpart-workbench@1.10.0 @microsoft/sp-tslint-rules@1.10.0 @microsoft/rush-stack-compiler-3.3@0.3.5
|
||||
```
|
||||
|
||||
### Modify files
|
||||
|
||||
#### [./.yo-rc.json](./.yo-rc.json)
|
||||
|
||||
Update version in .yo-rc.json:
|
||||
|
||||
```json
|
||||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.10.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### [./tsconfig.json](./tsconfig.json)
|
||||
|
||||
Update tsconfig.json extends property:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json"
|
||||
}
|
||||
```
|
||||
|
||||
Update tsconfig.json inlineSources value:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"inlineSources": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update tsconfig.json strictNullChecks value:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update tsconfig.json noUnusedLocals value:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noUnusedLocals": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### [src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.ts](src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.ts)
|
||||
|
||||
Refactor the code to import property pane property from the @microsoft/sp-property-pane npm package instead of the @microsoft/sp-webpart-base package:
|
||||
|
||||
```ts
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import { IPropertyPaneConfiguration, PropertyPaneToggle } from "@microsoft/sp-property-pane";
|
||||
```
|
||||
|
||||
#### [./package.json](./package.json)
|
||||
|
||||
Add package.json property:
|
||||
|
||||
```json
|
||||
{
|
||||
"main": "lib/index.js"
|
||||
}
|
||||
```
|
||||
|
||||
#### [src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.manifest.json](src\webparts\workbenchCustomizer\WorkbenchCustomizerWebPart.manifest.json)
|
||||
|
||||
Update the supportedHosts property in the manifest:
|
||||
|
||||
```json
|
||||
{
|
||||
"supportedHosts": ["SharePointWebPart"]
|
||||
}
|
||||
```
|
@ -1,8 +1,10 @@
|
||||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"libraryName": "react-async-await-sp-pnp-js",
|
||||
"framework": "react",
|
||||
"version": "1.0.2",
|
||||
"libraryId": "c3bd0bfa-1bee-4af0-ad3d-a72d02b4dc7c"
|
||||
"version": "1.10.0",
|
||||
"isDomainIsolated": false,
|
||||
"isCreatingSolution": true,
|
||||
"packageManager": "npm",
|
||||
"componentType": "webpart",
|
||||
"framework": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"async-await-pn-p-js-bundle": {
|
||||
|
@ -1,3 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-async-await-sp-pnp-js",
|
||||
|
@ -1,8 +1,11 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-async-await-sp-pnp-js-client-side-solution",
|
||||
"id": "c3bd0bfa-1bee-4af0-ad3d-a72d02b4dc7c",
|
||||
"version": "1.0.0.0"
|
||||
"version": "1.0.0.0",
|
||||
"isDomainIsolated": false,
|
||||
"includeClientSideAssets": true
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-async-await-sp-pnp-js.sppkg"
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"https": true,
|
||||
|
@ -1,45 +0,0 @@
|
||||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
|
||||
// Display errors as warnings
|
||||
"displayAsWarning": true,
|
||||
// The TSLint task may have been configured with several custom lint rules
|
||||
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
|
||||
// project). If true, this flag will deactivate any of these rules.
|
||||
"removeExistingRules": true,
|
||||
// When true, the TSLint task is configured with some default TSLint "rules.":
|
||||
"useDefaultConfigAsBase": false,
|
||||
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
|
||||
// which are active, other than the list of rules below.
|
||||
"lintConfig": {
|
||||
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
|
||||
"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-case": true,
|
||||
"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,
|
||||
"valid-typeof": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.initialize(gulp);
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
build.initialize(require('gulp'));
|
||||
|
||||
|
22428
samples/react-async-await-sp-pnp-js/package-lock.json
generated
22428
samples/react-async-await-sp-pnp-js/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,33 +1,43 @@
|
||||
{
|
||||
"name": "react-async-await-sp-pnp-js",
|
||||
"version": "0.0.1",
|
||||
"version": "1.1.10",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
"node": ">=0.10.13"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "~1.4.1",
|
||||
"@microsoft/sp-webpart-base": "~1.4.1",
|
||||
"@microsoft/rush-stack-compiler-3.2": "^0.6.2",
|
||||
"@microsoft/sp-core-library": "1.10.0",
|
||||
"@microsoft/sp-property-pane": "1.10.0",
|
||||
"@microsoft/sp-webpart-base": "1.10.0",
|
||||
"@pnp/common": "^1.1.2",
|
||||
"@pnp/logging": "^1.1.2",
|
||||
"@pnp/odata": "^1.1.2",
|
||||
"@pnp/sp": "^1.1.2",
|
||||
"@types/react": "0.14.46",
|
||||
"@types/react-addons-shallow-compare": "0.14.17",
|
||||
"@types/react-addons-test-utils": "0.14.15",
|
||||
"@types/react-addons-update": "0.14.14",
|
||||
"@types/react-dom": "0.14.18",
|
||||
"@types/webpack-env": ">=1.12.1 <1.14.0",
|
||||
"react": "15.4.2",
|
||||
"react-dom": "15.4.2"
|
||||
"@pnp/sp": "^2.0.2",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "16.8.8",
|
||||
"@types/react-dom": "16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"natives": "^1.1.6",
|
||||
"office-ui-fabric-react": "6.189.2",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "~1.4.1",
|
||||
"@microsoft/sp-module-interfaces": "~1.4.1",
|
||||
"@microsoft/sp-webpart-workbench": "~1.4.1",
|
||||
"@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-tslint-rules": "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",
|
||||
"@types/chai": ">=3.4.34 <3.6.0",
|
||||
"@types/mocha": ">=2.2.33 <2.6.0"
|
||||
"tslint-microsoft-contrib": "5.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.8.8"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
|
1
samples/react-async-await-sp-pnp-js/src/index.ts
Normal file
1
samples/react-async-await-sp-pnp-js/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
@ -1,15 +1,17 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
|
||||
"id": "c81d0661-6ead-4c3c-a3a0-261e2a7edd44",
|
||||
"alias": "AsyncAwaitPnPJsWebPart",
|
||||
"componentType": "WebPart",
|
||||
"version": "0.0.1",
|
||||
"manifestVersion": 2,
|
||||
"supportedHosts": ["SharePointWebPart"],
|
||||
"safeWithCustomScriptDisabled": false,
|
||||
"version": "*",
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "c81d0661-6ead-4c3c-a3a0-261e2a7edd44",
|
||||
"group": { "default": "Under Development" },
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "AsyncAwaitPnPJs" },
|
||||
"description": { "default": "AsyncAwaitPnPJs description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
|
@ -1,11 +1,13 @@
|
||||
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 {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
} from '@microsoft/sp-property-pane';
|
||||
|
||||
import { sp } from '@pnp/sp';
|
||||
|
||||
import * as strings from 'asyncAwaitPnPJsStrings';
|
||||
import AsyncAwaitPnPJs from './components/AsyncAwaitPnPJs';
|
||||
@ -17,14 +19,14 @@ import { IAsyncAwaitPnPJsWebPartProps } from './IAsyncAwaitPnPJsWebPartProps';
|
||||
export default class AsyncAwaitPnPJsWebPart extends BaseClientSideWebPart<IAsyncAwaitPnPJsWebPartProps> {
|
||||
|
||||
// // https://github.com/SharePoint/PnP-JS-Core/wiki/Using-sp-pnp-js-in-SharePoint-Framework
|
||||
// public onInit(): Promise<void> {
|
||||
// return super.onInit().then(_ => {
|
||||
// // establish SPFx context
|
||||
// pnp.setup({
|
||||
// spfxContext: this.context
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
public onInit(): Promise<void> {
|
||||
return super.onInit().then(_ => {
|
||||
// establish SPFx context
|
||||
sp.setup({
|
||||
spfxContext: this.context
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IAsyncAwaitPnPJsProps > = React.createElement(
|
||||
|
@ -1,4 +1,6 @@
|
||||
|
||||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.container {
|
||||
max-width: 700px;
|
||||
margin: 0px auto;
|
||||
|
@ -5,7 +5,10 @@ import styles from "./AsyncAwaitPnPJs.module.scss";
|
||||
import { IFile, IResponseItem } from "../interfaces";
|
||||
|
||||
// import pnp and pnp logging system
|
||||
import { Web } from "@pnp/sp";
|
||||
import { sp } from "@pnp/sp";
|
||||
import "@pnp/sp/webs";
|
||||
import "@pnp/sp/lists";
|
||||
import "@pnp/sp/items";
|
||||
import { Logger, LogLevel, LogEntry, FunctionListener } from "@pnp/logging";
|
||||
|
||||
// import SPFx Logging system
|
||||
@ -139,8 +142,8 @@ export default class AsyncAwaitPnPJs extends React.Component<IAsyncAwaitPnPJsPro
|
||||
// - .usingCaching() will be using SessionStorage by default to cache the results
|
||||
// - .get() always returns a promise
|
||||
// - await converts Promise<IResponseItem[]> into IResponse[]
|
||||
const web: Web = new Web(this.props.pageContext.web.absoluteUrl);
|
||||
const response: IResponseItem[] = await web.lists
|
||||
|
||||
const response: IResponseItem[] = await sp.web.lists
|
||||
.getByTitle(libraryName)
|
||||
.items
|
||||
.select("Title", "FileLeafRef", "File/Length")
|
||||
@ -161,10 +164,10 @@ export default class AsyncAwaitPnPJs extends React.Component<IAsyncAwaitPnPJsPro
|
||||
this.setState({ ...this.state, items });
|
||||
|
||||
// intentionally set wrong query to see console errors...
|
||||
const failResponse: IResponseItem[] = await web.lists
|
||||
const failResponse: IResponseItem[] = await sp.web.lists
|
||||
.getByTitle(libraryName)
|
||||
.items
|
||||
.select("Title", "FileLeafRef", "File/Length", "NonExistingColumn")
|
||||
.select("Title", "FileLeafRef", "File/Length")
|
||||
.expand("File/Length")
|
||||
.usingCaching()
|
||||
.get();
|
||||
|
@ -0,0 +1,46 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.2/MicrosoftTeams.schema.json",
|
||||
"manifestVersion": "1.2",
|
||||
"packageName": "AsyncAwaitPnPJs",
|
||||
"id": "c81d0661-6ead-4c3c-a3a0-261e2a7edd44",
|
||||
"version": "0.1",
|
||||
"developer": {
|
||||
"name": "SPFx + Teams Dev",
|
||||
"websiteUrl": "https://products.office.com/en-us/sharepoint/collaboration",
|
||||
"privacyUrl": "https://privacy.microsoft.com/en-us/privacystatement",
|
||||
"termsOfUseUrl": "https://www.microsoft.com/en-us/servicesagreement"
|
||||
},
|
||||
"name": {
|
||||
"short": "AsyncAwaitPnPJs"
|
||||
},
|
||||
"description": {
|
||||
"short": "AsyncAwaitPnPJs description",
|
||||
"full": "AsyncAwaitPnPJs description"
|
||||
},
|
||||
"icons": {
|
||||
"outline": "tab20x20.png",
|
||||
"color": "tab96x96.png"
|
||||
},
|
||||
"accentColor": "#004578",
|
||||
"configurableTabs": [
|
||||
{
|
||||
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=c81d0661-6ead-4c3c-a3a0-261e2a7edd44", "canUpdateConfiguration": true,
|
||||
"scopes": [
|
||||
"team"
|
||||
]
|
||||
}
|
||||
],
|
||||
"validDomains": [
|
||||
"*.login.microsoftonline.com",
|
||||
"*.sharepoint.com",
|
||||
"*.sharepoint-df.com",
|
||||
"spoppe-a.akamaihd.net",
|
||||
"spoprod-a.akamaihd.net",
|
||||
"resourceseng.blob.core.windows.net",
|
||||
"msft.spoppe.com"
|
||||
],
|
||||
"webApplicationInfo": {
|
||||
"resource": "https://{teamSiteDomain}",
|
||||
"id": "00000003-0000-0ff1-ce00-000000000000"
|
||||
}
|
||||
}
|
@ -1,13 +1,18 @@
|
||||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "commonjs",
|
||||
"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"
|
||||
@ -21,5 +26,12 @@
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
||||
|
@ -1,3 +1,30 @@
|
||||
{
|
||||
"rulesDirectory": "./config"
|
||||
}
|
||||
"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
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
// Type definitions for Microsoft ODSP projects
|
||||
// Project: ODSP
|
||||
|
||||
/* Global definition for UNIT_TEST builds
|
||||
Code that is wrapped inside an if(UNIT_TEST) {...}
|
||||
block will not be included in the final bundle when the
|
||||
--ship flag is specified */
|
||||
declare const UNIT_TEST: boolean;
|
@ -1 +0,0 @@
|
||||
/// <reference path="@ms/odsp.d.ts" />
|
25
samples/react-outlook-copy2teams/.editorconfig
Normal file
25
samples/react-outlook-copy2teams/.editorconfig
Normal file
@ -0,0 +1,25 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
32
samples/react-outlook-copy2teams/.gitignore
vendored
Normal file
32
samples/react-outlook-copy2teams/.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
13
samples/react-outlook-copy2teams/.yo-rc.json
Normal file
13
samples/react-outlook-copy2teams/.yo-rc.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"plusBeta": true,
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.10.0",
|
||||
"libraryName": "outlook-2-sp-spfx",
|
||||
"libraryId": "41e21307-ed8c-4409-b12f-9c575675bb37",
|
||||
"packageManager": "npm",
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
72
samples/react-outlook-copy2teams/README.md
Normal file
72
samples/react-outlook-copy2teams/README.md
Normal file
@ -0,0 +1,72 @@
|
||||
## outlook-2-teams-spfx
|
||||
|
||||
|
||||
## Summary
|
||||
This SPFx Outlook Add-In lets you browse your OneDrive, joined Teams or Groups and select a folder to save your complete mail in there.
|
||||
This sample shows you working with the current Office context and receive information on currently selected mail from there.
|
||||
Furthermore it shows you how to retrieve a complete mail as a mimestream via Microsoft Graph and finally two file operations with Microsoft Graph as well:
|
||||
* Writing normal files smaller 4MB
|
||||
* Writing big files with an UploadSession when bigger than 4MB
|
||||
|
||||
## outlook-2-teams-spfx in action
|
||||
![WebPartInAction](https://mmsharepoint.files.wordpress.com/2020/01/addin_overall.png)
|
||||
|
||||
A detailed functionality and technical description can be found in the [author's blog series](https://mmsharepoint.wordpress.com/2020/01/11/an-outlook-add-in-with-sharepoint-framework-spfx-introduction/)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![drop](https://img.shields.io/badge/drop-1.10.0-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [Tutorial for creating Outlook Web Access extension using SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/office-addins-tutorial)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
outlook-2-teams-spfx| Markus Moeller ([@moeller2_0](http://www.twitter.com/moeller2_0))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|February 05, 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
|
||||
* in the command line run:
|
||||
* restore dependencies: `npm install`
|
||||
From here you can also follow the deployment steps from the official [Microsoft Tutorial](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/office-addins-tutorial#packaging-and-deploying-your-solution-to-sharepoint)
|
||||
* build solution `gulp build --ship`
|
||||
* bundle solution: `gulp bundle --ship`
|
||||
* package solution: `gulp package-solution --ship`
|
||||
* locate solution at `.\sharepoint\solution\outlook-2-sp-spfx.sppkg`
|
||||
* upload it to your tenant app catalog
|
||||
* Go to your Outlook Web Access and selct a mail
|
||||
* Choose ... and "Get Add Ins"
|
||||
* Choose My Add-ins from left menu
|
||||
* Choose *Add from file... under the Custom add-ins
|
||||
* Upload the manifest xml file from \officeAddin folder
|
||||
* Click Install on the warning message to get your add-in available on the tenant
|
||||
* Close the add-in window by clicking X on the top-right corner
|
||||
* Activate again the context menu from [...] and choose "Copy to SharePoint" to activate the add-in in your inbox
|
||||
|
||||
## Features
|
||||
|
||||
This Outlook Add-In shows the following capabilities on top of the SharePoint Framework:
|
||||
|
||||
* Select Office context and attributes of currently selected mail
|
||||
* Use Microsoft Graph to retrieve joined Groups and Teams
|
||||
* Use Microsoft Graph to retrieve folders and subfolders for OneDrive or Teams/Group drives
|
||||
* Use Microsoft Graph to retrieve complete mail mimestream by given ID
|
||||
* Use Microsoft Graph to save normal or big files (in size bigger 4MB) with different concepts
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-outlook-copy2teams" />
|
18
samples/react-outlook-copy2teams/config/config.json
Normal file
18
samples/react-outlook-copy2teams/config/config.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"outlook-2-share-point-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/outlook2SharePoint/Outlook2SharePointWebPart.js",
|
||||
"manifest": "./src/webparts/outlook2SharePoint/Outlook2SharePointWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"Outlook2SharePointWebPartStrings": "lib/webparts/outlook2SharePoint/loc/{locale}.js"
|
||||
}
|
||||
}
|
4
samples/react-outlook-copy2teams/config/copy-assets.json
Normal file
4
samples/react-outlook-copy2teams/config/copy-assets.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
@ -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": "outlook-2-sp-spfx",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "outlook-2-sp-spfx-client-side-solution",
|
||||
"id": "41e21307-ed8c-4409-b12f-9c575675bb37",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Group.Read.All"
|
||||
},
|
||||
{
|
||||
"resource": "Windows Azure Active Directory",
|
||||
"scope": "User.Read"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Files.Read"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Files.ReadWrite"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Mail.Read"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Sites.ReadWrite.All"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/outlook-2-sp-spfx.sppkg"
|
||||
}
|
||||
}
|
10
samples/react-outlook-copy2teams/config/serve.json
Normal file
10
samples/react-outlook-copy2teams/config/serve.json
Normal 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/"
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
7
samples/react-outlook-copy2teams/gulpfile.js
vendored
Normal file
7
samples/react-outlook-copy2teams/gulpfile.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(require('gulp'));
|
@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0"
|
||||
xmlns:mailappor="http://schemas.microsoft.com/office/mailappversionoverrides/1.0"
|
||||
xsi:type="MailApp">
|
||||
<Id>7f1ef545-1d02-4cbd-b4e1-2f4140c1667a</Id>
|
||||
<Version>1.0.0.0</Version>
|
||||
<ProviderName>SPFx Provider</ProviderName>
|
||||
<DefaultLocale>en-US</DefaultLocale>
|
||||
<DisplayName DefaultValue="Copy to SharePoint"/>
|
||||
<Description DefaultValue="An Add-In to copy full mails to Teams, Groups or OneDrive."/>
|
||||
<IconUrl DefaultValue="https://cdn.graph.office.net/prod/media/shared/addin-icon.png"/>
|
||||
<HighResolutionIconUrl DefaultValue="https://cdn.graph.office.net/prod/media/shared/addin-icon.png"/>
|
||||
<SupportUrl DefaultValue="https://localhost:4321/help"/>
|
||||
<AppDomains>
|
||||
<AppDomain>https://login.microsoftonline.com</AppDomain>
|
||||
<AppDomain>https://login.windows.net</AppDomain>
|
||||
</AppDomains>
|
||||
<Hosts>
|
||||
<Host Name="Mailbox" />
|
||||
</Hosts>
|
||||
<Requirements>
|
||||
<Sets>
|
||||
<Set Name="Mailbox" MinVersion="1.4" />
|
||||
<Set Name="SharePointHostedAddin" MinVersion="1.1" />
|
||||
</Sets>
|
||||
</Requirements>
|
||||
<FormSettings>
|
||||
<Form xsi:type="ItemRead">
|
||||
<DesktopSettings>
|
||||
<SourceLocation DefaultValue="https://_SharePointTenantUrl_/_layouts/15/outlookhostedapp.aspx?componentId=7f1ef545-1d02-4cbd-b4e1-2f4140c1667a&isConfigureMode=true"/>
|
||||
<RequestedHeight>250</RequestedHeight>
|
||||
</DesktopSettings>
|
||||
</Form>
|
||||
</FormSettings>
|
||||
<Permissions>ReadWriteMailbox</Permissions>
|
||||
<Rule xsi:type="RuleCollection" Mode="Or">
|
||||
<Rule xsi:type="ItemIs" ItemType="Message" FormType="Read" />
|
||||
</Rule>
|
||||
<DisableEntityHighlighting>false</DisableEntityHighlighting>
|
||||
<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides" xsi:type="VersionOverridesV1_0">
|
||||
<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides/1.1" xsi:type="VersionOverridesV1_1">
|
||||
<Hosts>
|
||||
<Host xsi:type="MailHost">
|
||||
<DesktopFormFactor>
|
||||
<ExtensionPoint xsi:type="MessageReadCommandSurface">
|
||||
<OfficeTab id="TabDefault">
|
||||
<Group id="msgReadGroup">
|
||||
<Label resid="GroupLabel" />
|
||||
<Control xsi:type="Button" id="msgReadOpenPaneButton">
|
||||
<Label resid="TaskpaneButton.Label" />
|
||||
<Supertip>
|
||||
<Title resid="TaskpaneButton.Label" />
|
||||
<Description resid="TaskpaneButton.Tooltip" />
|
||||
</Supertip>
|
||||
<Icon>
|
||||
<bt:Image size="16" resid="Icon.16x16" />
|
||||
<bt:Image size="32" resid="Icon.32x32" />
|
||||
<bt:Image size="80" resid="Icon.80x80" />
|
||||
</Icon>
|
||||
<Action xsi:type="ShowTaskpane">
|
||||
<SourceLocation resid="Taskpane.Url" />
|
||||
</Action>
|
||||
</Control>
|
||||
</Group>
|
||||
</OfficeTab>
|
||||
</ExtensionPoint>
|
||||
</DesktopFormFactor>
|
||||
</Host>
|
||||
</Hosts>
|
||||
<Resources>
|
||||
<bt:Images>
|
||||
<bt:Image id="Icon.16x16" DefaultValue="https://cdn.graph.office.net/prod/media/shared/addin-icon.png"/>
|
||||
<bt:Image id="Icon.32x32" DefaultValue="https://cdn.graph.office.net/prod/media/shared/addin-icon.png"/>
|
||||
<bt:Image id="Icon.80x80" DefaultValue="https://cdn.graph.office.net/prod/media/shared/addin-icon.png"/>
|
||||
</bt:Images>
|
||||
<bt:Urls>
|
||||
<bt:Url id="Taskpane.Url" DefaultValue="https://_SharePointTenantUrl_/_layouts/15/outlookhostedapp.aspx?componentId=7f1ef545-1d02-4cbd-b4e1-2f4140c1667a&isConfigureMode=true" />
|
||||
</bt:Urls>
|
||||
<bt:ShortStrings>
|
||||
<bt:String id="GroupLabel" DefaultValue="Add-in groupLabel"/>
|
||||
<bt:String id="TaskpaneButton.Label" DefaultValue="Show Taskpane"/>
|
||||
</bt:ShortStrings>
|
||||
<bt:LongStrings>
|
||||
<bt:String id="TaskpaneButton.Tooltip" DefaultValue="Opens taskpane."/>
|
||||
</bt:LongStrings>
|
||||
</Resources>
|
||||
</VersionOverrides>
|
||||
</VersionOverrides>
|
||||
</OfficeApp>
|
16977
samples/react-outlook-copy2teams/package-lock.json
generated
Normal file
16977
samples/react-outlook-copy2teams/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
samples/react-outlook-copy2teams/package.json
Normal file
43
samples/react-outlook-copy2teams/package.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "outlook-2-sp-spfx",
|
||||
"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.10.0-plusbeta",
|
||||
"@microsoft/sp-lodash-subset": "1.10.0-plusbeta",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.10.0-plusbeta",
|
||||
"@microsoft/sp-property-pane": "1.10.0-plusbeta",
|
||||
"@microsoft/sp-webpart-base": "1.10.0-plusbeta",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/office-js": "^1.0.59",
|
||||
"@types/react": "16.8.8",
|
||||
"@types/react-dom": "16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"office-ui-fabric-react": "6.189.2",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.8.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.10.0-plusbeta",
|
||||
"@microsoft/sp-tslint-rules": "1.10.0-plusbeta",
|
||||
"@microsoft/sp-module-interfaces": "1.10.0-plusbeta",
|
||||
"@microsoft/sp-webpart-workbench": "1.10.0-plusbeta",
|
||||
"@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"
|
||||
}
|
||||
}
|
@ -0,0 +1,221 @@
|
||||
import { MSGraphClient, MSGraphClientFactory } from '@microsoft/sp-http';
|
||||
import Utilities from './Utilities';
|
||||
import { IFolder } from '../model/IFolder';
|
||||
import { IMail } from '../model/IMail';
|
||||
|
||||
export default class GraphController {
|
||||
private client: MSGraphClient;
|
||||
|
||||
constructor (graphFactory: MSGraphClientFactory, callback: () => void) {
|
||||
graphFactory
|
||||
.getClient()
|
||||
.then((client: MSGraphClient) => {
|
||||
this.client = client;
|
||||
callback();
|
||||
});
|
||||
|
||||
this.retrieveMimeMail = this.retrieveMimeMail.bind(this);
|
||||
}
|
||||
|
||||
public getClient() {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function retrieves all 1st-level folders from user's OneDrive
|
||||
*/
|
||||
public getOneDriveFolder(): Promise<IFolder[]> {
|
||||
return this.client
|
||||
.api('me/drive/root/children')
|
||||
.version('v1.0')
|
||||
.filter('folder ne null')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
response.value.forEach((item) => {
|
||||
folders.push({ id: item.id, name: item.name, driveID: item.parentReference.driveId, parentFolder: null});
|
||||
});
|
||||
return folders;
|
||||
});
|
||||
}
|
||||
|
||||
public getGroupRootFolders(group: IFolder): Promise<IFolder[]> {
|
||||
return this.client
|
||||
.api(`drives/${group.driveID}/root/children`)
|
||||
.version('v1.0')
|
||||
.filter('folder ne null')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
response.value.forEach((item) => {
|
||||
folders.push({ id: item.id, name: item.name, driveID: group.driveID, parentFolder: group});
|
||||
});
|
||||
return folders;
|
||||
});
|
||||
}
|
||||
|
||||
public getSubFolder(folder: IFolder): Promise<IFolder[]> {
|
||||
return this.client
|
||||
.api(`drives/${folder.driveID}/items/${folder.id}/children`)
|
||||
.version('v1.0')
|
||||
.filter('folder ne null')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
response.value.forEach((item) => {
|
||||
folders.push({ id: item.id, name: item.name, driveID: folder.driveID, parentFolder: folder});
|
||||
});
|
||||
return folders;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function retrievs the user's membership groups from Graph
|
||||
*/
|
||||
public getJoinedGroups(): Promise<IFolder[]> {
|
||||
return this.client
|
||||
.api('me/memberOf')
|
||||
.version('v1.0')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
response.value.forEach((item) => {
|
||||
// Show unified Groups but NO Teams
|
||||
if (item['@odata.type'] === '#microsoft.graph.group') {
|
||||
if(!item.resourceProvisioningOptions || item.resourceProvisioningOptions.indexOf('Team') === -1) {
|
||||
folders.push({ id: item.id, name: item.displayName, driveID: item.id, parentFolder: null});
|
||||
}
|
||||
}
|
||||
});
|
||||
return folders;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function retrievs the user's membership groups from Graph
|
||||
*/
|
||||
public getJoinedTeams(): Promise<IFolder[]> {
|
||||
return this.client
|
||||
.api('me/joinedTeams')
|
||||
.version('v1.0')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
response.value.forEach((item) => {
|
||||
folders.push({ id: item.id, name: item.displayName, driveID: item.id, parentFolder: null});
|
||||
});
|
||||
return folders;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function retrieves all Drives for a given Group
|
||||
*/
|
||||
public getGroupDrives(group: IFolder): Promise<IFolder[]> {
|
||||
return this.client
|
||||
.api(`groups/${group.id}/drives`)
|
||||
.version('v1.0')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
response.value.forEach((item) => {
|
||||
folders.push({ id: item.id, name: item.name, driveID: item.id, parentFolder: group});
|
||||
});
|
||||
return folders;
|
||||
});
|
||||
}
|
||||
|
||||
public retrieveMimeMail(driveID: string, folderID: string, mail: IMail, clientCallback: (msg: string)=>void): Promise<string> {
|
||||
return this.client
|
||||
.api(`me/messages/${mail.id}/$value`)
|
||||
.version('v1.0')
|
||||
.responseType('TEXT')
|
||||
.get((err: any, response, rawResponse): any => {
|
||||
if (response.length < (4 * 1024 * 1024)) // If Mail size bigger 4MB use resumable upload
|
||||
{
|
||||
this.saveNormalMail(driveID, folderID, response, Utilities.createMailFileName(mail.subject), clientCallback);
|
||||
}
|
||||
else {
|
||||
this.saveBigMail(driveID, folderID, response, Utilities.createMailFileName(mail.subject), clientCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private saveNormalMail(driveID: string, folderID: string, mimeStream: string, fileName: string, clientCallback: (msg: string)=>void) {
|
||||
const apiUrl = driveID !== folderID ? `drives/${driveID}/items/${folderID}:/${fileName}.eml:/content` : `drives/${driveID}/root:/${fileName}.eml:/content`;
|
||||
this.client
|
||||
.api(apiUrl)
|
||||
.put(mimeStream)
|
||||
.then((response) => {
|
||||
clientCallback('Success');
|
||||
})
|
||||
.catch((error) => {
|
||||
clientCallback('Error');
|
||||
});
|
||||
}
|
||||
|
||||
public async saveBigMail(driveID: string, folderID: string, mimeStream: string, fileName: string, clientCallback: (msg: string)=>void) {
|
||||
const sessionOptions = {
|
||||
"item": {
|
||||
"@microsoft.graph.conflictBehavior": "rename"
|
||||
}
|
||||
};
|
||||
const apiUrl = driveID !== folderID ? `drives/${driveID}/items/${folderID}:/${fileName}.eml:/createUploadSession` : `drives/${driveID}/root:/${fileName}.eml:/createUploadSession`;
|
||||
this.client
|
||||
.api(apiUrl)
|
||||
.post(JSON.stringify(sessionOptions))
|
||||
.then(async (response):Promise<any> => {
|
||||
console.log(response.uploadUrl);
|
||||
console.log(response.expirationDateTime);
|
||||
try {
|
||||
const resp = await this.uploadMailSlices(mimeStream, response.uploadUrl);
|
||||
console.log(resp);
|
||||
clientCallback('Success');
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
clientCallback('Error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async uploadMailSlices(mimeStream: string, uploadUrl: string) {
|
||||
let minSize=0;
|
||||
let maxSize=327680; // 320kb slices
|
||||
while(mimeStream.length > minSize) {
|
||||
const fileSlice = mimeStream.slice(minSize, maxSize);
|
||||
const resp = await this.uploadMailSlice(uploadUrl, minSize, maxSize, mimeStream.length, fileSlice);
|
||||
minSize = maxSize;
|
||||
maxSize += 327680;
|
||||
if (maxSize > mimeStream.length) {
|
||||
maxSize = mimeStream.length;
|
||||
}
|
||||
if (resp.id !== undefined) {
|
||||
return resp;
|
||||
}
|
||||
else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async uploadMailSlice(uploadUrl: string, minSize: number, maxSize: number, totalSize: number, fileSlice: string) {
|
||||
const header = {
|
||||
"Content-Length": `${maxSize - minSize}`,
|
||||
"Content-Range": `bytes ${minSize}-${maxSize-1}/${totalSize}`,
|
||||
};
|
||||
return await this.client
|
||||
.api(uploadUrl)
|
||||
.headers(header)
|
||||
.put(fileSlice);
|
||||
}
|
||||
|
||||
private saveMailCallback(error: any, response: any, rawResponse?: any): void {
|
||||
if (error !== null) {
|
||||
console.log(error);
|
||||
}
|
||||
else {
|
||||
console.log(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
export default class Utilities {
|
||||
public static createMailFileName(subject: string): string {
|
||||
let fileName = subject.replace(/ /g, '_').replace(/:/g, '_');
|
||||
return fileName;
|
||||
}
|
||||
}
|
1
samples/react-outlook-copy2teams/src/index.ts
Normal file
1
samples/react-outlook-copy2teams/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
7
samples/react-outlook-copy2teams/src/model/IFolder.ts
Normal file
7
samples/react-outlook-copy2teams/src/model/IFolder.ts
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
export interface IFolder {
|
||||
name: string;
|
||||
id: string;
|
||||
driveID: string;
|
||||
parentFolder: IFolder;
|
||||
}
|
4
samples/react-outlook-copy2teams/src/model/IMail.ts
Normal file
4
samples/react-outlook-copy2teams/src/model/IMail.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface IMail {
|
||||
id: string;
|
||||
subject: string;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "7f1ef545-1d02-4cbd-b4e1-2f4140c1667a",
|
||||
"alias": "Outlook2SharePointWebPart",
|
||||
"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": "Outlook 2 SharePoint" },
|
||||
"description": { "default": "Enables to copy mails fully to SharePoint, OneDrive, Teams" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "Outlook 2 SharePoint"
|
||||
}
|
||||
}]
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
|
||||
import * as strings from 'Outlook2SharePointWebPartStrings';
|
||||
import { IMail } from '../../model/IMail';
|
||||
import Outlook2SharePoint from './components/Outlook2SharePoint';
|
||||
import { IOutlook2SharePointProps } from './components/IOutlook2SharePointProps';
|
||||
|
||||
export interface IOutlook2SharePointWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class Outlook2SharePointWebPart extends BaseClientSideWebPart <IOutlook2SharePointWebPartProps> {
|
||||
public render(): void {
|
||||
let mail: IMail = null;
|
||||
if (this.context.sdks.office) {
|
||||
const item = this.context.sdks.office.context.mailbox.item;
|
||||
if (item !== null) {
|
||||
mail = { id: item.itemId,subject: item.subject };
|
||||
}
|
||||
}
|
||||
|
||||
const element: React.ReactElement<IOutlook2SharePointProps> = React.createElement(
|
||||
Outlook2SharePoint,
|
||||
{
|
||||
msGraphClientFactory: this.context.msGraphClientFactory,
|
||||
mail: mail
|
||||
}
|
||||
);
|
||||
|
||||
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
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.folder {
|
||||
.header {
|
||||
@include ms-fontSize-m;
|
||||
margin-left: 3px;
|
||||
}
|
||||
.isLink {
|
||||
cursor: pointer;
|
||||
}
|
||||
.sublist {
|
||||
list-style-type: none;
|
||||
margin-inline-start: 20px;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import { Icon } from 'office-ui-fabric-react/lib/Icon';
|
||||
import styles from './Folder.module.scss';
|
||||
import { IFolderProps } from './IFolderProps';
|
||||
|
||||
export default class Folder extends React.Component<IFolderProps, {}> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IFolderProps> {
|
||||
return (
|
||||
<li className={styles.folder}>
|
||||
<Icon iconName="DocLibrary" className="ms-IconDocLibrary" />
|
||||
<span className={`${styles.header} ${styles.isLink}`} onClick={this.getSubFolder}>{this.props.folder.name}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
private getSubFolder = () => {
|
||||
this.props.subFolderCallback(this.props.folder);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.groups {
|
||||
margin-bottom: 10px;
|
||||
.header {
|
||||
@include ms-fontSize-l;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.list {
|
||||
list-style-type: none;
|
||||
padding-inline-start: 20px;
|
||||
}
|
||||
.saveBtn {
|
||||
margin: 5px 10px 5px 10px;
|
||||
}
|
||||
.spinnerContainer {
|
||||
position: relative;
|
||||
@include ms-md12;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
}
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
import * as React from 'react';
|
||||
import { Overlay } from 'office-ui-fabric-react/lib/Overlay';
|
||||
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||
import Breadcrumb from './controls/Breadcrumb';
|
||||
import Folder from './Folder';
|
||||
import styles from './Groups.module.scss';
|
||||
import { IGroupsProps } from './IGroupsProps';
|
||||
import { IGroupsState } from './IGroupsState';
|
||||
import { IFolder } from '../../../model/IFolder';
|
||||
|
||||
export default class Groups extends React.Component<IGroupsProps, IGroupsState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
folders: [],
|
||||
grandParentFolder: null,
|
||||
parentFolder: null,
|
||||
showSpinner: false
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
if (this.props.graphController !== null) {
|
||||
this.getGroups();
|
||||
}
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IGroupsProps> {
|
||||
let fldrs = this.state.folders.map((fldr) => {
|
||||
return <Folder folder={fldr} subFolderCallback={fldr.parentFolder===null?this.getGroupDrives:this.getSubFolders}></Folder>;
|
||||
});
|
||||
return (
|
||||
<div className={styles.groups}>
|
||||
<div>
|
||||
<div>
|
||||
<Breadcrumb
|
||||
grandParentFolder={this.state.grandParentFolder}
|
||||
parentFolder={this.state.parentFolder}
|
||||
rootCallback={this.showRoot}
|
||||
parentFolderCallback={this.showParentFolder}>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
<div className="recent-content">
|
||||
<ul className={styles.list}>
|
||||
{fldrs}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<PrimaryButton
|
||||
className={styles.saveBtn}
|
||||
text="Save here"
|
||||
onClick={this.saveMailTo}
|
||||
disabled={this.state.parentFolder === null}
|
||||
allowDisabledFocus={true}
|
||||
/>
|
||||
{ this.state.showSpinner && (
|
||||
<div className={styles.spinnerContainer}>
|
||||
<Overlay >
|
||||
<Spinner size={ SpinnerSize.large } label='Processing request' />
|
||||
</Overlay>
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getGroups = () => {
|
||||
this.props.graphController.getJoinedGroups().then((folders) => {
|
||||
this.setState((prevState: IGroupsState, props: IGroupsProps) => {
|
||||
return {
|
||||
folders: folders
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getGroupDrives = (group: IFolder) => {
|
||||
let nextParent: IFolder = null;
|
||||
this.state.folders.forEach((fldr) => {
|
||||
if (fldr.id === group.id) {
|
||||
nextParent = fldr;
|
||||
}
|
||||
});
|
||||
this.props.graphController.getGroupDrives(group).then((folders) => {
|
||||
if (folders.length > 0) {
|
||||
this.setState((prevState: IGroupsState, props: IGroupsProps) => {
|
||||
return {
|
||||
folders: folders,
|
||||
grandParentFolder: null,
|
||||
parentFolder: group
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getSubFolders = (folder: IFolder) => {
|
||||
if (folder.id === folder.driveID) {
|
||||
this.props.graphController.getGroupRootFolders(folder).then((folders) => {
|
||||
this.setState((prevState: IGroupsState, props: IGroupsProps) => {
|
||||
return {
|
||||
folders: folders,
|
||||
grandParentFolder: folder.parentFolder,
|
||||
parentFolder: folder
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.props.graphController.getSubFolder(folder).then((folders) => {
|
||||
this.setState((prevState: IGroupsState, props: IGroupsProps) => {
|
||||
return {
|
||||
folders: folders,
|
||||
grandParentFolder: folder.parentFolder,
|
||||
parentFolder: folder
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private showRoot = () => {
|
||||
this.getGroups();
|
||||
}
|
||||
|
||||
private showParentFolder = (parentFolder: IFolder) => {
|
||||
if (this.state.grandParentFolder===null) {
|
||||
this.getGroupDrives(parentFolder);
|
||||
}
|
||||
else {
|
||||
this.getSubFolders(parentFolder);
|
||||
}
|
||||
}
|
||||
|
||||
private saveMailTo = () => {
|
||||
this.setState((prevState: IGroupsState, props: IGroupsProps) => {
|
||||
return {
|
||||
showSpinner: true
|
||||
};
|
||||
});
|
||||
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback);
|
||||
}
|
||||
|
||||
private saveMailCallback = (message: string) => {
|
||||
this.setState((prevState: IGroupsState, props: IGroupsProps) => {
|
||||
return {
|
||||
showSpinner: false
|
||||
};
|
||||
});
|
||||
if (message.indexOf('Success') > -1) {
|
||||
this.props.successCallback(message);
|
||||
}
|
||||
else {
|
||||
this.props.errorCallback(message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { IFolder } from '../../../model/IFolder';
|
||||
|
||||
export interface IFolderProps {
|
||||
folder: IFolder;
|
||||
subFolderCallback: (folder: IFolder) => void;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import GraphController from '../../../controller/GraphController';
|
||||
import { IMail } from '../../../model/IMail';
|
||||
|
||||
|
||||
export interface IGroupsProps {
|
||||
graphController: GraphController;
|
||||
mail: IMail;
|
||||
successCallback: (msg: string) => void;
|
||||
errorCallback: (msg: string) => void;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { IFolder } from '../../../model/IFolder';
|
||||
|
||||
export interface IGroupsState {
|
||||
folders: IFolder[];
|
||||
grandParentFolder: IFolder;
|
||||
parentFolder: IFolder;
|
||||
showSpinner: boolean;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import GraphController from '../../../controller/GraphController';
|
||||
import { IMail } from '../../../model/IMail';
|
||||
|
||||
export interface IOneDriveProps {
|
||||
graphController: GraphController;
|
||||
mail: IMail;
|
||||
successCallback: (msg: string) => void;
|
||||
errorCallback: (msg: string) => void;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { IFolder } from '../../../model/IFolder';
|
||||
|
||||
export interface IOneDriveState {
|
||||
folders: IFolder[];
|
||||
grandParentFolder: IFolder;
|
||||
parentFolder: IFolder;
|
||||
showSpinner: boolean;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { MSGraphClientFactory } from '@microsoft/sp-http';
|
||||
import { IMail } from '../../../model/IMail';
|
||||
|
||||
export interface IOutlook2SharePointProps {
|
||||
mail: IMail;
|
||||
msGraphClientFactory: MSGraphClientFactory;
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import GraphController from '../../../controller/GraphController';
|
||||
|
||||
export interface IOutlook2SharePointState {
|
||||
graphController: GraphController;
|
||||
showSuccess: boolean;
|
||||
showError: boolean;
|
||||
showOneDrive: boolean;
|
||||
showTeams: boolean;
|
||||
showGroups: boolean;
|
||||
successMessage: string;
|
||||
errorMessage: string;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import GraphController from '../../../controller/GraphController';
|
||||
import { IMail } from '../../../model/IMail';
|
||||
|
||||
|
||||
export interface ITeamsProps {
|
||||
graphController: GraphController;
|
||||
mail: IMail;
|
||||
successCallback: (msg: string) => void;
|
||||
errorCallback: (msg: string) => void;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { IFolder } from '../../../model/IFolder';
|
||||
|
||||
export interface ITeamsState {
|
||||
folders: IFolder[];
|
||||
grandParentFolder: IFolder;
|
||||
parentFolder: IFolder;
|
||||
showSpinner: boolean;
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
import * as React from 'react';
|
||||
import { Overlay } from 'office-ui-fabric-react/lib/Overlay';
|
||||
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||
import Folder from './Folder';
|
||||
import styles from './Groups.module.scss';
|
||||
import Breadcrumb from './controls/Breadcrumb';
|
||||
import { IOneDriveProps } from './IOneDriveProps';
|
||||
import { IOneDriveState } from './IOneDriveState';
|
||||
import { IFolder } from '../../../model/IFolder';
|
||||
|
||||
export default class OneDrive extends React.Component<IOneDriveProps, IOneDriveState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
folders: [],
|
||||
grandParentFolder: null,
|
||||
parentFolder: null,
|
||||
showSpinner: false
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
if (this.props.graphController !== null) {
|
||||
this.getFolder();
|
||||
}
|
||||
}
|
||||
public render(): React.ReactElement<IOneDriveProps> {
|
||||
let fldrs = this.state.folders.map((fldr) => {
|
||||
return <Folder folder={fldr} subFolderCallback={this.getSubFolders}></Folder>;
|
||||
});
|
||||
return (
|
||||
<div className={styles.groups}>
|
||||
<div>
|
||||
<div>
|
||||
<Breadcrumb
|
||||
grandParentFolder={this.state.grandParentFolder}
|
||||
parentFolder={this.state.parentFolder}
|
||||
rootCallback={this.showRoot}
|
||||
parentFolderCallback={this.showParentFolder}>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
<div className="recent-content">
|
||||
<ul className={styles.list}>
|
||||
{fldrs}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<PrimaryButton
|
||||
className={styles.saveBtn}
|
||||
text="Save here"
|
||||
onClick={this.saveMailTo}
|
||||
allowDisabledFocus={true}
|
||||
/>
|
||||
{ this.state.showSpinner && (
|
||||
<div className={styles.spinnerContainer}>
|
||||
<Overlay >
|
||||
<Spinner size={ SpinnerSize.large } label='Processing request' />
|
||||
</Overlay>
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getFolder = () => {
|
||||
this.props.graphController.getOneDriveFolder().then((folders) => {
|
||||
this.setState((prevState: IOneDriveState, props: IOneDriveProps) => {
|
||||
return {
|
||||
folders: folders
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getSubFolders = (folder: IFolder) => {
|
||||
this.props.graphController.getSubFolder(folder).then((folders) => {
|
||||
if (folders.length > 0) {
|
||||
this.setState((prevState: IOneDriveState, props: IOneDriveProps) => {
|
||||
return {
|
||||
folders: folders,
|
||||
grandParentFolder: folder.parentFolder,
|
||||
parentFolder: folder
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private showRoot = () => {
|
||||
this.getFolder();
|
||||
}
|
||||
|
||||
private showParentFolder = (parentFolder: IFolder) => {
|
||||
this.getSubFolders(parentFolder);
|
||||
}
|
||||
|
||||
private saveMailTo = () => {
|
||||
this.setState((prevState: IOneDriveState, props: IOneDriveProps) => {
|
||||
return {
|
||||
showSpinner: true
|
||||
};
|
||||
});
|
||||
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback);
|
||||
}
|
||||
|
||||
private saveMailCallback = (message: string) => {
|
||||
this.setState((prevState: IOneDriveState, props: IOneDriveProps) => {
|
||||
return {
|
||||
showSpinner: false
|
||||
};
|
||||
});
|
||||
if (message.indexOf('Success') > -1) {
|
||||
this.props.successCallback(message);
|
||||
}
|
||||
else {
|
||||
this.props.errorCallback(message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.outlook2SharePoint {
|
||||
font-family: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
.header {
|
||||
cursor: pointer;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.headerIcon {
|
||||
font-size: 1.5em;
|
||||
font-weight: bolder;
|
||||
}
|
||||
.headerText {
|
||||
@include ms-fontSize-l;
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
import * as React from 'react';
|
||||
import styles from './Outlook2SharePoint.module.scss';
|
||||
import { Icon } from 'office-ui-fabric-react/lib/Icon';
|
||||
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||
import GraphController from '../../../controller/GraphController';
|
||||
import Groups from './Groups';
|
||||
import OneDrive from './OneDrive';
|
||||
import Teams from './Teams';
|
||||
import { IOutlook2SharePointProps } from './IOutlook2SharePointProps';
|
||||
import { IOutlook2SharePointState } from './IOutlook2SharePointState';
|
||||
|
||||
export default class Outlook2SharePoint extends React.Component<IOutlook2SharePointProps, IOutlook2SharePointState> {
|
||||
private graphController: GraphController;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
graphController: null,
|
||||
showError: false,
|
||||
showSuccess: false,
|
||||
showOneDrive: false,
|
||||
showTeams: false,
|
||||
showGroups: false,
|
||||
successMessage: '',
|
||||
errorMessage: ''
|
||||
};
|
||||
this.graphController = new GraphController(this.props.msGraphClientFactory, this.graphClientReadyCallback);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IOutlook2SharePointProps> {
|
||||
|
||||
return (
|
||||
<div className={ styles.outlook2SharePoint }>
|
||||
{this.state.showSuccess && <div>
|
||||
<MessageBar
|
||||
messageBarType={MessageBarType.success}
|
||||
isMultiline={false}
|
||||
onDismiss={this.closeMessage}
|
||||
dismissButtonAriaLabel="Close"
|
||||
truncated={true}
|
||||
overflowButtonAriaLabel="See more"
|
||||
>
|
||||
{this.state.successMessage}
|
||||
</MessageBar>
|
||||
</div>}
|
||||
{this.state.showError && <div>
|
||||
<MessageBar
|
||||
messageBarType={MessageBarType.error}
|
||||
isMultiline={false}
|
||||
onDismiss={this.closeMessage}
|
||||
dismissButtonAriaLabel="Close"
|
||||
truncated={true}
|
||||
overflowButtonAriaLabel="See more"
|
||||
>
|
||||
{this.state.errorMessage}
|
||||
</MessageBar>
|
||||
</div>}
|
||||
<div className={styles.header} onClick={this.showOneDrive}>
|
||||
<Icon iconName="OneDrive" className={`ms-IconOneDrive ${styles.headerIcon}`} />
|
||||
<span className={styles.headerText}>OneDrive</span>
|
||||
</div>
|
||||
{this.state.graphController && this.state.showOneDrive &&
|
||||
<OneDrive
|
||||
graphController={this.state.graphController}
|
||||
mail={this.props.mail}
|
||||
successCallback={this.showSuccess}
|
||||
errorCallback={this.showError}>
|
||||
</OneDrive>}
|
||||
|
||||
<div className={styles.header} onClick={this.showTeams}>
|
||||
<Icon iconName="TeamsLogo" className={`ms-IconTeamsLogo ${styles.headerIcon}`} />
|
||||
<span className={styles.headerText}>Microsoft Teams</span>
|
||||
</div>
|
||||
{this.state.graphController && this.state.showTeams &&
|
||||
<Teams
|
||||
graphController={this.state.graphController}
|
||||
mail={this.props.mail}
|
||||
successCallback={this.showSuccess}
|
||||
errorCallback={this.showError}>
|
||||
</Teams>}
|
||||
<div className={styles.header} onClick={this.showGroups}>
|
||||
<Icon iconName="Group" className={`ms-IconGroup ${styles.headerIcon}`} />
|
||||
<span className={styles.headerText}>Microsoft Groups</span>
|
||||
</div>
|
||||
{this.state.graphController && this.state.showGroups &&
|
||||
<Groups
|
||||
graphController={this.state.graphController}
|
||||
mail={this.props.mail}
|
||||
successCallback={this.showSuccess}
|
||||
errorCallback={this.showError}>
|
||||
</Groups>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function first retrieves all OneDrive root folders from user
|
||||
*/
|
||||
private graphClientReadyCallback = () => {
|
||||
this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => {
|
||||
return {
|
||||
graphController: this.graphController
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private showError = (message: string) => {
|
||||
this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => {
|
||||
return {
|
||||
showError: true,
|
||||
showSuccess: false,
|
||||
errorMessage: message
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private showSuccess = (message: string) => {
|
||||
this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => {
|
||||
return {
|
||||
showSuccess: true,
|
||||
showError: false,
|
||||
successMessage: message
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private closeMessage = () => {
|
||||
this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => {
|
||||
return {
|
||||
showSuccess: false,
|
||||
showError: false
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function expands the Teams section and collapses the other ones
|
||||
*/
|
||||
private showTeams = () => {
|
||||
this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => {
|
||||
return {
|
||||
showTeams: true,
|
||||
showOneDrive: false,
|
||||
showGroups: false
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function expands the OneDrive section and collapses the other ones
|
||||
*/
|
||||
private showOneDrive = () => {
|
||||
this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => {
|
||||
return {
|
||||
showOneDrive: true,
|
||||
showTeams: false,
|
||||
showGroups: false
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function expands the Groups section and collapses the other ones
|
||||
*/
|
||||
private showGroups = () => {
|
||||
this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => {
|
||||
return {
|
||||
showGroups: true,
|
||||
showTeams: false,
|
||||
showOneDrive: false
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
import * as React from 'react';
|
||||
import { Overlay } from 'office-ui-fabric-react/lib/Overlay';
|
||||
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||
import Breadcrumb from './controls/Breadcrumb';
|
||||
import Folder from './Folder';
|
||||
import styles from './Groups.module.scss';
|
||||
import { ITeamsProps } from './ITeamsProps';
|
||||
import { ITeamsState } from './ITeamsState';
|
||||
import { IFolder } from '../../../model/IFolder';
|
||||
|
||||
export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
folders: [],
|
||||
grandParentFolder: null,
|
||||
parentFolder: null,
|
||||
showSpinner: false
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
if (this.props.graphController !== null) {
|
||||
this.getTeams();
|
||||
}
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<ITeamsState> {
|
||||
let fldrs = this.state.folders.map((fldr) => {
|
||||
return <Folder folder={fldr} subFolderCallback={fldr.parentFolder===null?this.getGroupDrives:this.getSubFolders}></Folder>;
|
||||
});
|
||||
return (
|
||||
<div className={styles.groups}>
|
||||
<div>
|
||||
<div>
|
||||
<Breadcrumb
|
||||
grandParentFolder={this.state.grandParentFolder}
|
||||
parentFolder={this.state.parentFolder}
|
||||
rootCallback={this.showRoot}
|
||||
parentFolderCallback={this.showParentFolder}>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
<div className="recent-content">
|
||||
<ul className={styles.list}>
|
||||
{fldrs}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<PrimaryButton
|
||||
className={styles.saveBtn}
|
||||
text="Save here"
|
||||
onClick={this.saveMailTo}
|
||||
disabled={this.state.parentFolder === null}
|
||||
allowDisabledFocus={true}
|
||||
/>
|
||||
{ this.state.showSpinner && (
|
||||
<div className={styles.spinnerContainer}>
|
||||
<Overlay >
|
||||
<Spinner size={ SpinnerSize.large } label='Processing request' />
|
||||
</Overlay>
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getTeams = () => {
|
||||
this.props.graphController.getJoinedTeams().then((folders) => {
|
||||
this.setState((prevState: ITeamsState, props: ITeamsProps) => {
|
||||
return {
|
||||
folders: folders
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getGroupDrives = (group: IFolder) => {
|
||||
let nextParent: IFolder = null;
|
||||
this.state.folders.forEach((fldr) => {
|
||||
if (fldr.id === group.id) {
|
||||
nextParent = fldr;
|
||||
}
|
||||
});
|
||||
this.props.graphController.getGroupDrives(group).then((folders) => {
|
||||
if (folders.length > 0) {
|
||||
this.setState((prevState: ITeamsState, props: ITeamsProps) => {
|
||||
return {
|
||||
folders: folders,
|
||||
grandParentFolder: null,
|
||||
parentFolder: group
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getSubFolders = (folder: IFolder) => {
|
||||
if (folder.id === folder.driveID) {
|
||||
this.props.graphController.getGroupRootFolders(folder).then((folders) => {
|
||||
this.setState((prevState: ITeamsState, props: ITeamsProps) => {
|
||||
return {
|
||||
folders: folders,
|
||||
grandParentFolder: folder.parentFolder,
|
||||
parentFolder: folder
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.props.graphController.getSubFolder(folder).then((folders) => {
|
||||
this.setState((prevState: ITeamsState, props: ITeamsProps) => {
|
||||
return {
|
||||
folders: folders,
|
||||
grandParentFolder: folder.parentFolder,
|
||||
parentFolder: folder
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private showRoot = () => {
|
||||
this.getTeams();
|
||||
}
|
||||
|
||||
private showParentFolder = (parentFolder: IFolder) => {
|
||||
if (this.state.grandParentFolder===null) {
|
||||
this.getGroupDrives(parentFolder);
|
||||
}
|
||||
else {
|
||||
this.getSubFolders(parentFolder);
|
||||
}
|
||||
}
|
||||
|
||||
private saveMailTo = () => {
|
||||
this.setState((prevState: ITeamsState, props: ITeamsProps) => {
|
||||
return {
|
||||
showSpinner: true
|
||||
};
|
||||
});
|
||||
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback);
|
||||
}
|
||||
|
||||
private saveMailCallback = (message: string) => {
|
||||
this.setState((prevState: ITeamsState, props: ITeamsProps) => {
|
||||
return {
|
||||
showSpinner: false
|
||||
};
|
||||
});
|
||||
if (message.indexOf('Success') > -1) {
|
||||
this.props.successCallback(message);
|
||||
}
|
||||
else {
|
||||
this.props.errorCallback(message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.breadcrumb {
|
||||
@include ms-Grid;
|
||||
.rootIcon {
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
@include ms-Grid-col;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
}
|
||||
.grandParent {
|
||||
@include ms-Grid-col;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.link {
|
||||
cursor: pointer;
|
||||
}
|
||||
.nonLink {
|
||||
margin-left: 5px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import * as React from 'react';
|
||||
import { Icon } from 'office-ui-fabric-react/lib/Icon';
|
||||
import styles from './Breadcrumb.module.scss';
|
||||
import { IBreadcrumbProps } from './IBreadcrumbProps';
|
||||
//import { IFolderState } from './IFolderState';
|
||||
|
||||
export default class Breadcrumb extends React.Component<IBreadcrumbProps, {}> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
subFolders: []
|
||||
};
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IBreadcrumbProps> {
|
||||
return (
|
||||
<div className={styles.breadcrumb}>
|
||||
{this.props.grandParentFolder !== null && this.props.parentFolder !== null &&
|
||||
<Icon onClick={this.showRoot} iconName="DoubleChevronLeft" className={`ms-IconDoubleChevronLeft ${styles.rootIcon}`} />}
|
||||
<div className={styles.row}>
|
||||
{this.props.grandParentFolder &&
|
||||
<div className={styles.grandParent}>
|
||||
<span className={styles.link} onClick={this.showParentFolder}>{this.props.grandParentFolder.name}</span>
|
||||
</div>}
|
||||
{this.props.parentFolder &&
|
||||
<div className={styles.grandParent}>
|
||||
<Icon iconName="ChevronRight" className="ms-IconChevronRight" />
|
||||
<span className={styles.nonLink}>{this.props.parentFolder.name}</span>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private showRoot = () => {
|
||||
this.props.rootCallback();
|
||||
}
|
||||
|
||||
private showParentFolder = () => {
|
||||
this.props.parentFolderCallback(this.props.grandParentFolder);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { IFolder } from '../../../../model/IFolder';
|
||||
|
||||
export interface IBreadcrumbProps {
|
||||
grandParentFolder: IFolder;
|
||||
parentFolder: IFolder;
|
||||
rootCallback: () => void;
|
||||
parentFolderCallback: (folder: IFolder) => void;
|
||||
}
|
7
samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/en-us.js
vendored
Normal file
7
samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/en-us.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
10
samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/mystrings.d.ts
vendored
Normal file
10
samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/mystrings.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
declare interface IOutlook2SharePointWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'Outlook2SharePointWebPartStrings' {
|
||||
const strings: IOutlook2SharePointWebPartStrings;
|
||||
export = strings;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user