Merge branch 'master' of https://github.com/pnp/sp-dev-fx-webparts into pnp-master

This commit is contained in:
petkir 2020-06-09 09:55:42 +02:00
commit 40afc4eebf
57 changed files with 43071 additions and 23940 deletions

View File

@ -7028,8 +7028,8 @@ websocket-driver@>=0.5.1:
websocket-extensions ">=0.1.1"
websocket-extensions@>=0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7"
version "0.1.4"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
whatwg-fetch@0.11.0, whatwg-fetch@>=0.10.0:
version "0.11.0"

View File

@ -8932,8 +8932,8 @@ websocket-driver@>=0.5.1:
websocket-extensions ">=0.1.1"
websocket-extensions@>=0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7"
version "0.1.4"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
whatwg-fetch@2.0.3:
version "2.0.3"

View File

@ -14,7 +14,7 @@ extensions:
# Modern Calendar
## Summary
This is a modern webpart built on the GA version of the [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview) demonstrating how to build a custom web part with a calendar capabilities in it.
This is a modern webpart built on the GA version of the [SharePoint Framework](https://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview) demonstrating how to build a custom web part with a calendar capabilities in it.
![SS1](https://cloud.githubusercontent.com/assets/13068139/23584809/14c4333e-0121-11e7-9bf1-3117651222d3.png)
![SS2](https://cloud.githubusercontent.com/assets/13068139/23584808/14c3ec26-0121-11e7-8be8-65fbcca32b62.png)
@ -26,7 +26,7 @@ This is a modern webpart built on the GA version of the [SharePoint Framework](h
## Applies to
* [SharePoint Framework](https://blogs.office.com/2017/02/23/sharepoint-framework-reaches-general-availability-build-and-deploy-engaging-web-parts-today/)
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
## Prerequisites
@ -37,12 +37,14 @@ None
Solution|Author(s)
--------|---------
js-modern-calendar | Jeremy Coleman (MCP, PC Professional, Inc.)
js-modern-calendar | Nanddeep Nachan ([@NanddeepNachan](twitter.com/NanddeepNachan))
## Version history
Version|Date|Comments
-------|----|--------
1.0.0.0|February 11, 2017|Initial release
1.0.0.1|June 05, 2020|Updated the external CDN references to public CDN references
## 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.**
@ -67,4 +69,4 @@ Version|Date|Comments
## Features
Renders a calendar from any list available on the selected site. Site, List, Start, End, Event Title,Event Details and Calendar Theme are user-definable in the web part properties, so that you could technically use a custom list as the source for calendar presentation.
![](https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-modern-calendar)
![](https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-modern-calendar)

View File

@ -1,14 +1,18 @@
{
"entries": [
{
"entry": "./lib/webparts/modernCalendar/ModernCalendarWebPart.js",
"manifest": "./src/webparts/modernCalendar/ModernCalendarWebPart.manifest.json",
"outputPath": "./dist/modern-calendar.bundle.js"
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"modern-calendar.bundle": {
"components": [
{
"entrypoint": "./lib/webparts/modernCalendar/ModernCalendarWebPart.js",
"manifest": "./src/webparts/modernCalendar/ModernCalendarWebPart.manifest.json"
}
]
}
],
"externals": {
},
"localizedResources": {
"modernCalendarStrings": "webparts/modernCalendar/loc/{locale}.js"
}
"modernCalendarStrings": "lib/webparts/modernCalendar/loc/{locale}.js"
},
"externals": {}
}

View File

@ -1,8 +1,9 @@
{
"solution": {
"name": "spFX Modern Calendar",
"name": "SPFx Modern Calendar",
"id": "3d593a2f-73f1-486f-9dae-555c6f6b584d",
"version": "1.0.0.1"
"version": "1.0.0.1",
"includeClientSideAssets": true
},
"paths": {
"zippedPackage": "solution/modern-calendar.sppkg"

View File

@ -1,3 +1,4 @@
{
"cdnBasePath": "https://publiccdn.sharepointonline.com/pcpro365.sharepoint.com/163800463de5f9285ad4f6f9cc0a97985b96b057e062d4733849cc0fc90eeffbd6d72bb8/calendar/"
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -34,7 +34,7 @@ export default class CalendarTemplate {
`;
}
public static themeBase: string = `https://publiccdn.sharepointonline.com/pcpro365.sharepoint.com/163800463de5f9285ad4f6f9cc0a97985b96b057e062d4733849cc0fc90eeffbd6d72bb8/calendar/themes/`;
public static themeBase: string = `https://code.jquery.com/ui/1.12.1/themes/`;
public static themeNames: Array<string> = [
'default',
@ -63,12 +63,12 @@ export default class CalendarTemplate {
'ui-darkness',
'ui-lightness',
'vader'
]
];
public static theme(): IPropertyPaneDropdownOption[] {
var themes: IPropertyPaneDropdownOption[] = [];
CalendarTemplate.themeNames.forEach(function(name,index) {
themes.push({key: CalendarTemplate.themeBase + name + '/jquery-ui.min.css', text: name.toLocaleUpperCase()})
themes.push({key: CalendarTemplate.themeBase + name + '/jquery-ui.min.css', text: name.toLocaleUpperCase()});
});
return themes;
}

View File

@ -26,7 +26,7 @@ import {
Environment,
EnvironmentType
} from '@microsoft/sp-core-library';
import { EventObjectInput, OptionsInput } from 'fullcalendar';
import { EventObjectInput, OptionsInput } from 'fullcalendar';
import { Default as View } from 'fullcalendar/View';
export interface ISPLists {
@ -39,27 +39,27 @@ export interface ISPList {
}
export interface EventObjects {
value: EventObjectInput [];
value: EventObjectInput[];
}
export default class ModernCalendarWebPart extends BaseClientSideWebPart<IModernCalendarWebPartProps> {
public constructor() {
super();
//Modify with your a CDN or local path
SPComponentLoader.loadCss('//publiccdn.sharepointonline.com/pcpro365.sharepoint.com/163800463de5f9285ad4f6f9cc0a97985b96b057e062d4733849cc0fc90eeffbd6d72bb8/calendar/sweetalert2.min.css');
SPComponentLoader.loadCss('//publiccdn.sharepointonline.com/pcpro365.sharepoint.com/163800463de5f9285ad4f6f9cc0a97985b96b057e062d4733849cc0fc90eeffbd6d72bb8/calendar/fullcalendar.min.css');
// Modify with your a CDN or local path
SPComponentLoader.loadCss('https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/8.11.8/sweetalert2.min.css');
SPComponentLoader.loadCss('https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.9.0/fullcalendar.min.css');
}
public render(): void {
if (this.properties.theme != null){
if (this.properties.theme != null) {
SPComponentLoader.loadCss(this.properties.theme);
}
if (!this.properties.other){
if (!this.properties.other) {
jQuery('input[aria-label=hide-col]').parent().hide();
}
//Check required properties before rendering list
if (this.properties.listTitle == null || this.properties.start == null || this.properties.end == null || this.properties.title == null || this.properties.detail == null) {
this.domElement.innerHTML = CalendarTemplate.emptyHtml(this.properties.description);
@ -74,120 +74,121 @@ export default class ModernCalendarWebPart extends BaseClientSideWebPart<IModern
}
protected onPropertyPaneConfigurationStart(): void {
//Set a default theme
if (this.properties.theme == null){
//Set a default theme
if (this.properties.theme == null) {
this.properties.theme = CalendarTemplate.theme()[0].key.toString();
}
if (this.properties.site){
if (this.properties.site) {
this.listDisabled = false;
}
if (this.properties.listTitle && (!this.properties.start || !this.properties.end || !this.properties.title || !this.properties.detail)){
if (this.properties.listTitle && (!this.properties.start || !this.properties.end || !this.properties.title || !this.properties.detail)) {
//this._getColumnsAsync();
}
if (!this.properties.other){
if (!this.properties.other) {
jQuery('input[aria-label=hide-col]').parent().hide();
}
if (this.properties.site && this.properties.listTitle && this.properties.start && this.properties.start && this.properties.end && this.properties.title && this.properties.detail){
if (this.properties.site && this.properties.listTitle && this.properties.start && this.properties.start && this.properties.end && this.properties.title && this.properties.detail) {
this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'Configuration');
this._getSiteRootWeb()
.then((response0) => {
this._getSiteRootWeb()
.then((response0) => {
this._getSites(response0['Url'])
.then((response) => {
var sites: IPropertyPaneDropdownOption[] = [];
sites.push({key:this.context.pageContext.web.absoluteUrl, text:'This Site'});
sites.push({key:'other', text:'Other Site (Specify Url)'});
sites.push({ key: this.context.pageContext.web.absoluteUrl, text: 'This Site' });
sites.push({ key: 'other', text: 'Other Site (Specify Url)' });
for (var _key in response.value) {
if (this.context.pageContext.web.absoluteUrl != response.value[_key]['Url']){
sites.push({key: response.value[_key]['Url'], text: response.value[_key]['Title']});
}
}
this._siteOptions = sites;
if (this.properties.site ){
this._getListTitles(this.properties.site)
.then((response2) => {
this._dropdownOptions = response2.value.map((list: ISPList) => {
return {
key: list.Title,
text: list.Title
};
});
this._getListColumns(this.properties.listTitle,this.properties.site)
.then((response3) => {
var col: IPropertyPaneDropdownOption[] = [];
for (var _key in response3.value) {
col.push({key: response3.value[_key]['InternalName'], text: response3.value[_key]['Title']});
if (this.context.pageContext.web.absoluteUrl != response.value[_key]['Url']) {
sites.push({ key: response.value[_key]['Url'], text: response.value[_key]['Title'] });
}
this._columnOptions = col;
this.colsDisabled = false;
this.listDisabled = false;
this.context.propertyPane.refresh();
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.render();
});
}
this._siteOptions = sites;
if (this.properties.site) {
this._getListTitles(this.properties.site)
.then((response2) => {
this._dropdownOptions = response2.value.map((list: ISPList) => {
return {
key: list.Title,
text: list.Title
};
});
this._getListColumns(this.properties.listTitle, this.properties.site)
.then((response3) => {
var col: IPropertyPaneDropdownOption[] = [];
for (var _key in response3.value) {
col.push({ key: response3.value[_key]['InternalName'], text: response3.value[_key]['Title'] });
}
this._columnOptions = col;
this.colsDisabled = false;
this.listDisabled = false;
this.context.propertyPane.refresh();
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.render();
});
});
}
});
}
});
});
} else {
this._getSitesAsync();
this._getSitesAsync();
}
}
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
if (newValue == 'other' ){
this.properties.other = true;
this.properties.listTitle = null;
jQuery('input[aria-label=hide-col]').parent().show();
} else if (oldValue === 'other' && newValue != 'other') {
this.properties.other = false;
this.properties.siteOther = null;
this.properties.listTitle = null;
jQuery('input[aria-label=hide-col]').parent().hide();
}
this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'Configuration');
if ((propertyPath === 'site' || propertyPath === 'siteOther') && newValue) {
this.colsDisabled = true;
this.listDisabled = true;
var siteUrl = newValue;
if (this.properties.other) { siteUrl = this.properties.siteOther; } else { jQuery('input[aria-label=hide-col]').parent().hide(); }
if ((this.properties.other && this.properties.siteOther.length > 25) || !this.properties.other){
this._getListTitles(siteUrl)
.then((response) => {
this._dropdownOptions = response.value.map((list: ISPList) => {
return {
key: list.Title,
text: list.Title
};
});
this.listDisabled = false;
this.context.propertyPane.refresh();
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.render();
if (newValue == 'other') {
this.properties.other = true;
this.properties.listTitle = null;
jQuery('input[aria-label=hide-col]').parent().show();
} else if (oldValue === 'other' && newValue != 'other') {
this.properties.other = false;
this.properties.siteOther = null;
this.properties.listTitle = null;
jQuery('input[aria-label=hide-col]').parent().hide();
}
this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'Configuration');
if ((propertyPath === 'site' || propertyPath === 'siteOther') && newValue) {
this.colsDisabled = true;
this.listDisabled = true;
var siteUrl = newValue;
if (this.properties.other) { siteUrl = this.properties.siteOther; } else { jQuery('input[aria-label=hide-col]').parent().hide(); }
if ((this.properties.other && this.properties.siteOther.length > 25) || !this.properties.other) {
this._getListTitles(siteUrl)
.then((response) => {
this._dropdownOptions = response.value.map((list: ISPList) => {
return {
key: list.Title,
text: list.Title
};
});
}
} else if (propertyPath === 'listTitle' && newValue) {
// tslint:disable-next-line:no-duplicate-variable
var siteUrl = newValue;
if (this.properties.other) { siteUrl = this.properties.siteOther; }
this._getListColumns(newValue,siteUrl)
.then((response) => {
var col: IPropertyPaneDropdownOption[] = [];
for (var _key in response.value) {
col.push({key: response.value[_key]['InternalName'], text: response.value[_key]['Title']});
}
this._columnOptions = col;
this.colsDisabled = false;
this.context.propertyPane.refresh();
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.render();
});
} else {
//Handle other fields here
this.render();
this.listDisabled = false;
this.context.propertyPane.refresh();
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.render();
});
}
} else if (propertyPath === 'listTitle' && newValue) {
// tslint:disable-next-line:no-duplicate-variable
var siteUrl = newValue;
if (this.properties.other) { siteUrl = this.properties.siteOther; }
this._getListColumns(newValue, siteUrl)
.then((response) => {
var col: IPropertyPaneDropdownOption[] = [];
for (var _key in response.value) {
col.push({ key: response.value[_key]['InternalName'], text: response.value[_key]['Title'] });
}
this._columnOptions = col;
this.colsDisabled = false;
this.context.propertyPane.refresh();
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.render();
});
} else {
//Handle other fields here
this.render();
}
}
private colsDisabled: boolean = true;
@ -265,16 +266,16 @@ export default class ModernCalendarWebPart extends BaseClientSideWebPart<IModern
private _getSiteRootWeb(): Promise<ISPLists> {
return this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + `/_api/Site/RootWeb?$select=Title,Url`, SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
.then((response: SPHttpClientResponse) => {
return response.json();
});
}
private _getSites(rootWebUrl: string): Promise<ISPLists> {
return this.context.spHttpClient.get(rootWebUrl + `/_api/web/webs?$select=Title,Url`, SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
.then((response: SPHttpClientResponse) => {
return response.json();
});
}
private _getListTitles(site: string): Promise<ISPLists> {
@ -284,7 +285,7 @@ export default class ModernCalendarWebPart extends BaseClientSideWebPart<IModern
});
}
private _getListColumns(listNameColumns: string,listsite: string): Promise<any> {
private _getListColumns(listNameColumns: string, listsite: string): Promise<any> {
return this.context.spHttpClient.get(listsite + `/_api/web/lists/GetByTitle('${listNameColumns}')/Fields?$filter=Hidden eq false and ReadOnlyField eq false`, SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
@ -292,89 +293,89 @@ export default class ModernCalendarWebPart extends BaseClientSideWebPart<IModern
}
private _getListData(listName: string, site: string): Promise<any> {
return this.context.spHttpClient.get(site + `/_api/web/lists/GetByTitle('${listName}')/items?$select=${encodeURIComponent(this.properties.title)},${encodeURIComponent(this.properties.start)},${encodeURIComponent(this.properties.end)},${encodeURIComponent(this.properties.detail)},Created,Author/ID,Author/Title&$expand=Author/ID,Author/Title&$orderby=Id desc&$limit=500`,SPHttpClient.configurations.v1)
return this.context.spHttpClient.get(site + `/_api/web/lists/GetByTitle('${listName}')/items?$select=${encodeURIComponent(this.properties.title)},${encodeURIComponent(this.properties.start)},${encodeURIComponent(this.properties.end)},${encodeURIComponent(this.properties.detail)},Created,Author/ID,Author/Title&$expand=Author/ID,Author/Title&$orderby=Id desc&$limit=500`, SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}
private _renderList(items: any[]): void {
var calItems: EventObjectInput [] = items.map((list: any) => {
return {
title: list[this.properties.title],
start: list[this.properties.start],
end: list[this.properties.end],
id: list['Id'],
detail: list[this.properties.detail]
};
var calItems: EventObjectInput[] = items.map((list: any) => {
return {
title: list[this.properties.title],
start: list[this.properties.start],
end: list[this.properties.end],
id: list['Id'],
detail: list[this.properties.detail]
};
});
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
const calendarOptions:EventObjectInput = {
const calendarOptions: EventObjectInput = {
title: "test",
theme: true,
events: calItems,
eventClick: (_event) => {
var eventDetail = moment(_event['start']).format('MM/DD/YYYY hh:mm') + ' - ' + moment(_event['end']).format('MM/DD/YYYY hh:mm') + '<br>' + _event['detail'];
swal2.default(_event.title,eventDetail,'info');
var eventDetail = moment(_event['start']).format('MM/DD/YYYY hh:mm') + ' - ' + moment(_event['end']).format('MM/DD/YYYY hh:mm') + '<br>' + _event['detail'];
swal2.default(_event.title, eventDetail, 'info');
}
};
jQuery('.spfxcalendar', this.domElement).fullCalendar(calendarOptions);
}
private _getSitesAsync(): void {
this._getSiteRootWeb()
this._getSiteRootWeb()
.then((response) => {
this._getSites(response['Url'])
.then((response1) => {
var sites: IPropertyPaneDropdownOption[] = [];
sites.push({key:this.context.pageContext.web.absoluteUrl, text:'This Site'});
sites.push({key:'other', text:'Other Site (Specify Url)'});
for (var _key in response1.value) {
sites.push({key: response1.value[_key]['Url'], text: response1.value[_key]['Title']});
}
this._siteOptions = sites;
this.context.propertyPane.refresh();
var siteUrl = this.properties.site;
if (this.properties.other) { siteUrl = this.properties.siteOther; }
this._getListTitles(siteUrl)
.then((response2) => {
this._dropdownOptions = response2.value.map((list: ISPList) => {
return {
key: list.Title,
text: list.Title
};
});
this.context.propertyPane.refresh();
if (this.properties.listTitle) {
this._getListColumns(this.properties.listTitle,this.properties.site)
.then((response3) => {
var col: IPropertyPaneDropdownOption[] = [];
for (var _key in response3.value) {
col.push({key: response3.value[_key]['InternalName'], text: response3.value[_key]['Title']});
}
this._columnOptions = col;
this.colsDisabled = false;
this.listDisabled = false;
this.context.propertyPane.refresh();
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.render();
});
.then((response1) => {
var sites: IPropertyPaneDropdownOption[] = [];
sites.push({ key: this.context.pageContext.web.absoluteUrl, text: 'This Site' });
sites.push({ key: 'other', text: 'Other Site (Specify Url)' });
for (var _key in response1.value) {
sites.push({ key: response1.value[_key]['Url'], text: response1.value[_key]['Title'] });
}
this._siteOptions = sites;
this.context.propertyPane.refresh();
var siteUrl = this.properties.site;
if (this.properties.other) { siteUrl = this.properties.siteOther; }
this._getListTitles(siteUrl)
.then((response2) => {
this._dropdownOptions = response2.value.map((list: ISPList) => {
return {
key: list.Title,
text: list.Title
};
});
this.context.propertyPane.refresh();
if (this.properties.listTitle) {
this._getListColumns(this.properties.listTitle, this.properties.site)
.then((response3) => {
var col: IPropertyPaneDropdownOption[] = [];
for (var _key in response3.value) {
col.push({ key: response3.value[_key]['InternalName'], text: response3.value[_key]['Title'] });
}
this._columnOptions = col;
this.colsDisabled = false;
this.listDisabled = false;
this.context.propertyPane.refresh();
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.render();
});
}
});
});
});
});
}
private _renderListAsync(): void {
var siteUrl = this.properties.site;
if (this.properties.other) { siteUrl = this.properties.siteOther; }
this._getListData(this.properties.listTitle, siteUrl).then((response) => {
this._renderList(response.value);
}).catch((err) => {
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.context.statusRenderer.renderError(this.domElement,"There was an error loading your list, please verify the selected list has Calendar Events or choose a new list.");
});
var siteUrl = this.properties.site;
if (this.properties.other) { siteUrl = this.properties.siteOther; }
this._getListData(this.properties.listTitle, siteUrl).then((response) => {
this._renderList(response.value);
}).catch((err) => {
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.context.statusRenderer.renderError(this.domElement, "There was an error loading your list, please verify the selected list has Calendar Events or choose a new list.");
});
}
}

View File

@ -26,7 +26,7 @@ Note: This approach will not "pollute" your solution with additional resources o
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/drop-1.10.0-green.svg)
![1.10.0](https://img.shields.io/badge/drop-1.10.0-green.svg)
## Applies to
@ -49,6 +49,7 @@ Version|Date|Comments
-------|----|--------
1.0|January 24, 2019|Initial release
1.1|February 05, 2020|Update to SPFx 1.10.0
1.2|June 04, 2020|Added full-width support
## Disclaimer

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@
"properties": {
"requiresPageRefresh": false,
"customWorkbenchStyles": true,
"customWorkbenchStylesFullWidth": false,
"previewMode": true
}
}]

View File

@ -8,6 +8,7 @@ import * as strings from 'WorkbenchCustomizerWebPartStrings';
export interface IWorkbenchCustomizerWebPartProps {
requiresPageRefresh: boolean;
customWorkbenchStyles: boolean;
customWorkbenchStylesFullWidth: boolean;
previewMode: boolean;
}
@ -26,6 +27,10 @@ export default class WorkbenchCustomizerWebPart extends BaseClientSideWebPart<IW
await import('./styles/customWorkbenchStyles.module.scss');
}
if (this.properties.customWorkbenchStyles && this.properties.customWorkbenchStylesFullWidth) {
await import('./styles/customWorkbenchStylesFullWidth.module.scss');
}
if (this.properties.previewMode) {
const previewBtn = document.getElementsByName("Preview")[0];
previewBtn.click();
@ -68,6 +73,10 @@ export default class WorkbenchCustomizerWebPart extends BaseClientSideWebPart<IW
PropertyPaneToggle('customWorkbenchStyles', {
label: strings.CustomWorkbenchStylesFieldLabel
}),
PropertyPaneToggle('customWorkbenchStylesFullWidth', {
label: strings.customWorkbenchStylesFullWidthFieldLabel,
disabled: !this.properties.customWorkbenchStyles
}),
PropertyPaneToggle('previewMode', {
label: strings.PreviewModeFieldLabel
})

View File

@ -1,8 +1,9 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"PropertyPaneDescription": "Web part that can customize the behaviour of the workbench page to work around some of the existing limitations. It's recommended that you refresh the page after changing web part properties.",
"BasicGroupName": "Configuration",
"CustomWorkbenchStylesFieldLabel": "Enable custom styles for Workbench",
"customWorkbenchStylesFullWidthFieldLabel": "Enable custom styles for full-width",
"PreviewModeFieldLabel": "Enable Preview mode by default",
}
});

View File

@ -2,6 +2,7 @@ declare interface IWorkbenchCustomizerWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
CustomWorkbenchStylesFieldLabel: string;
customWorkbenchStylesFullWidthFieldLabel: string;
PreviewModeFieldLabel: string;
}

View File

@ -1,5 +1,4 @@
:global #workbenchPageContent {
// max-width: 1316px;
max-width: 100%;
left: 0;
right: 0;

View File

@ -0,0 +1,24 @@
:global #workbenchPageContent {
& > div {
& > div {
& > div {
.CanvasComponent {
.CanvasZone {
max-width: 100%;
padding-left: 0;
padding-right: 0;
.CanvasSection {
padding-left: 0;
padding-right: 0;
.ControlZone {
padding: 0;
}
}
}
}
}
}
}
}

View File

@ -17334,9 +17334,9 @@
}
},
"websocket-extensions": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true
},
"whatwg-encoding": {

View File

@ -6130,7 +6130,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -6346,12 +6347,14 @@
"minimist": {
"version": "1.2.5",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -6370,6 +6373,7 @@
"version": "0.5.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
@ -6558,7 +6562,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -6614,6 +6619,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -6657,12 +6663,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -17540,9 +17548,9 @@
}
},
"websocket-extensions": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true
},
"whatwg-encoding": {

View File

@ -19685,9 +19685,9 @@
}
},
"websocket-extensions": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true
},
"whatwg-encoding": {

View File

@ -10029,8 +10029,8 @@ websocket-driver@>=0.5.1:
websocket-extensions ">=0.1.1"
websocket-extensions@>=0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
version "0.1.4"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
version "1.0.3"

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,13 @@
{
"@microsoft/generator-sharepoint": {
"plusBeta": true,
"isCreatingSolution": true,
"environment": "spo",
"version": "1.10.0",
"libraryName": "react-outlook-add-todo-task",
"libraryId": "3702cca0-5492-4bb3-b393-3fbdd553e79a",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,87 @@
# Create To Do Task from Email (Outlook Add in)
## Summary
This webpart allows us to create a new To Do task using the new ToDo MS Graph endpoint. If deployed as an Outlook Add In, the Task title comes from email subject. Let´s say this is similar to the OOTB "Flag" action, but here you can select the ToDo List where to store the Task, and you can modify the Title before adding it.
![Create ToDo task](./assets/spfx-todo-outlook.gif)
## Graph To-Do Preview endpoints
As of today, To Do endpoint is not very well documented yet. It was presented in latest Build 2020. Here are some of the basic operations. You can get more information and see the Build session from this link: [https://developer.microsoft.com/en-us/office/blogs/introducing-the-new-microsoft-graph-to-do-api/](https://developer.microsoft.com/en-us/office/blogs/introducing-the-new-microsoft-graph-to-do-api/)
### Get lists
GET https://graph.microsoft.com/beta/me/todo/lists
### Create new List
POST https://graph.microsoft.com/beta/me/todo/lists
```json
{
displayName: "My new List"
}
```
### Get tasks in list
GET https://graph.microsoft.com/beta/me/todo/lists/{listId}/tasks
### Create new Task in List
POST https://graph.microsoft.com/beta/me/todo/lists/{listId}/tasks
```json
{
"importance": "high",
"status": "notStarted",
"title": "New task to do",
"body": {
"content": "You have a new task to do",
"contentType": "text"
}
}
```
## Used SharePoint Framework Version
![SPFx v1.10.0](https://img.shields.io/badge/SPFx-1.10.0-green.svg)
## Solution
Solution|Author(s)
--------|---------
react-outlook-add-todo-task|Luis Mañez (MVP, [ClearPeople](http://www.clearpeople.com), @luismanez)
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|Jun 3, 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 repo
* gulp bundle --ship
* gulp package-solution --ship
* deploy package to SharePoint App Catalog (check tenant deploy)
* Using O365 CLI to configure MS Graph permissions to allow creating ToDo tasks
```ps
spo serviceprincipal grant add --resource "Microsoft Graph" --scope "Tasks.ReadWrite"
```
* deploy spfx solution as Outlook add-in following instructions here: [https://docs.microsoft.com/en-us/sharepoint/dev/spfx/office-addins-create#deployment-of-your-add-in](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/office-addins-create#deployment-of-your-add-in)
## Features
This sample illustrates the following concepts on top of the SharePoint Framework:
* New ToDo MS Graph endpoint
* Using __MSGraphClient__
* Outlook SPFx add-in
* Using _async / await_ for the async calls
* FluentUI components
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-outlook-add-todo-task" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

View File

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

View File

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

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "react-outlook-add-todo-task",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,14 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-outlook-add-todo-task-client-side-solution",
"id": "3702cca0-5492-4bb3-b393-3fbdd553e79a",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/react-outlook-add-todo-task.sppkg"
}
}

View File

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

View File

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

View File

@ -0,0 +1,7 @@
'use strict';
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
build.initialize(require('gulp'));

View File

@ -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>d459dda9-d0a0-4e5c-b996-1e48327a306d</Id>
<Version>1.0.0.0</Version>
<ProviderName>Luis Manez MVP</ProviderName>
<DefaultLocale>en-US</DefaultLocale>
<DisplayName DefaultValue="To Do PnP"/>
<Description DefaultValue="Add To Do tasks from an email"/>
<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=d459dda9-d0a0-4e5c-b996-1e48327a306d"/>
<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=d459dda9-d0a0-4e5c-b996-1e48327a306d" />
</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>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
{
"name": "react-outlook-add-todo-task",
"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": {
"react": "16.8.5",
"react-dom": "16.8.5",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"office-ui-fabric-react": "6.189.2",
"@microsoft/sp-core-library": "1.10.0-plusbeta",
"@microsoft/sp-property-pane": "1.10.0-plusbeta",
"@microsoft/sp-webpart-base": "1.10.0-plusbeta",
"@microsoft/sp-lodash-subset": "1.10.0-plusbeta",
"@microsoft/sp-office-ui-fabric-core": "1.10.0-plusbeta",
"@types/webpack-env": "1.13.1",
"@types/es6-promise": "0.0.33"
},
"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"
}
}

View File

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

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "d459dda9-d0a0-4e5c-b996-1e48327a306d",
"alias": "CreateTaskWebPart",
"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": "CreateTask" },
"description": { "default": "Add new Task to MS To Do from an Email" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "CreateTask"
}
}]
}

View File

@ -0,0 +1,104 @@
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 { MSGraphClient } from '@microsoft/sp-http';
import * as strings from 'CreateTaskWebPartStrings';
import CreateTask from './components/CreateTask';
import { ICreateTaskProps } from './components/ICreateTaskProps';
import { ICreateTaskContext } from './models/ICreateTaskContext';
export interface ICreateTaskWebPartProps {
}
export default class CreateTaskWebPart extends BaseClientSideWebPart<ICreateTaskWebPartProps> {
private _graphHttpClient: MSGraphClient;
protected onInit(): Promise<void> {
return new Promise((resolve, reject) => {
this.context.msGraphClientFactory.getClient().then(client => {
this._graphHttpClient = client;
resolve();
}).catch(error => {
console.log(error);
reject(error);
});
});
}
private _getContext(): ICreateTaskContext {
if (this.context.sdks.office) {
const mailboxItem: Office.MessageRead = this.context.sdks.office.context.mailbox.item;
const context: ICreateTaskContext = {
graphHttpClient: this._graphHttpClient,
siteUrl: this.context.pageContext.web.absoluteUrl,
item: {
id: mailboxItem.itemId,
subject: mailboxItem.subject,
from: mailboxItem.from.emailAddress,
to: mailboxItem.to[0].emailAddress
}
};
return context;
}
else {
const context: ICreateTaskContext = {
item: null,
graphHttpClient: this._graphHttpClient,
siteUrl: this.context.pageContext.web.absoluteUrl
};
return context;
}
}
public render(): void {
const context: ICreateTaskContext = this._getContext();
const element: React.ReactElement<ICreateTaskProps> = React.createElement(
CreateTask,
{
context: context
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,74 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.createTask {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,187 @@
import * as React from "react";
import styles from "./CreateTask.module.scss";
import { ICreateTaskProps } from "./ICreateTaskProps";
import { ITodoList, ITodoListItem } from "../models/ITodo";
import { ICreateTaskState } from "./ICreateTaskState";
import {
Stack,
Dropdown,
IDropdownOption,
Label,
TextField,
IStackTokens,
IDropdownStyles,
PrimaryButton,
MessageBar,
Link,
MessageBarType
} from "office-ui-fabric-react";
import { MSGraphClient } from "@microsoft/sp-http";
export default class CreateTask extends React.Component<
ICreateTaskProps,
ICreateTaskState
> {
constructor(props: ICreateTaskProps) {
super(props);
this.state = {
listItemAdded: false,
selectedList: undefined,
todoLists: [],
newTaskTitle: this.props.context.item
? this.props.context.item.subject
: "New Task title here!",
showSelectListError: false
};
this._onDropdownChange = this._onDropdownChange.bind(this);
this._onChangeNewTaskTitle = this._onChangeNewTaskTitle.bind(this);
this._createTask = this._createTask.bind(this);
}
private async _getTodoLists(): Promise<ITodoList[]> {
const endpoint: string = "https://graph.microsoft.com/beta/me/todo/lists";
const response: any = await this.props.context.graphHttpClient
.api(endpoint)
.get();
const graphResponse: any = response.value;
const todoLists: ITodoList[] = graphResponse.map((item) => {
const list: ITodoList = { id: item.id, displayName: item.displayName };
return list;
});
return todoLists;
}
public async componentDidMount(): Promise<void> {
const lists: ITodoList[] = await this._getTodoLists();
this.setState({
todoLists: lists,
});
}
public render(): React.ReactElement<ICreateTaskProps> {
if (this.state.todoLists.length <= 0) {
return <div>Loading to-do lists...</div>;
}
const options: IDropdownOption[] = this.state.todoLists.map((item) => {
const dropdownOption: IDropdownOption = {
key: item.id,
text: item.displayName,
};
return dropdownOption;
});
const horizontalGapStackTokens: IStackTokens = {
childrenGap: 10,
};
const dropdownStyles: Partial<IDropdownStyles> = {
dropdown: { width: 200 },
};
const ActionInfo =
<MessageBar messageBarType={MessageBarType.success}>
Your task has been added!
<Link href="https://to-do.office.com/tasks" target="_blank">
Open To Do App
</Link>
</MessageBar>
;
return (
<Stack verticalAlign="end" tokens={horizontalGapStackTokens} styles={{root: {padding: '10px'}}}>
<Label styles={{root: {fontSize:'18px', fontWeight:'bold'}}}>To Do Outlook SPFx Add-in</Label>
<Stack>
<Dropdown
placeholder="Select a to-do list"
options={options}
onChange={this._onDropdownChange}
styles={dropdownStyles}
errorMessage={this.state.showSelectListError ? 'Please select a To Do list' : undefined}
/>
</Stack>
<Stack styles={{ root: { width: "100%" } }}>
<TextField
value={this.state.newTaskTitle}
onChange={this._onChangeNewTaskTitle}
/>
</Stack>
<Stack>
<PrimaryButton text="Add Task" onClick={this._createTask} />
</Stack>
<Stack>
{this.state.listItemAdded ? ActionInfo : null}
</Stack>
</Stack>
);
}
private _onDropdownChange = (
event: React.FormEvent<HTMLDivElement>,
item: IDropdownOption
): void => {
const selectedList: ITodoList = this.state.todoLists.filter(
(i) => i.id === item.key
)[0];
this.setState({
selectedList: selectedList,
showSelectListError: false
});
}
private _onChangeNewTaskTitle(
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
) {
if (newValue) {
this.setState({
newTaskTitle: newValue,
});
}
}
private async _createTask(): Promise<void> {
try {
if (!this.state.selectedList) {
this.setState({
showSelectListError: true
});
return;
}
const listId: string = this.state.selectedList.id;
const taskTitle: string = this.state.newTaskTitle;
const endpoint: string = `https://graph.microsoft.com/beta/me/todo/lists/${listId}/tasks`;
const body: ITodoListItem = {
importance: "high",
status: "notStarted",
title: taskTitle,
body: {
content: "You have a new task to do added from SPFx component!",
contentType: "text",
},
};
var response: any = await this.props.context.graphHttpClient
.api(endpoint)
.post(body);
this.setState({
listItemAdded: true
});
console.log(response);
} catch (e) {
console.log(e);
}
}
}

View File

@ -0,0 +1,5 @@
import { ICreateTaskContext } from "../models/ICreateTaskContext";
export interface ICreateTaskProps {
context: ICreateTaskContext;
}

View File

@ -0,0 +1,10 @@
import { ITodoList } from "../models/ITodo";
import { Dictionary } from "../models/Dictionary";
export interface ICreateTaskState {
todoLists: ITodoList[];
selectedList: ITodoList;
newTaskTitle: string;
listItemAdded: boolean;
showSelectListError: boolean;
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface ICreateTaskWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'CreateTaskWebPartStrings' {
const strings: ICreateTaskWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,3 @@
export interface Dictionary<T> {
[key: string]: T | undefined;
}

View File

@ -0,0 +1,8 @@
import { MSGraphClient } from '@microsoft/sp-http';
import { IMailItem } from "./IMailItem";
export interface ICreateTaskContext {
item: IMailItem;
graphHttpClient: MSGraphClient;
siteUrl: string;
}

View File

@ -0,0 +1,6 @@
export interface IMailItem {
id: string;
subject: string;
from: string;
to: string;
}

View File

@ -0,0 +1,14 @@
export interface ITodoList {
id: string;
displayName: string;
}
export interface ITodoListItem {
importance: "low" | "normal" | "high";
status: "notStarted" | "inProgress" | "completed" | "waitingOnOthers" | "deferred";
title: string;
body: {
content: string;
contentType: "text" | "html";
};
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

View File

@ -0,0 +1,30 @@
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}

View File

@ -1,6 +1,6 @@
{
"name": "react-project-online",
"version": "0.0.1",
"version": "1.4.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -7184,7 +7184,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -7205,12 +7206,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -7225,17 +7228,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -7352,7 +7358,8 @@
"inherits": {
"version": "2.0.4",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -7364,6 +7371,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -7378,6 +7386,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -7385,12 +7394,14 @@
"minimist": {
"version": "1.2.5",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -7409,6 +7420,7 @@
"version": "0.5.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
@ -7470,7 +7482,8 @@
"npm-normalize-package-bin": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"npm-packlist": {
"version": "1.4.8",
@ -7498,7 +7511,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -7510,6 +7524,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -7587,7 +7602,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -7623,6 +7639,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -7642,6 +7659,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -7685,12 +7703,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -17332,9 +17352,9 @@
}
},
"websocket-extensions": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true
},
"whatwg-encoding": {

View File

@ -7625,8 +7625,8 @@ websocket-driver@>=0.5.1:
websocket-extensions ">=0.1.1"
websocket-extensions@>=0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7"
version "0.1.4"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
whatwg-fetch@0.11.0:
version "0.11.0"

View File

@ -17895,9 +17895,9 @@
}
},
"websocket-extensions": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true
},
"whatwg-encoding": {

View File

@ -10546,9 +10546,9 @@ websocket-driver@>=0.5.1:
websocket-extensions ">=0.1.1"
websocket-extensions@>=0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==
version "0.1.4"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
version "1.0.5"

File diff suppressed because it is too large Load Diff