mirror of
https://github.com/pnp/sp-dev-fx-webparts.git
synced 2025-02-07 13:38:39 +00:00
Removed xml2js references (#760)
This commit is contained in:
parent
67f63e2bc0
commit
780348ff39
@ -12,6 +12,7 @@
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"sourceMaps": true,
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack:///.././src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||
@ -28,6 +29,7 @@
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"sourceMaps": true,
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack:///.././src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||
|
@ -2,11 +2,10 @@
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.7.0",
|
||||
"version": "1.7.1",
|
||||
"libraryName": "react-calendar-feed",
|
||||
"libraryId": "dd42aa00-b07d-48a2-8896-cc2f8c0d3fae",
|
||||
"libraryId": "25653136-fc83-4abe-b9d2-a4ac041959d5",
|
||||
"packageManager": "npm",
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
# React Calendar Feed Web Part
|
||||
|
||||
## Summary
|
||||
|
||||
This web part uses RSS event feeds, iCal feeds, or WordPress calendar feeds and renders events using a look and feel that is consistent with the SharePoint out-of-the-box Group calendar/events web part.
|
||||
|
||||
![The web part in action](./assets/react-calendar-feed-demo.gif)
|
||||
@ -15,7 +16,7 @@ For more information about how this solution was built, including some design de
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![SPFx v1.7](https://img.shields.io/badge/SPFx-1.7-green.svg)
|
||||
![SPFx v1.7.1](https://img.shields.io/badge/SPFx-1.7.1-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
@ -48,6 +49,7 @@ Version|Date|Comments
|
||||
1.0|May 15, 2018|Initial release
|
||||
2.0|June 25, 2018|Converted to SPFx 1.5 and added Exchange Public Calendar support
|
||||
3.0|November 9, 2018|Converted to SPFx 1.7; Added SharePoint Calendar feed
|
||||
4.0|January 16. 2019|Converted to SPFx 1.7.1; Removed NPM libraries associated with issue #708.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
@ -57,31 +59,32 @@ Version|Date|Comments
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Clone this repository
|
||||
- in the command line run:
|
||||
- `npm install`
|
||||
- `gulp serve`
|
||||
- Insert the web part on a page
|
||||
- When prompted to configure the web part, select **Configure** to launch the web part property pane.
|
||||
- Select a feed type (RSS, iCal, WordPress, or Mock if using the debug solution)
|
||||
- Provide the feed's URL. If using _Mock_, provide any valid URL (value will be ignored). If you wish to use a SharePoint calendar feed, provide the URL to the list (e.g.: https://yourtenant.sharepoint.com/sites/sitename/lists/eventlistname)
|
||||
- Specify a date range (one week, two weeks, one month, one quarter, one year)
|
||||
- Specify a maximum number of events to retrieve
|
||||
- If necessary, specify to use a proxy. Use this option if you encounter issues where your feed provider does not accept your tenant URL as a CORS origin.
|
||||
- If desired, specify how long (in minutes) you want to expire your users' local storage and refresh the events.
|
||||
* Clone this repository
|
||||
* in the command line run:
|
||||
* `npm install`
|
||||
* `gulp serve`
|
||||
* Insert the web part on a page
|
||||
* When prompted to configure the web part, select **Configure** to launch the web part property pane.
|
||||
* Select a feed type (RSS, iCal, WordPress, or Mock if using the debug solution)
|
||||
* Provide the feed's URL. If using _Mock_, provide any valid URL (value will be ignored). If you wish to use a SharePoint calendar feed, provide the URL to the list (e.g.: https://yourtenant.sharepoint.com/sites/sitename/lists/eventlistname)
|
||||
* Specify a date range (one week, two weeks, one month, one quarter, one year)
|
||||
* Specify a maximum number of events to retrieve
|
||||
* If necessary, specify to use a proxy. Use this option if you encounter issues where your feed provider does not accept your tenant URL as a CORS origin.
|
||||
* If desired, specify how long (in minutes) you want to expire your users' local storage and refresh the events.
|
||||
|
||||
## Features
|
||||
|
||||
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
- Rendering different views based on size
|
||||
- Loading third-party CSS from a CDN
|
||||
- Excluding mock data from production build
|
||||
- Using @pnp/spfx-property-controls
|
||||
- Using @pnp/spfx-controls-react
|
||||
- Using localStorage to cache results locally
|
||||
- Creating shared components and services
|
||||
- Creating extensible services
|
||||
- Using a proxy to resolve CORS issues
|
||||
- Retrieving SharePoint events from a list with a filter
|
||||
* Rendering different views based on size
|
||||
* Loading third-party CSS from a CDN
|
||||
* Excluding mock data from production build
|
||||
* Using @pnp/spfx-property-controls
|
||||
* Using @pnp/spfx-controls-react
|
||||
* Using localStorage to cache results locally
|
||||
* Creating shared components and services
|
||||
* Creating extensible services
|
||||
* Using a proxy to resolve CORS issues
|
||||
* Retrieving SharePoint events from a list with a filter
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-calendar-feed" />
|
||||
|
@ -14,6 +14,7 @@
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"CalendarFeedSummaryWebPartStrings": "lib/webparts/calendarFeedSummary/loc/{locale}.js",
|
||||
"CalendarServicesStrings": "lib/shared/services/CalendarService/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
|
||||
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
|
||||
}
|
||||
|
@ -2,11 +2,9 @@
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-calendar-feed-client-side-solution",
|
||||
"id": "dd42aa00-b07d-48a2-8896-cc2f8c0d3fae",
|
||||
"version": "3.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"isDomainIsolated": false,
|
||||
"iconPath": "images/appicon.png"
|
||||
"id": "25653136-fc83-4abe-b9d2-a4ac041959d5",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-calendar-feed.sppkg"
|
||||
|
8899
samples/react-calendar-feed/package-lock.json
generated
8899
samples/react-calendar-feed/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-calendar-feed",
|
||||
"version": "0.0.1",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@ -11,32 +11,37 @@
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.7.0",
|
||||
"@microsoft/sp-lodash-subset": "1.7.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.7.0",
|
||||
"@microsoft/sp-webpart-base": "1.7.0",
|
||||
"@pnp/pnpjs": "^1.2.5",
|
||||
"@pnp/spfx-controls-react": "^1.10.0",
|
||||
"@pnp/spfx-property-controls": "^1.11.0",
|
||||
"@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",
|
||||
"@pnp/common": "^1.2.8",
|
||||
"@pnp/logging": "^1.2.8",
|
||||
"@pnp/odata": "^1.2.8",
|
||||
"@pnp/sp": "^1.2.8",
|
||||
"@pnp/spfx-controls-react": "^1.11.0",
|
||||
"@pnp/spfx-property-controls": "^1.13.1",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "16.4.2",
|
||||
"@types/react-dom": "16.0.5",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"feedparser": "^2.2.9",
|
||||
"ical.js": "^1.3.0",
|
||||
"ics-js": "^0.10.2",
|
||||
"moment": "^2.22.2",
|
||||
"react": "16.3.2",
|
||||
"react-dom": "16.3.2",
|
||||
"react-slick": "^0.23.2",
|
||||
"slick-carousel": "^1.8.1",
|
||||
"xml-js": "^1.6.8",
|
||||
"xml2js": "^0.4.19"
|
||||
"rss-parser": "^3.6.2",
|
||||
"slick-carousel": "^1.8.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.7.0",
|
||||
"@microsoft/sp-tslint-rules": "1.7.0",
|
||||
"@microsoft/sp-module-interfaces": "1.7.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.7.0",
|
||||
"@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",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
|
@ -73,14 +73,23 @@ export abstract class BaseCalendarService implements ICalendarService {
|
||||
*/
|
||||
protected fetchResponse(feedUrl: string): Promise<HttpClientResponse> {
|
||||
// would love to use a different approach to workaround CORS issues
|
||||
const requestUrl: string = this.UseCORS ?
|
||||
`https://cors-anywhere.herokuapp.com/${feedUrl}` :
|
||||
feedUrl;
|
||||
const requestUrl: string = this.getCORSUrl(feedUrl);
|
||||
|
||||
return this.Context.httpClient.fetch(requestUrl,
|
||||
HttpClient.configurations.v1, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URL or a CORS-formatted URL
|
||||
* @param feedUrl The URL for the feed
|
||||
*/
|
||||
protected getCORSUrl(feedUrl: string): string {
|
||||
// would love to use a different approach to workaround CORS issues
|
||||
return this.UseCORS ?
|
||||
`https://cors-anywhere.herokuapp.com/${feedUrl}` :
|
||||
feedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrives the response and returns a JSON object
|
||||
* @param feedUrl The URL where to retrieve the events
|
||||
|
@ -5,57 +5,58 @@ import { iCalCalendarService } from "./iCalCalendarService";
|
||||
import { ExchangePublicCalendarService } from "./ExchangePublicCalendarService";
|
||||
import { SharePointCalendarService } from "./SharePointCalendarService";
|
||||
|
||||
// Localization
|
||||
import * as strings from "CalendarServicesStrings";
|
||||
|
||||
export enum CalendarServiceProviderType {
|
||||
SharePoint = "SharePoint",
|
||||
WordPress = "WordPress",
|
||||
Exchange = "Exchange",
|
||||
iCal = "iCal",
|
||||
RSS = "RSS",
|
||||
Mock = "Mock"
|
||||
}
|
||||
|
||||
export class CalendarServiceProviderList {
|
||||
public static getProviders(): any[] {
|
||||
const providers: any[] = [];
|
||||
const providers: any[] = [
|
||||
{
|
||||
label: strings.SharePointProviderName,
|
||||
key: CalendarServiceProviderType.SharePoint,
|
||||
initialize: () => new SharePointCalendarService()
|
||||
},
|
||||
{
|
||||
label: strings.ExchangeProviderName,
|
||||
key: CalendarServiceProviderType.Exchange,
|
||||
initialize: () => new ExchangePublicCalendarService()
|
||||
},
|
||||
{
|
||||
label: strings.WordPressProviderName,
|
||||
key: CalendarServiceProviderType.WordPress,
|
||||
initialize: () => new WordPressFullCalendarService()
|
||||
},
|
||||
{
|
||||
label: strings.iCalProviderName,
|
||||
key: CalendarServiceProviderType.iCal,
|
||||
initialize: () => new iCalCalendarService()
|
||||
},
|
||||
{
|
||||
label: strings.RSSProviderName,
|
||||
key: CalendarServiceProviderType.RSS,
|
||||
initialize: () => new RSSCalendarService()
|
||||
}
|
||||
];
|
||||
|
||||
// only include the Mock service provider in DEBUG
|
||||
if (DEBUG) {
|
||||
providers.push({
|
||||
label: "Mock",
|
||||
key: "mock",
|
||||
label: strings.MockProviderName,
|
||||
key: CalendarServiceProviderType.Mock,
|
||||
initialize: () => new MockCalendarService()
|
||||
});
|
||||
}
|
||||
|
||||
providers.push({
|
||||
label: "SharePoint Calendar",
|
||||
key: "sharepoint",
|
||||
initialize: () => new SharePointCalendarService()
|
||||
});
|
||||
|
||||
providers.push({
|
||||
label: "Exchange Public Calendar",
|
||||
key: "exchange",
|
||||
initialize: () => new ExchangePublicCalendarService()
|
||||
});
|
||||
|
||||
providers.push({
|
||||
label: "WordPress",
|
||||
key: "wordpress",
|
||||
initialize: () => new WordPressFullCalendarService()
|
||||
});
|
||||
|
||||
providers.push({
|
||||
label: "iCal",
|
||||
key: "ical",
|
||||
initialize: () => new iCalCalendarService()
|
||||
});
|
||||
|
||||
providers.push({
|
||||
label: "RSS",
|
||||
key: "RSS",
|
||||
initialize: () => new RSSCalendarService()
|
||||
});
|
||||
|
||||
return providers;
|
||||
}
|
||||
}
|
||||
|
||||
export enum CalendarServiceProviderType {
|
||||
SharePoint = "SharePoint",
|
||||
WordPress = "WordPress",
|
||||
iCal = "iCal",
|
||||
RSS = "RSS",
|
||||
Mock = "Mock"
|
||||
}
|
||||
|
@ -6,12 +6,10 @@
|
||||
* every one I could find on NPM and GitHub and found that they did not meet my needs.
|
||||
* I'm open to suggestions, though, if you have a library that you think would work better.
|
||||
*/
|
||||
import { HttpClientResponse } from "@microsoft/sp-http";
|
||||
import * as convert from "xml-js";
|
||||
import { ICalendarService } from "..";
|
||||
import { BaseCalendarService } from "../BaseCalendarService";
|
||||
import { ICalendarEvent } from "../ICalendarEvent";
|
||||
import { unescape } from "@microsoft/sp-lodash-subset";
|
||||
import RSSParser from 'rss-parser';
|
||||
|
||||
export class RSSCalendarService extends BaseCalendarService implements ICalendarService {
|
||||
constructor() {
|
||||
@ -20,83 +18,25 @@ export class RSSCalendarService extends BaseCalendarService implements ICalendar
|
||||
}
|
||||
|
||||
public getEvents = (): Promise<ICalendarEvent[]> => {
|
||||
const parameterizedFeedUrl: string = this.replaceTokens(this.FeedUrl, this.EventRange);
|
||||
const parameterizedFeedUrl: string = this.getCORSUrl(this.replaceTokens(this.FeedUrl, this.EventRange));
|
||||
|
||||
return this.fetchResponse(parameterizedFeedUrl)
|
||||
.then((response: HttpClientResponse) => response.text())
|
||||
.then((xml: string): ICalendarEvent[] => {
|
||||
// convert RSS feed from XML to JSON
|
||||
const results: any = convert.xml2js(xml, { compact: false });
|
||||
if (results === undefined) {
|
||||
throw "No results";
|
||||
}
|
||||
let parser = new RSSParser();
|
||||
return parser.parseURL(parameterizedFeedUrl).then(feed => {
|
||||
|
||||
// get the RSS element
|
||||
const rss: any = results.elements[0];
|
||||
if (rss === undefined) {
|
||||
throw "No root";
|
||||
}
|
||||
|
||||
// get the first channel in the RSS feed
|
||||
const channel: any = rss.elements[0];
|
||||
if (channel === undefined) {
|
||||
throw "No channel";
|
||||
}
|
||||
|
||||
// get all items in the feed
|
||||
const items: any[] = channel.elements.filter(e => e.name === "item" && e.type === "element");
|
||||
|
||||
// convert each RSS element to an event
|
||||
let events: ICalendarEvent[] = items.map((item: any) => {
|
||||
let title: string = this._getElementValue(item, "title");
|
||||
let link: string = this._getElementValue(item, "link");
|
||||
let pubDate: Date = new Date(this._getElementValue(item, "pubDate"));
|
||||
let description: string = this._getElementValue(item, "description");
|
||||
let events: ICalendarEvent[] = feed.items.map(item => {
|
||||
let pubDate: Date = new Date(item.isoDate);
|
||||
return {
|
||||
title: title,
|
||||
title: item.title,
|
||||
start: pubDate,
|
||||
end: pubDate,
|
||||
url: link,
|
||||
allDay: true,
|
||||
description: description,
|
||||
url: item.link,
|
||||
allDay: false,
|
||||
description: item.content,
|
||||
location: undefined, // no equivalent in RSS
|
||||
category: undefined // no equivalent in RSS
|
||||
category: item.categories && item.categories.length > 0 && item.categories[0]
|
||||
};
|
||||
});
|
||||
|
||||
return this.filterEventRange(events);
|
||||
}).catch((error: any) => {
|
||||
console.log("Exception caught by catch in RSS provider", error);
|
||||
throw error;
|
||||
return events;
|
||||
});
|
||||
}
|
||||
|
||||
private _getElementValue(item: any, fieldName: string): string {
|
||||
if (!item || !item.elements) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// get the elements
|
||||
const filteredElements: any[] = item.elements.filter(e => e.name === fieldName);
|
||||
|
||||
if (filteredElements.length < 1 || filteredElements[0].elements.length < 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const firstElement: any = filteredElements[0].elements[0];
|
||||
|
||||
switch (firstElement.type) {
|
||||
case "text":
|
||||
return firstElement.text;
|
||||
case "cdata":
|
||||
let cdata:string = firstElement.cdata;
|
||||
if (cdata !== undefined) {
|
||||
cdata = unescape(cdata);
|
||||
}
|
||||
return cdata;
|
||||
}
|
||||
|
||||
console.log("Found an RSS field type I didn't know", firstElement.type);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { HttpClientResponse } from "@microsoft/sp-http";
|
||||
import { ICalendarService } from "..";
|
||||
import { BaseCalendarService } from "../BaseCalendarService";
|
||||
import { ICalendarEvent } from "../ICalendarEvent";
|
||||
import { Web, sp } from "@pnp/sp";
|
||||
import { Web } from "@pnp/sp";
|
||||
import { combine } from "@pnp/common";
|
||||
|
||||
export class SharePointCalendarService extends BaseCalendarService
|
||||
|
10
samples/react-calendar-feed/src/shared/services/CalendarService/loc/en-us.js
vendored
Normal file
10
samples/react-calendar-feed/src/shared/services/CalendarService/loc/en-us.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
define([], function() {
|
||||
return {
|
||||
"SharePointProviderName": "SharePoint Calendar",
|
||||
"WordPressProviderName": "WordPress",
|
||||
"ExchangeProviderName": "Exchange Public Calendar",
|
||||
"iCalProviderName": "iCal",
|
||||
"RSSProviderName": "RSS",
|
||||
"MockProviderName": "Mock",
|
||||
}
|
||||
});
|
13
samples/react-calendar-feed/src/shared/services/CalendarService/loc/mystrings.d.ts
vendored
Normal file
13
samples/react-calendar-feed/src/shared/services/CalendarService/loc/mystrings.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
declare interface ICalendarServicesStrings {
|
||||
SharePointProviderName: string;
|
||||
WordPressProviderName: string;
|
||||
ExchangeProviderName: string;
|
||||
iCalProviderName: string;
|
||||
RSSProviderName: string;
|
||||
MockProviderName: string;
|
||||
}
|
||||
|
||||
declare module 'CalendarServicesStrings' {
|
||||
const strings: ICalendarServicesStrings;
|
||||
export = strings;
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "0459b501-da31-43c7-9a9a-d5c59cc2d667",
|
||||
"id": "acef6c3f-852a-42e3-8a4b-7ea191ae9687",
|
||||
"alias": "CalendarFeedSummaryWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
|
@ -1,16 +1,35 @@
|
||||
// tslint:disable-next-line:max-line-length
|
||||
import { BaseClientSideWebPart, IPropertyPaneConfiguration, IPropertyPaneDropdownOption, PropertyPaneDropdown } from "@microsoft/sp-webpart-base";
|
||||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
|
||||
// SharePoint imports
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
IPropertyPaneDropdownOption,
|
||||
PropertyPaneDropdown
|
||||
} from "@microsoft/sp-webpart-base";
|
||||
|
||||
// Needed for data versions
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
|
||||
// PnP Property controls
|
||||
import { CalloutTriggers } from "@pnp/spfx-property-controls/lib/PropertyFieldHeader";
|
||||
import { PropertyFieldNumber } from "@pnp/spfx-property-controls/lib/PropertyFieldNumber";
|
||||
import { PropertyFieldSliderWithCallout } from "@pnp/spfx-property-controls/lib/PropertyFieldSliderWithCallout";
|
||||
import { PropertyFieldTextWithCallout } from "@pnp/spfx-property-controls/lib/PropertyFieldTextWithCallout";
|
||||
import { PropertyFieldToggleWithCallout } from "@pnp/spfx-property-controls/lib/PropertyFieldToggleWithCallout";
|
||||
|
||||
// Localization
|
||||
import * as strings from "CalendarFeedSummaryWebPartStrings";
|
||||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
|
||||
// Calendar services
|
||||
import { CalendarEventRange, DateRange, ICalendarService } from "../../shared/services/CalendarService";
|
||||
import { CalendarServiceProviderList } from "../../shared/services/CalendarService/CalendarServiceProviderList";
|
||||
import { CalendarServiceProviderList, CalendarServiceProviderType } from "../../shared/services/CalendarService/CalendarServiceProviderList";
|
||||
|
||||
// Web part properties
|
||||
import { ICalendarFeedSummaryWebPartProps } from "./CalendarFeedSummaryWebPart.types";
|
||||
|
||||
// Calendar Feed Summary component
|
||||
import CalendarFeedSummary from "./components/CalendarFeedSummary";
|
||||
import { ICalendarFeedSummaryProps } from "./components/CalendarFeedSummary.types";
|
||||
|
||||
@ -33,6 +52,29 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
|
||||
this._providerList = CalendarServiceProviderList.getProviders();
|
||||
}
|
||||
|
||||
protected onInit(): Promise<void> {
|
||||
return new Promise<void>((resolve, _reject) => {
|
||||
|
||||
let {
|
||||
cacheDuration,
|
||||
dateRange,
|
||||
} = this.properties;
|
||||
|
||||
// make sure to set a default date range if it isn't defined
|
||||
// somehow this is an issue when binding to properties that are enums
|
||||
if (dateRange === undefined) {
|
||||
dateRange = DateRange.Year;
|
||||
}
|
||||
|
||||
if (cacheDuration === undefined) {
|
||||
// default to 15 minutes
|
||||
cacheDuration = 15;
|
||||
}
|
||||
|
||||
resolve(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the web part
|
||||
*/
|
||||
@ -73,46 +115,50 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
|
||||
* Show the configuration pane
|
||||
*/
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
|
||||
// create a drop down of feed providers from our list
|
||||
const feedTypeOptions: IPropertyPaneDropdownOption[] = this._providerList.map(provider => {
|
||||
return { key: provider.key, text: provider.label };
|
||||
});
|
||||
|
||||
// make sure to set a default date range if it isn't defined
|
||||
// somehow this is an issue when binding to properties that are enums
|
||||
if (this.properties.dateRange === undefined) {
|
||||
this.properties.dateRange = DateRange.Year;
|
||||
}
|
||||
|
||||
if (this.properties.cacheDuration === undefined) {
|
||||
// default to 15 minutes
|
||||
this.properties.cacheDuration = 15;
|
||||
}
|
||||
const {
|
||||
feedUrl,
|
||||
maxEvents,
|
||||
useCORS,
|
||||
cacheDuration,
|
||||
feedType
|
||||
} = this.properties;
|
||||
|
||||
const isMock: boolean = feedType === CalendarServiceProviderType.Mock;
|
||||
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
displayGroupsAsAccordion: true,
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.FeedSettingsGroupName,
|
||||
groupFields: [
|
||||
// feed type drop down. Add your own types in the drop-down list
|
||||
PropertyPaneDropdown("feedType", {
|
||||
label: strings.FeedTypeFieldLabel,
|
||||
options: feedTypeOptions
|
||||
}),
|
||||
// feed url input box
|
||||
PropertyFieldTextWithCallout("feedUrl", {
|
||||
// feed url input box -- only if not using a mock provider
|
||||
!isMock && PropertyFieldTextWithCallout("feedUrl", {
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: "feedUrlFieldId",
|
||||
label: strings.FeedUrlFieldLabel,
|
||||
calloutContent:
|
||||
React.createElement("p", {}, strings.FeedUrlCallout),
|
||||
React.createElement("div", {}, strings.FeedUrlCallout),
|
||||
calloutWidth: 200,
|
||||
value: this.properties.feedUrl,
|
||||
deferredValidationTime: 500,
|
||||
value: feedUrl,
|
||||
placeholder: "https://",
|
||||
deferredValidationTime: 200,
|
||||
onGetErrorMessage: this._validateFeedUrl.bind(this)
|
||||
}),
|
||||
// how days ahead from today are we getting
|
||||
@ -131,30 +177,32 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
|
||||
// advanced group
|
||||
{
|
||||
groupName: strings.AdvancedGroupName,
|
||||
isCollapsed: true,
|
||||
groupFields: [
|
||||
// how many items are we diplaying in a page
|
||||
PropertyFieldNumber("maxEvents", {
|
||||
key: "maxEventsFieldId",
|
||||
label: strings.MaxEventsFieldLabel,
|
||||
description: strings.MaxEventsFieldDescription,
|
||||
value: this.properties.maxEvents,
|
||||
value: maxEvents,
|
||||
minValue: 0,
|
||||
disabled: false
|
||||
}),
|
||||
// use CORS toggle
|
||||
PropertyFieldToggleWithCallout("useCORS", {
|
||||
disabled: isMock,
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
key: "useCORSFieldId",
|
||||
label: strings.UseCORSFieldLabel,
|
||||
calloutWidth: 200,
|
||||
calloutContent: React.createElement("p", {}, strings.UseCORSFieldCallout),
|
||||
calloutContent: React.createElement("div", {}, isMock ? strings.UseCORSFieldCalloutDisabled : strings.UseCORSFieldCallout),
|
||||
onText: strings.CORSOn,
|
||||
offText: strings.CORSOff,
|
||||
checked: this.properties.useCORS
|
||||
checked: useCORS
|
||||
}),
|
||||
// cache duration slider
|
||||
PropertyFieldSliderWithCallout("cacheDuration", {
|
||||
calloutContent: React.createElement("p", {}, strings.CacheDurationFieldCallout),
|
||||
calloutContent: React.createElement("div", {}, strings.CacheDurationFieldCallout),
|
||||
calloutTrigger: CalloutTriggers.Hover,
|
||||
calloutWidth: 200,
|
||||
key: "cacheDurationFieldId",
|
||||
@ -163,7 +211,7 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
|
||||
min: 0,
|
||||
step: 15,
|
||||
showValue: true,
|
||||
value: this.properties.cacheDuration
|
||||
value: cacheDuration
|
||||
})
|
||||
],
|
||||
}
|
||||
@ -181,20 +229,34 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data version
|
||||
*/
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('2.0');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the web part is configured and ready to show events. If it returns false, we'll show the configuration placeholder.
|
||||
*/
|
||||
private _isConfigured(): boolean {
|
||||
const { feedUrl, feedType } = this.properties;
|
||||
|
||||
// see if web part has a feed type configured
|
||||
const hasFeedType: boolean = feedType !== null
|
||||
&& feedType !== undefined;
|
||||
|
||||
// Mock feeds don't need anything else
|
||||
if (feedType === CalendarServiceProviderType.Mock) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// see if web part has a feed url configured
|
||||
const hasFeedUrl: boolean = feedUrl !== null
|
||||
&& feedUrl !== undefined
|
||||
&& feedUrl !== "";
|
||||
|
||||
// see if web part has a feed type configured
|
||||
const hasFeedType: boolean = feedType !== null
|
||||
&& feedType !== undefined;
|
||||
|
||||
// if we have a feed url and a feed type, we are configured
|
||||
return hasFeedUrl && hasFeedType;
|
||||
@ -205,6 +267,12 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
|
||||
* @param feedUrl The URL to validate
|
||||
*/
|
||||
private _validateFeedUrl(feedUrl: string): string {
|
||||
if (this.properties.feedType === CalendarServiceProviderType.Mock) {
|
||||
// we don't need a URL for mock feeds
|
||||
return '';
|
||||
}
|
||||
|
||||
// Make sure the feed isn't empty or null
|
||||
if (feedUrl === null ||
|
||||
feedUrl.trim().length === 0) {
|
||||
return strings.FeedUrlValidationNoUrl;
|
||||
@ -212,9 +280,10 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
|
||||
|
||||
if (!feedUrl.match(/(http|https):\/\/(\w+:{0,1}\w*)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/)) {
|
||||
return strings.FeedUrlValidationInvalidFormat;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
// No errors
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DateRange } from "../../shared/services/CalendarService";
|
||||
import { DateRange, CalendarServiceProviderType } from "../../shared/services/CalendarService";
|
||||
|
||||
/**
|
||||
* Web part properties stored in web part configuration
|
||||
@ -6,7 +6,7 @@ import { DateRange } from "../../shared/services/CalendarService";
|
||||
export interface ICalendarFeedSummaryWebPartProps {
|
||||
title: string; // title of the web part
|
||||
feedUrl: string; // the URL where to get the feed from
|
||||
feedType: string; // the type of feed provider
|
||||
feedType: CalendarServiceProviderType; // the type of feed provider
|
||||
maxEvents: number; // maximum number of events
|
||||
dateRange: DateRange; // date range to retrieve events
|
||||
useCORS: boolean; // use CORS proxy when retrieving events
|
||||
|
@ -286,7 +286,6 @@ export default class CalendarFeedSummary extends React.Component<ICalendarFeedSu
|
||||
events } = this.state;
|
||||
const isEditMode: boolean = this.props.displayMode === DisplayMode.Edit;
|
||||
|
||||
console.log("EVENTS", events);
|
||||
return (<div>
|
||||
<div>
|
||||
<div role="application">
|
||||
|
@ -22,6 +22,7 @@ define([], function() {
|
||||
"DateRangeOptionQuarter": "Next quarter",
|
||||
"UseCORSFieldLabel": "Use proxy",
|
||||
"UseCORSFieldCallout": "Enable this option if you get a CORS message",
|
||||
"UseCORSFieldCalloutDisabled": "This option is disabled when using the Mock provider",
|
||||
"CORSOn": "On",
|
||||
"CORSOff": "Off",
|
||||
"AdvancedGroupName": "Advanced",
|
||||
@ -51,6 +52,7 @@ define([], function() {
|
||||
"AddToCalendarAriaLabel": "Press enter to download the calendar file to your device.",
|
||||
"AddToCalendarButtonLabel": "Add to my calendar",
|
||||
"AllDayDateFormat": "dddd, MMMM Do YYYY",
|
||||
"LocalizedTimeFormat": "llll"
|
||||
"LocalizedTimeFormat": "llll",
|
||||
"FeedSettingsGroupName": "Calendar feed"
|
||||
}
|
||||
});
|
||||
|
@ -22,6 +22,7 @@ declare interface ICalendarFeedSummaryWebPartStrings {
|
||||
DateRangeOptionQuarter: string;
|
||||
UseCORSFieldLabel: string;
|
||||
UseCORSFieldCallout: string;
|
||||
UseCORSFieldCalloutDisabled: string;
|
||||
CORSOn: string;
|
||||
CORSOff: string;
|
||||
AdvancedGroupName: string;
|
||||
@ -52,6 +53,7 @@ declare interface ICalendarFeedSummaryWebPartStrings {
|
||||
AddToCalendarButtonLabel: string;
|
||||
AllDayDateFormat: string;
|
||||
LocalizedTimeFormat: string;
|
||||
FeedSettingsGroupName: string;
|
||||
}
|
||||
|
||||
declare module 'CalendarFeedSummaryWebPartStrings' {
|
||||
|
@ -1,47 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.2/MicrosoftTeams.schema.json",
|
||||
"manifestVersion": "1.2",
|
||||
"packageName": "CalendarFeedSummary",
|
||||
"id": "0459b501-da31-43c7-9a9a-d5c59cc2d667",
|
||||
"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": "CalendarFeedSummary"
|
||||
},
|
||||
"description": {
|
||||
"short": "Shows a summary view of a list of calendar events retrieved from an external feed.",
|
||||
"full": "Shows a summary view of a list of calendar events retrieved from an external feed."
|
||||
},
|
||||
"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=0459b501-da31-43c7-9a9a-d5c59cc2d667",
|
||||
"canUpdateConfiguration": false,
|
||||
"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"
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 933 B |
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
Loading…
x
Reference in New Issue
Block a user