Removed xml2js references (#760)

This commit is contained in:
Hugo Bernier 2019-01-19 05:52:47 -05:00 committed by Vesa Juvonen
parent 67f63e2bc0
commit 780348ff39
22 changed files with 4991 additions and 4449 deletions

View File

@ -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/*"

View File

@ -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"
}
}
}

View File

@ -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" />

View File

@ -14,7 +14,8 @@
"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"
}
}
}

View File

@ -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"

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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

View File

@ -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"
}

View File

@ -6,97 +6,37 @@
* 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() {
super();
this.Name = "RSS";
}
constructor() {
super();
this.Name = "RSS";
}
public getEvents = (): Promise<ICalendarEvent[]> => {
const parameterizedFeedUrl: string = this.replaceTokens(this.FeedUrl, this.EventRange);
public getEvents = (): Promise<ICalendarEvent[]> => {
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");
return {
title: title,
start: pubDate,
end: pubDate,
url: link,
allDay: true,
description: description,
location: undefined, // no equivalent in RSS
category: undefined // no equivalent in RSS
};
});
return this.filterEventRange(events);
}).catch((error: any) => {
console.log("Exception caught by catch in RSS provider", error);
throw error;
});
}
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 "";
}
let events: ICalendarEvent[] = feed.items.map(item => {
let pubDate: Date = new Date(item.isoDate);
return {
title: item.title,
start: pubDate,
end: pubDate,
url: item.link,
allDay: false,
description: item.content,
location: undefined, // no equivalent in RSS
category: item.categories && item.categories.length > 0 && item.categories[0]
};
});
return events;
});
}
}

View File

@ -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

View File

@ -0,0 +1,10 @@
define([], function() {
return {
"SharePointProviderName": "SharePoint Calendar",
"WordPressProviderName": "WordPress",
"ExchangeProviderName": "Exchange Public Calendar",
"iCalProviderName": "iCal",
"RSSProviderName": "RSS",
"MockProviderName": "Mock",
}
});

View 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;
}

View File

@ -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,

View File

@ -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", {
// 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 '';
}
/**

View File

@ -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

View File

@ -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">

View File

@ -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"
}
});

View File

@ -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' {

View File

@ -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