Upgraded to SPFx1.7 (#678)

This commit is contained in:
Hugo Bernier 2018-11-12 03:47:44 -05:00 committed by Vesa Juvonen
parent f1359c18cc
commit 2782b8ca98
50 changed files with 8076 additions and 24966 deletions

View File

@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto

View File

@ -1,11 +1,12 @@
{ {
"@microsoft/generator-sharepoint": { "@microsoft/generator-sharepoint": {
"version": "1.5.0", "isCreatingSolution": true,
"environment": "spo",
"version": "1.7.0",
"libraryName": "react-calendar-feed", "libraryName": "react-calendar-feed",
"libraryId": "dd42aa00-b07d-48a2-8896-cc2f8c0d3fae", "libraryId": "dd42aa00-b07d-48a2-8896-cc2f8c0d3fae",
"environment": "spo",
"isCreatingSolution": true,
"packageManager": "npm", "packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart" "componentType": "webpart"
} }
} }

View File

@ -15,7 +15,7 @@ For more information about how this solution was built, including some design de
## Used SharePoint Framework Version ## Used SharePoint Framework Version
![SPFx v1.5](https://img.shields.io/badge/SPFx-1.5-green.svg) ![SPFx v1.7](https://img.shields.io/badge/SPFx-1.7-green.svg)
## Applies to ## Applies to
@ -47,6 +47,7 @@ Version|Date|Comments
-------|----|-------- -------|----|--------
1.0|May 15, 2018|Initial release 1.0|May 15, 2018|Initial release
2.0|June 25, 2018|Converted to SPFx 1.5 and added Exchange Public Calendar support 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
## Disclaimer ## Disclaimer
@ -63,7 +64,7 @@ Version|Date|Comments
- Insert the web part on a page - Insert the web part on a page
- When prompted to configure the web part, select **Configure** to launch the web part property pane. - 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) - 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. - 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 date range (one week, two weeks, one month, one quarter, one year)
- Specify a maximum number of events to retrieve - 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 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.
@ -81,5 +82,6 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
- Creating shared components and services - Creating shared components and services
- Creating extensible services - Creating extensible services
- Using a proxy to resolve CORS issues - 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" /> <img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-calendar-feed" />

View File

@ -17,4 +17,4 @@
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/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" "PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
} }
} }

View File

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

View File

@ -3,8 +3,10 @@
"solution": { "solution": {
"name": "react-calendar-feed-client-side-solution", "name": "react-calendar-feed-client-side-solution",
"id": "dd42aa00-b07d-48a2-8896-cc2f8c0d3fae", "id": "dd42aa00-b07d-48a2-8896-cc2f8c0d3fae",
"version": "1.0.1.2", "version": "3.0.0.0",
"includeClientSideAssets": true "includeClientSideAssets": true,
"isDomainIsolated": false,
"iconPath": "images/appicon.png"
}, },
"paths": { "paths": {
"zippedPackage": "solution/react-calendar-feed.sppkg" "zippedPackage": "solution/react-calendar-feed.sppkg"

View File

@ -1,45 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/tslint.schema.json",
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-case": true,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"valid-typeof": true,
"variable-name": false,
"whitespace": false
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "react-calendar-feed", "name": "react-calendar-feed",
"version": "1.1.0", "version": "0.0.1",
"private": true, "private": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -11,34 +11,35 @@
"test": "gulp test" "test": "gulp test"
}, },
"dependencies": { "dependencies": {
"@microsoft/sp-core-library": "~1.4.1", "@microsoft/sp-core-library": "1.7.0",
"@microsoft/sp-lodash-subset": "~1.4.1", "@microsoft/sp-lodash-subset": "1.7.0",
"@microsoft/sp-office-ui-fabric-core": "~1.4.1", "@microsoft/sp-office-ui-fabric-core": "1.7.0",
"@microsoft/sp-webpart-base": "~1.4.1", "@microsoft/sp-webpart-base": "1.7.0",
"@pnp/spfx-controls-react": "^1.3.0", "@pnp/pnpjs": "^1.2.5",
"@pnp/spfx-property-controls": "^1.6.0", "@pnp/spfx-controls-react": "^1.10.0",
"@pnp/spfx-property-controls": "^1.11.0",
"@types/es6-promise": "0.0.33", "@types/es6-promise": "0.0.33",
"@types/react": "15.6.6", "@types/react": "16.4.2",
"@types/react-dom": "15.5.6", "@types/react-dom": "16.0.5",
"@types/webpack-env": ">=1.12.1 <1.14.0", "@types/webpack-env": "1.13.1",
"ical.js": "^1.2.2", "ical.js": "^1.3.0",
"ics-js": "^0.10.2", "ics-js": "^0.10.2",
"jquery": "^3.3.1", "moment": "^2.22.2",
"moment": "^2.22.1", "react": "16.3.2",
"react": "15.6.2", "react-dom": "16.3.2",
"react-dom": "15.6.2", "react-slick": "^0.23.2",
"react-slick": "^0.23.1",
"slick-carousel": "^1.8.1", "slick-carousel": "^1.8.1",
"xml-js": "^1.6.2", "xml-js": "^1.6.8",
"xml2js": "^0.4.19" "xml2js": "^0.4.19"
}, },
"devDependencies": { "devDependencies": {
"@microsoft/sp-build-web": "~1.4.1", "@microsoft/sp-build-web": "1.7.0",
"@microsoft/sp-module-interfaces": "~1.4.1", "@microsoft/sp-tslint-rules": "1.7.0",
"@microsoft/sp-webpart-workbench": "~1.4.1", "@microsoft/sp-module-interfaces": "1.7.0",
"@types/chai": ">=3.4.34 <3.6.0", "@microsoft/sp-webpart-workbench": "1.7.0",
"@types/mocha": ">=2.2.33 <2.6.0", "gulp": "~3.9.1",
"ajv": "~5.2.2", "@types/chai": "3.4.34",
"gulp": "~3.9.1" "@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

@ -1,82 +1,88 @@
import { css } from '@uifabric/utilities/lib/css'; import { css } from "@uifabric/utilities/lib/css";
import { IconButton } from 'office-ui-fabric-react/lib/Button'; import { IconButton } from "office-ui-fabric-react/lib/Button";
import * as React from 'react'; import * as React from "react";
import Slider from 'react-slick'; import Slider from "react-slick";
import { ICarouselContainerProps, ICarouselContainerState } from '.'; import { ICarouselContainerProps, ICarouselContainerState } from ".";
import styles from './CarouselContainer.module.scss'; import styles from "./CarouselContainer.module.scss";
/** /**
* Carousel container * Carousel container
* Presents the child compoments as a slick slide * Presents the child compoments as a slick slide
*/ */
export class CarouselContainer extends React.Component<ICarouselContainerProps, ICarouselContainerState> { export class CarouselContainer extends React.Component<
// the slick slider used in normal views ICarouselContainerProps,
private _slider: Slider; ICarouselContainerState
> {
// the slick slider used in normal views
private _slider: Slider;
/** /**
* Renders a slick switch, a slide for each child, and next/previous arrows * Renders a slick switch, a slide for each child, and next/previous arrows
*/ */
public render(): React.ReactElement<ICarouselContainerProps> { public render(): React.ReactElement<ICarouselContainerProps> {
var settings: any = { // slick seems to have an issue with having "infinite" mode set to true and having less items than the number of slides per page
accessibility: true, // set infinite to true only if there are more than 3 children
arrows: false, var isInfinite: boolean = React.Children.count(this.props.children) > 3;
autoplaySpeed: 5000, var settings: any = {
dots: true, accessibility: true,
infinite: true, arrows: false,
slidesToShow: 4, autoplaySpeed: 5000,
slidesToScroll: 4, dots: true,
speed: 500, infinite: isInfinite,
centerPadding: "50px", slidesToShow: 4,
pauseOnHover: true, slidesToScroll: 4,
variableWidth: false, speed: 500,
useCSS: true, centerPadding: "50px",
rows: 1, pauseOnHover: true,
respondTo: 'slider', variableWidth: false,
responsive: [ useCSS: true,
rows: 1,
{ respondTo: "slider",
breakpoint: 2560, responsive: [
settings: { {
slidesToShow: 3, breakpoint: 2560,
slidesToScroll: 3, settings: {
} slidesToShow: 3,
}, slidesToScroll: 3
{ }
breakpoint: 801, },
settings: { {
slidesToShow: 2, breakpoint: 801,
slidesToScroll: 2, settings: {
} slidesToShow: 2,
}, slidesToScroll: 2
// there is no 1 slide option, as it converts to narrow view }
] }
}; // there is no 1 slide option, as it converts to narrow view
]
};
return ( return (
<div className={css(styles.carouselContainer, styles.filmStrip)}> <div className={css(styles.carouselContainer, styles.filmStrip)}>
<Slider ref={c => (this._slider = c)} <Slider ref={c => (this._slider = c)} {...settings}>
{...settings}> {this.props.children}
{this.props.children} </Slider>
</Slider> <div
<div className={css(styles.indexButtonContainer, styles.sliderButtons)}
className={css(styles.indexButtonContainer, styles.sliderButtons)} style={{ left: "10px" }}
style={{ "left": "10px" }} onClick={() => this._slider.slickPrev()}
onClick={() => this._slider.slickPrev()} >
> <IconButton
<IconButton className={css(styles.indexButton, styles.leftPositioned)} className={css(styles.indexButton, styles.leftPositioned)}
iconProps={{ iconName: "ChevronLeft" }} iconProps={{ iconName: "ChevronLeft" }}
/> />
</div> </div>
<div <div
className={css(styles.indexButtonContainer, styles.sliderButtons)} className={css(styles.indexButtonContainer, styles.sliderButtons)}
style={{ "right": "10px" }} style={{ right: "10px" }}
onClick={() => this._slider.slickNext()} onClick={() => this._slider.slickNext()}
> >
<IconButton className={css(styles.indexButton, styles.rightPositioned)} <IconButton
iconProps={{ iconName: "ChevronRight" }} className={css(styles.indexButton, styles.rightPositioned)}
/> iconProps={{ iconName: "ChevronRight" }}
</div> />
</div> </div>
); </div>
} );
}
} }

View File

@ -1,2 +1,2 @@
export * from './CarouselContainer'; export * from "./CarouselContainer";
export * from './CarouselContainer.types'; export * from "./CarouselContainer.types";

View File

@ -1,8 +1,8 @@
import * as moment from 'moment'; import * as moment from "moment";
import { css } from 'office-ui-fabric-react/lib/Utilities'; import { css } from "office-ui-fabric-react/lib/Utilities";
import * as React from 'react'; import * as React from "react";
import styles from './DateBox.module.scss'; import styles from "./DateBox.module.scss";
import { DateBoxSize, IDateBoxProps, IDateBoxState } from './DateBox.types'; import { DateBoxSize, IDateBoxProps, IDateBoxState } from "./DateBox.types";
/** /**
* Shows a date in a SharePoint-looking date * Shows a date in a SharePoint-looking date
@ -50,4 +50,4 @@ export class DateBox extends React.Component<IDateBoxProps, IDateBoxState> {
<div className={styles.date} data-automation-id="multipleDayEndDateContainer">{endMoment.format("MMM D").toUpperCase()}</div> <div className={styles.date} data-automation-id="multipleDayEndDateContainer">{endMoment.format("MMM D").toUpperCase()}</div>
</div>); </div>);
} }
} }

View File

@ -1,2 +1,2 @@
export * from './DateBox'; export * from "./DateBox";
export * from './DateBox.types'; export * from "./DateBox.types";

View File

@ -1,15 +1,13 @@
import { Guid } from '@microsoft/sp-core-library'; import { Guid } from "@microsoft/sp-core-library";
import * as strings from 'CalendarFeedSummaryWebPartStrings'; import * as strings from "CalendarFeedSummaryWebPartStrings";
import * as ICS from 'ics-js'; import * as ICS from "ics-js";
import * as moment from 'moment'; import * as moment from "moment";
import { ActionButton, DocumentCard, DocumentCardType, FocusZone, css } from 'office-ui-fabric-react'; import { ActionButton, DocumentCard, DocumentCardType, FocusZone, css } from "office-ui-fabric-react";
import * as React from 'react'; import * as React from "react";
import { IEventCardProps, IEventCardState } from '.'; import { IEventCardProps, IEventCardState } from ".";
import { DateBox, DateBoxSize } from '../DateBox'; import { DateBox, DateBoxSize } from "../DateBox";
import styles from './EventCard.module.scss'; import styles from "./EventCard.module.scss";
const AllDayFormat: string = 'dddd, MMMM Do YYYY'; import { Text } from "@microsoft/sp-core-library";
const LocalizedTimeFormat: string = 'llll';
import { Text } from '@microsoft/sp-core-library';
/** /**
* Shows an event in a document card * Shows an event in a document card
*/ */
@ -31,10 +29,10 @@ export class EventCard extends React.Component<IEventCardProps, IEventCardState>
title, title,
url, url,
category, category,
description, // description,
location } = this.props.event; location } = this.props.event;
const eventDate: moment.Moment = moment(start); const eventDate: moment.Moment = moment(start);
const dateString: string = allDay ? eventDate.format(AllDayFormat) : eventDate.format(LocalizedTimeFormat); const dateString: string = allDay ? eventDate.format(strings.AllDayDateFormat) : eventDate.format(strings.LocalizedTimeFormat);
const { isEditMode } = this.props; const { isEditMode } = this.props;
return ( return (
<div> <div>
@ -87,10 +85,11 @@ export class EventCard extends React.Component<IEventCardProps, IEventCardState>
allDay, allDay,
title, title,
url, url,
category, // category,
location } = this.props.event; // location
} = this.props.event;
const eventDate: moment.Moment = moment.utc(start); const eventDate: moment.Moment = moment.utc(start);
const dateString: string = allDay ? eventDate.format(AllDayFormat) : eventDate.format(LocalizedTimeFormat); const dateString: string = allDay ? eventDate.format(strings.AllDayDateFormat) : eventDate.format(strings.LocalizedTimeFormat);
return ( return (
<div> <div>
<div <div

View File

@ -1,4 +1,4 @@
import { ICalendarEvent } from '../../../../lib/shared/services/CalendarService'; import { ICalendarEvent } from "../../../shared/services/CalendarService";
export interface IEventCardProps { export interface IEventCardProps {
isEditMode: boolean; isEditMode: boolean;
@ -6,4 +6,4 @@ export interface IEventCardProps {
isNarrow: boolean; isNarrow: boolean;
} }
export interface IEventCardState {} export interface IEventCardState {}

View File

@ -1,2 +1,2 @@
export * from './EventCard'; export * from "./EventCard";
export * from './EventCard.types'; export * from "./EventCard.types";

View File

@ -1,10 +1,10 @@
import { ActionButton, IButtonProps } from 'office-ui-fabric-react/lib/Button'; import { ActionButton, IButtonProps } from "office-ui-fabric-react/lib/Button";
import { Icon } from 'office-ui-fabric-react/lib/Icon'; import { Icon } from "office-ui-fabric-react/lib/Icon";
import { css } from 'office-ui-fabric-react/lib/Utilities'; import { css } from "office-ui-fabric-react/lib/Utilities";
import * as React from 'react'; import * as React from "react";
import { IPagingProps, IPagingState } from '.'; import { IPagingProps, IPagingState } from ".";
import styles from './Paging.module.scss'; import styles from "./Paging.module.scss";
import * as strings from 'CalendarFeedSummaryWebPartStrings'; import * as strings from "CalendarFeedSummaryWebPartStrings";
/** /**
* A custom pagination control designed to look & feel like Office UI Fabric * A custom pagination control designed to look & feel like Office UI Fabric
@ -12,7 +12,7 @@ import * as strings from 'CalendarFeedSummaryWebPartStrings';
export class Paging extends React.Component<IPagingProps, IPagingState> { export class Paging extends React.Component<IPagingProps, IPagingState> {
public render(): React.ReactElement<IPagingProps> { public render(): React.ReactElement<IPagingProps> {
const { totalItems, itemsCountPerPage, currentPage } = this.props; const { currentPage } = this.props;
// calculate the page situation // calculate the page situation
const numberOfPages: number = this._getNumberOfPages(); const numberOfPages: number = this._getNumberOfPages();

View File

@ -1,2 +1,2 @@
export * from './Paging'; export * from "./Paging";
export * from './Paging.types'; export * from "./Paging.types";

View File

@ -1,9 +1,9 @@
import { HttpClient, HttpClientResponse } from '@microsoft/sp-http'; import { HttpClient, HttpClientResponse } from "@microsoft/sp-http";
import { IWebPartContext } from '@microsoft/sp-webpart-base'; import { IWebPartContext } from "@microsoft/sp-webpart-base";
import * as moment from 'moment'; import * as moment from "moment";
import { CalendarEventRange } from '.'; import { CalendarEventRange } from ".";
import { ICalendarEvent } from './ICalendarEvent'; import { ICalendarEvent } from "./ICalendarEvent";
import { ICalendarService } from './ICalendarService'; import { ICalendarService } from "./ICalendarService";
/** /**
* Base Calendar Service * Base Calendar Service
@ -60,7 +60,6 @@ export abstract class BaseCalendarService implements ICalendarService {
*/ */
protected replaceTokens(feedUrl: string, dateRange: CalendarEventRange): string { protected replaceTokens(feedUrl: string, dateRange: CalendarEventRange): string {
const startMoment: moment.Moment = moment(dateRange.Start); const startMoment: moment.Moment = moment(dateRange.Start);
const endMoment: moment.Moment = moment(dateRange.End);
const startDate: string = startMoment.format("YYYY-MM-DD"); const startDate: string = startMoment.format("YYYY-MM-DD");
const endDate: string = startMoment.format("YYYY-MM-DD"); const endDate: string = startMoment.format("YYYY-MM-DD");

View File

@ -1,4 +1,4 @@
import * as moment from 'moment'; import * as moment from "moment";
export enum DateRange { export enum DateRange {
OneWeek, OneWeek,

View File

@ -1,53 +1,61 @@
import { MockCalendarService } from './MockCalendarService'; import { MockCalendarService } from "./MockCalendarService";
import { RSSCalendarService } from './RSSCalendarService'; import { RSSCalendarService } from "./RSSCalendarService";
import { WordPressFullCalendarService } from './WordPressFullCalendarService'; import { WordPressFullCalendarService } from "./WordPressFullCalendarService";
import { iCalCalendarService } from './iCalCalendarService'; import { iCalCalendarService } from "./iCalCalendarService";
import { ExchangePublicCalendarService } from './ExchangePublicCalendarService'; import { ExchangePublicCalendarService } from "./ExchangePublicCalendarService";
import { SharePointCalendarService } from "./SharePointCalendarService";
export class CalendarServiceProviderList { export class CalendarServiceProviderList {
public static getProviders(): any[] { public static getProviders(): any[] {
const providers: any[] = []; const providers: any[] = [];
// only include the Mock service provider in DEBUG // only include the Mock service provider in DEBUG
if (DEBUG) { if (DEBUG) {
providers.push({ providers.push({
label: "Mock", label: "Mock",
key: "mock", key: "mock",
initialize: () => new MockCalendarService() initialize: () => new MockCalendarService()
}); });
}
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;
} }
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 { export enum CalendarServiceProviderType {
WordPress = "WordPress", SharePoint = "SharePoint",
iCal = "iCal", WordPress = "WordPress",
RSS = "RSS", iCal = "iCal",
Mock = "Mock" RSS = "RSS",
} Mock = "Mock"
}

View File

@ -1,9 +1,9 @@
/** /**
* Exchange Public Calendar Service * Exchange Public Calendar Service
*/ */
import { ICalendarService } from '..'; import { ICalendarService } from "..";
import { ICalendarEvent } from '../ICalendarEvent'; import { ICalendarEvent } from "../ICalendarEvent";
import { iCalCalendarService } from '../iCalCalendarService'; import { iCalCalendarService } from "../iCalCalendarService";
// tslint:disable-next-line:class-name // tslint:disable-next-line:class-name
export class ExchangePublicCalendarService extends iCalCalendarService implements ICalendarService { export class ExchangePublicCalendarService extends iCalCalendarService implements ICalendarService {
@ -13,8 +13,8 @@ export class ExchangePublicCalendarService extends iCalCalendarService implement
} }
public getEvents = (): Promise<ICalendarEvent[]> => { public getEvents = (): Promise<ICalendarEvent[]> => {
// Exchange public calendar shares are really ICS calendars. // exchange public calendar shares are really ICS calendars.
// we allow users to pass either the .html URL or // we allow users to pass either the .html URL or
// the .ics, but we really need the .ics file // the .ics, but we really need the .ics file
const htmlExtension:string = ".html"; const htmlExtension:string = ".html";
@ -25,15 +25,5 @@ export class ExchangePublicCalendarService extends iCalCalendarService implement
} }
return this.getEvents(); return this.getEvents();
} }
}
private changeExtensionToIcs(url: string): string {
const htmlExtension:string = ".html";
if (url.indexOf(htmlExtension, url.length - htmlExtension.length) >= 0) {
// the url ends with .html. Replace it with .ics
const root: string = url.substring(0, url.length - htmlExtension.length);
return `${root}.ics`;
}
return url;
}
}

View File

@ -1 +1 @@
export * from './ExchangePublicCalendarService'; export * from "./ExchangePublicCalendarService";

View File

@ -1,5 +1,5 @@
import { IWebPartContext } from '@microsoft/sp-webpart-base'; import { IWebPartContext } from "@microsoft/sp-webpart-base";
import { CalendarEventRange, ICalendarEvent } from '.'; import { CalendarEventRange, ICalendarEvent } from ".";
export interface ICalendarService { export interface ICalendarService {
Context: IWebPartContext; Context: IWebPartContext;
@ -9,4 +9,4 @@ export interface ICalendarService {
CacheDuration: number; CacheDuration: number;
Name: string; Name: string;
getEvents: () => Promise<ICalendarEvent[]>; getEvents: () => Promise<ICalendarEvent[]>;
} }

View File

@ -3,12 +3,11 @@
* This provider will NOT be listed in the list of available providers when this solution is packaged with --ship. * This provider will NOT be listed in the list of available providers when this solution is packaged with --ship.
* Don't freak out, it didn't just disappear. * Don't freak out, it didn't just disappear.
*/ */
import * as moment from 'moment'; import * as moment from "moment";
import { BaseCalendarService } from '../BaseCalendarService'; import { BaseCalendarService } from "../BaseCalendarService";
import { ICalendarEvent } from '../ICalendarEvent'; import { ICalendarEvent } from "../ICalendarEvent";
import { ICalendarService } from '../ICalendarService'; import { ICalendarService } from "../ICalendarService";
const today: Date = new Date();
const sampleEvents: ICalendarEvent[] = [ const sampleEvents: ICalendarEvent[] = [
{ {
"title": "This event will be tomorrow", "title": "This event will be tomorrow",
@ -155,4 +154,4 @@ export class MockCalendarService extends BaseCalendarService implements ICalenda
}, 1000); }, 1000);
}); });
} }
} }

View File

@ -1 +1 @@
export * from './MockCalendarService'; export * from "./MockCalendarService";

View File

@ -6,12 +6,12 @@
* every one I could find on NPM and GitHub and found that they did not meet my needs. * 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. * I'm open to suggestions, though, if you have a library that you think would work better.
*/ */
import { HttpClientResponse } from '@microsoft/sp-http'; import { HttpClientResponse } from "@microsoft/sp-http";
import * as convert from 'xml-js'; import * as convert from "xml-js";
import { ICalendarService } from '..'; import { ICalendarService } from "..";
import { BaseCalendarService } from '../BaseCalendarService'; import { BaseCalendarService } from "../BaseCalendarService";
import { ICalendarEvent } from '../ICalendarEvent'; import { ICalendarEvent } from "../ICalendarEvent";
import { escape, unescape } from '@microsoft/sp-lodash-subset'; import { unescape } from "@microsoft/sp-lodash-subset";
export class RSSCalendarService extends BaseCalendarService implements ICalendarService { export class RSSCalendarService extends BaseCalendarService implements ICalendarService {
constructor() { constructor() {
@ -51,7 +51,6 @@ export class RSSCalendarService extends BaseCalendarService implements ICalendar
let title: string = this._getElementValue(item, "title"); let title: string = this._getElementValue(item, "title");
let link: string = this._getElementValue(item, "link"); let link: string = this._getElementValue(item, "link");
let pubDate: Date = new Date(this._getElementValue(item, "pubDate")); let pubDate: Date = new Date(this._getElementValue(item, "pubDate"));
let category: string = this._getElementValue(item, "category");
let description: string = this._getElementValue(item, "description"); let description: string = this._getElementValue(item, "description");
return { return {
title: title, title: title,
@ -100,4 +99,4 @@ export class RSSCalendarService extends BaseCalendarService implements ICalendar
console.log("Found an RSS field type I didn't know", firstElement.type); console.log("Found an RSS field type I didn't know", firstElement.type);
return ""; return "";
} }
} }

View File

@ -1 +1 @@
export * from './RSSCalendarService'; export * from "./RSSCalendarService";

View File

@ -0,0 +1,82 @@
/**
* ExtensionService
*/
import { HttpClientResponse } from "@microsoft/sp-http";
import { ICalendarService } from "..";
import { BaseCalendarService } from "../BaseCalendarService";
import { ICalendarEvent } from "../ICalendarEvent";
import { Web, sp } from "@pnp/sp";
import { combine } from "@pnp/common";
export class SharePointCalendarService extends BaseCalendarService
implements ICalendarService {
constructor() {
super();
this.Name = "SharePoint";
}
public getEvents = (): Promise<ICalendarEvent[]> => {
const parameterizedFeedUrl: string = this.replaceTokens(
this.FeedUrl,
this.EventRange
);
// Get the URL
let webUrl = this.FeedUrl.toLowerCase();
// Break the URL into parts
let urlParts = webUrl.split("/");
// Get the web root
let webRoot = urlParts[0] + "/" + urlParts[1] + "/" + urlParts[2];
// Get the list URL
let listUrl = webUrl.substring(webRoot.length);
// Find the "lists" portion of the URL to get the site URL
let webLocation = listUrl.substr(0, listUrl.indexOf("lists/"));
let siteUrl = webRoot + webLocation;
// Open the web associated to the site
let web = new Web(siteUrl);
// Get the web
return web.get().then(() => {
// Build a filter so that we don't retrieve every single thing unless necesssary
let dateFilter:string = "EventDate ge datetime'"+this.EventRange.Start.toISOString()+"' and EndDate lt datetime'"+this.EventRange.End.toISOString()+"'";
// When we receive the web, get the list
return web
.getList(listUrl)
.items.select("Id,Title,Description,EventDate,EndDate,fAllDayEvent,Category,Location")
.filter(dateFilter)
.getAll()
.then((items: any[]) => {
// Once we get the list, convert to calendar events
let events: ICalendarEvent[] = items.map((item: any) => {
let eventUrl:string = combine(webUrl, "DispForm.aspx?ID="+item.Id);
return {
title: item.Title,
start: item.EventDate,
end: item.EndDate,
url: eventUrl,
allDay: item.fAllDayEvent,
category: item.Category,
description: item.Description,
location: item.Location
};
});
// Return the calendar items
return events;
})
.catch((error: any) => {
console.log(
"Exception caught by catch in SharePoint provider",
error
);
throw error;
});
});
}
}

View File

@ -0,0 +1 @@
export * from "./SharePointCalendarService";

View File

@ -1,10 +1,10 @@
/** /**
* ExtensionService * ExtensionService
*/ */
import { ICalendarService } from '..'; import { ICalendarService } from "..";
import { BaseCalendarService } from '../BaseCalendarService'; import { BaseCalendarService } from "../BaseCalendarService";
import { ICalendarEvent } from '../ICalendarEvent'; import { ICalendarEvent } from "../ICalendarEvent";
import { IWordPressFullCalendarEventResponse } from './IWordPressFullCalendarEventResponse'; import { IWordPressFullCalendarEventResponse } from "./IWordPressFullCalendarEventResponse";
export class WordPressFullCalendarService extends BaseCalendarService implements ICalendarService { export class WordPressFullCalendarService extends BaseCalendarService implements ICalendarService {
constructor() { constructor() {
@ -39,4 +39,4 @@ export class WordPressFullCalendarService extends BaseCalendarService implements
}); });
} }
} }

View File

@ -1,2 +1,2 @@
export * from './IWordPressFullCalendarEventResponse'; export * from "./IWordPressFullCalendarEventResponse";
export * from './WordPressFullCalendarService'; export * from "./WordPressFullCalendarService";

View File

@ -1,11 +1,11 @@
/** /**
* ExtensionService * ExtensionService
*/ */
import { HttpClientResponse } from '@microsoft/sp-http'; import { HttpClientResponse } from "@microsoft/sp-http";
import * as ICAL from 'ical.js'; import * as ICAL from "ical.js";
import { ICalendarService } from '..'; import { ICalendarService } from "..";
import { BaseCalendarService } from '../BaseCalendarService'; import { BaseCalendarService } from "../BaseCalendarService";
import { ICalendarEvent } from '../ICalendarEvent'; import { ICalendarEvent } from "../ICalendarEvent";
// tslint:disable-next-line:class-name // tslint:disable-next-line:class-name
export class iCalCalendarService extends BaseCalendarService implements ICalendarService { export class iCalCalendarService extends BaseCalendarService implements ICalendarService {
@ -47,4 +47,4 @@ export class iCalCalendarService extends BaseCalendarService implements ICalenda
}); });
} }
} }

View File

@ -1 +1 @@
export * from './iCalCalendarService'; export * from "./iCalCalendarService";

View File

@ -1,4 +1,4 @@
export * from './ICalendarEvent'; export * from "./ICalendarEvent";
export * from './ICalendarService'; export * from "./ICalendarService";
export * from './CalendarEventRange'; export * from "./CalendarEventRange";
export * from './CalendarServiceProviderList'; export * from "./CalendarServiceProviderList";

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,20 +1,15 @@
{ {
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "b83bb343-bc5e-460c-9efd-52de06d32ffa", "id": "0459b501-da31-43c7-9a9a-d5c59cc2d667",
"alias": "CalendarFeedSummaryWebPart", "alias": "CalendarFeedSummaryWebPart",
"componentType": "WebPart", "componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*", "version": "*",
"manifestVersion": 2, "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, "requiresCustomScript": false,
"preconfiguredEntries": [
{ "preconfiguredEntries": [{
"groupId": "cf066440-0614-43d6-98ae-0b31cf14c7c3", // Text, media and content "groupId": "cf066440-0614-43d6-98ae-0b31cf14c7c3",
"group": { "group": {
"default": "Text, media and content" "default": "Text, media and content"
}, },
@ -24,16 +19,13 @@
"description": { "description": {
"default": "Shows a summary view of a list of calendar events retrieved from an external feed." "default": "Shows a summary view of a list of calendar events retrieved from an external feed."
}, },
// commented out because of bug with base64 icons
// "iconImageUrl": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSIyOCIgdmlld0JveD0iMCAwIDEwLjU4IDcuNDEiPjxwYXRoIGQ9Ik0yLjQ4LS4wMVYuNUgxLjJ2Ni4xNWg1LjcyVjYuM0gxLjU0VjIuMmg2LjR2Mi44OGguMjVWLjVINi45MVYwaC0uMzZWLjVIMi44MlYwaC0uMTN6bS0uOTQuODVoLjk0di40M2guMzRWLjg0aDMuNzN2LjQzaC4zNlYuODRoMS4wM3YuOTRoLTYuNHYtLjQ3eiIvPjxwYXRoIGQ9Ik03LjIyIDUuNjh2LS40M2EyLjE2IDIuMTYgMCAwIDEgMi4xNyAyLjE3bC0uNDUtLjAxYy4wMi0uNy0uNDktMS43LTEuNzItMS43M3ptMCAuNzJ2LS40M2MuOTIuMDUgMS40My43IDEuNDQgMS40NWgtLjQzYy0uMDEtLjY2LS40NS0xLTEuMDEtMS4wMnptLjYuN2EuMy4zIDAgMCAxLS4zLjMuMy4zIDAgMCAxLS4zMS0uMy4zLjMgMCAwIDEgLjMtLjI5LjMuMyAwIDAgMSAuMy4zeiIvPjxwYXRoIGQ9Ik0yLjQ4LS4wMVYuNUgxLjJsLjAzIDIuMjMtLjAzIDMuOTJoNS43MlY2LjNIMS41NFYyLjJoNi40djIuODhoLjI1Vi41SDYuOTFWMGgtLjM2Vi41SDIuODJWMGgtLjEzem0tLjk0Ljg1aC45NHYuNDNoLjM0Vi44NGgzLjczdi40M2guMzZWLjg0aDEuMDN2Ljk0aC02LjR2LS40N3oiLz48cGF0aCBkPSJNMi41IDIuNzNoMS42OHYuMzZIMi41em00LjA3LjM2aC4zNHYyLjI4aC0uMzR6bS0xLjM3IDBoLjM1djIuMjhINS4yem0wIDIuMjhoMS43MXYuMzZoLTEuN3ptMC0yLjY0aDEuNzF2LjM2aC0xLjd6Ii8+PHBhdGggZD0iTTMuODYgMy4xaC4zNHYyLjI3aC0uMzR6bS0xLjM2IDBoLjM0djIuMjdIMi41em0wIDIuMjdoMS43di4zNkgyLjV6bTAtMi42NGgxLjd2LjM2SDIuNXoiLz48L3N2Zz4=",
"officeFabricIconFontName": "Calendar", "officeFabricIconFontName": "Calendar",
"properties": { "properties": {
"title": "Upcoming Events", "title": "Upcoming Events",
"dateRange": 4, /* Year */ "dateRange": 4,
"maxEvents": 4, "maxEvents": 4,
"useCORS": false, "useCORS": false,
"cacheDuration": 15 /* 15 minutes */ "cacheDuration": 15
} }
} }]
] }
}

View File

@ -1,19 +1,18 @@
import { Version } from '@microsoft/sp-core-library';
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
import { BaseClientSideWebPart, IPropertyPaneConfiguration, IPropertyPaneDropdownOption, PropertyPaneDropdown } from '@microsoft/sp-webpart-base'; import { BaseClientSideWebPart, IPropertyPaneConfiguration, IPropertyPaneDropdownOption, PropertyPaneDropdown } from "@microsoft/sp-webpart-base";
import { CalloutTriggers } from '@pnp/spfx-property-controls/lib/PropertyFieldHeader'; import { CalloutTriggers } from "@pnp/spfx-property-controls/lib/PropertyFieldHeader";
import { PropertyFieldNumber } from '@pnp/spfx-property-controls/lib/PropertyFieldNumber'; import { PropertyFieldNumber } from "@pnp/spfx-property-controls/lib/PropertyFieldNumber";
import { PropertyFieldSliderWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldSliderWithCallout'; import { PropertyFieldSliderWithCallout } from "@pnp/spfx-property-controls/lib/PropertyFieldSliderWithCallout";
import { PropertyFieldTextWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldTextWithCallout'; import { PropertyFieldTextWithCallout } from "@pnp/spfx-property-controls/lib/PropertyFieldTextWithCallout";
import { PropertyFieldToggleWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldToggleWithCallout'; import { PropertyFieldToggleWithCallout } from "@pnp/spfx-property-controls/lib/PropertyFieldToggleWithCallout";
import * as strings from 'CalendarFeedSummaryWebPartStrings'; import * as strings from "CalendarFeedSummaryWebPartStrings";
import * as React from 'react'; import * as React from "react";
import * as ReactDom from 'react-dom'; import * as ReactDom from "react-dom";
import { CalendarEventRange, DateRange, ICalendarService } from '../../shared/services/CalendarService'; import { CalendarEventRange, DateRange, ICalendarService } from "../../shared/services/CalendarService";
import { CalendarServiceProviderList } from '../../shared/services/CalendarService/CalendarServiceProviderList'; import { CalendarServiceProviderList } from "../../shared/services/CalendarService/CalendarServiceProviderList";
import { ICalendarFeedSummaryWebPartProps } from './CalendarFeedSummaryWebPart.types'; import { ICalendarFeedSummaryWebPartProps } from "./CalendarFeedSummaryWebPart.types";
import CalendarFeedSummary from './components/CalendarFeedSummary'; import CalendarFeedSummary from "./components/CalendarFeedSummary";
import { ICalendarFeedSummaryProps } from './components/CalendarFeedSummary.types'; import { ICalendarFeedSummaryProps } from "./components/CalendarFeedSummary.types";
// this is the same width that the SharePoint events web parts use to render as narrow // this is the same width that the SharePoint events web parts use to render as narrow
const MaxMobileWidth: number = 480; const MaxMobileWidth: number = 480;
@ -39,7 +38,7 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
*/ */
public render(): void { public render(): void {
// see if we need to render a mobile view // see if we need to render a mobile view
const isNarrow: boolean = this.width <= MaxMobileWidth; const isNarrow: boolean = this.domElement.clientWidth <= MaxMobileWidth;
// display the summary (or the configuration screen) // display the summary (or the configuration screen)
const element: React.ReactElement<ICalendarFeedSummaryProps> = React.createElement( const element: React.ReactElement<ICalendarFeedSummaryProps> = React.createElement(
@ -61,14 +60,6 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
ReactDom.render(element, this.domElement); ReactDom.render(element, this.domElement);
} }
/**
* We store our configure in version 1.0. If we ever change how we store our configuration information,
* we'll update the version number here.
*/
protected get dataVersion(): Version {
return Version.parse("1.0");
}
/** /**
* We're disabling reactive property panes here because we don't want the web part to try to update events as * We're disabling reactive property panes here because we don't want the web part to try to update events as
* people are typing in the feed URL. * people are typing in the feed URL.
@ -231,7 +222,6 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
*/ */
private _getDataProvider(): ICalendarService { private _getDataProvider(): ICalendarService {
const { const {
feedType,
feedUrl, feedUrl,
useCORS, useCORS,
cacheDuration cacheDuration

View File

@ -1,4 +1,4 @@
import { DateRange } from '../../shared/services/CalendarService'; import { DateRange } from "../../shared/services/CalendarService";
/** /**
* Web part properties stored in web part configuration * Web part properties stored in web part configuration
@ -11,4 +11,4 @@ export interface ICalendarFeedSummaryWebPartProps {
dateRange: DateRange; // date range to retrieve events dateRange: DateRange; // date range to retrieve events
useCORS: boolean; // use CORS proxy when retrieving events useCORS: boolean; // use CORS proxy when retrieving events
cacheDuration: number; // how long to cache events for cacheDuration: number; // how long to cache events for
} }

View File

@ -1,17 +1,17 @@
import { DisplayMode } from '@microsoft/sp-core-library'; import { DisplayMode } from "@microsoft/sp-core-library";
import { SPComponentLoader } from '@microsoft/sp-loader'; import { SPComponentLoader } from "@microsoft/sp-loader";
import { Placeholder } from '@pnp/spfx-controls-react/lib/Placeholder'; import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
import { WebPartTitle } from '@pnp/spfx-controls-react/lib/WebPartTitle'; import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import * as strings from 'CalendarFeedSummaryWebPartStrings'; import * as strings from "CalendarFeedSummaryWebPartStrings";
import * as moment from 'moment'; import * as moment from "moment";
import { FocusZone, FocusZoneDirection, List, Spinner, css } from 'office-ui-fabric-react'; import { FocusZone, FocusZoneDirection, List, Spinner, css } from "office-ui-fabric-react";
import * as React from 'react'; import * as React from "react";
import { CarouselContainer } from '../../../shared/components/CarouselContainer'; import { CarouselContainer } from "../../../shared/components/CarouselContainer";
import { EventCard } from '../../../shared/components/EventCard'; import { EventCard } from "../../../shared/components/EventCard";
import { Paging } from '../../../shared/components/Paging'; import { Paging } from "../../../shared/components/Paging";
import { CalendarServiceProviderType, ICalendarEvent, ICalendarService } from '../../../shared/services/CalendarService'; import { CalendarServiceProviderType, ICalendarEvent, ICalendarService } from "../../../shared/services/CalendarService";
import styles from './CalendarFeedSummary.module.scss'; import styles from "./CalendarFeedSummary.module.scss";
import { ICalendarFeedSummaryProps, ICalendarFeedSummaryState, IFeedCache } from './CalendarFeedSummary.types'; import { ICalendarFeedSummaryProps, ICalendarFeedSummaryState, IFeedCache } from "./CalendarFeedSummary.types";
// the key used when caching events // the key used when caching events
const CacheKey: string = "calendarFeedSummary"; const CacheKey: string = "calendarFeedSummary";
@ -30,8 +30,8 @@ export default class CalendarFeedSummary extends React.Component<ICalendarFeedSu
}; };
// needed for the slick slider in normal mode // needed for the slick slider in normal mode
SPComponentLoader.loadCss("https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css"); SPComponentLoader.loadCss("https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.css");
SPComponentLoader.loadCss("https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick-theme.min.css"); SPComponentLoader.loadCss("https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick-theme.min.css");
} }
/** /**
@ -69,8 +69,10 @@ export default class CalendarFeedSummary extends React.Component<ICalendarFeedSu
} }
const settingsHaveChanged: boolean = prevProvider.CacheDuration !== currProvider.CacheDuration || const settingsHaveChanged: boolean = prevProvider.CacheDuration !== currProvider.CacheDuration ||
prevProvider.Name !== currProvider.Name ||
prevProvider.FeedUrl !== currProvider.FeedUrl || prevProvider.FeedUrl !== currProvider.FeedUrl ||
prevProvider.Name !== currProvider.Name || prevProvider.Name !== currProvider.Name ||
prevProvider.EventRange.DateRange !== currProvider.EventRange.DateRange ||
prevProvider.UseCORS !== currProvider.UseCORS; prevProvider.UseCORS !== currProvider.UseCORS;
if (settingsHaveChanged) { if (settingsHaveChanged) {
@ -88,13 +90,7 @@ export default class CalendarFeedSummary extends React.Component<ICalendarFeedSu
public render(): React.ReactElement<ICalendarFeedSummaryProps> { public render(): React.ReactElement<ICalendarFeedSummaryProps> {
const { const {
isConfigured, isConfigured,
isNarrow,
} = this.props; } = this.props;
const {
events,
isLoading,
error
} = this.state;
// if we're not configured, show the placeholder // if we're not configured, show the placeholder
if (!isConfigured) { if (!isConfigured) {
@ -230,7 +226,6 @@ export default class CalendarFeedSummary extends React.Component<ICalendarFeedSu
*/ */
private _renderNarrowList(): JSX.Element { private _renderNarrowList(): JSX.Element {
const { const {
isLoading,
events, events,
currentPage currentPage
} = this.state; } = this.state;
@ -288,10 +283,10 @@ export default class CalendarFeedSummary extends React.Component<ICalendarFeedSu
*/ */
private _renderNormalList(): JSX.Element { private _renderNormalList(): JSX.Element {
const { const {
events, events } = this.state;
isLoading } = this.state;
const isEditMode: boolean = this.props.displayMode === DisplayMode.Edit; const isEditMode: boolean = this.props.displayMode === DisplayMode.Edit;
console.log("EVENTS", events);
return (<div> return (<div>
<div> <div>
<div role="application"> <div role="application">
@ -364,30 +359,4 @@ export default class CalendarFeedSummary extends React.Component<ICalendarFeedSu
}); });
} }
} }
}
/**
* Saves events in cache with an expiry date
* @param events an array of events to save in cache
*/
private _setCache(events: ICalendarEvent[]): void {
const { Name, FeedUrl, CacheDuration } = this.props.provider;
// don't cache if we haven't set a cache duration
if (CacheDuration === undefined || CacheDuration === 0) {
return;
}
const expiry: moment.Moment = moment().add(CacheDuration, "minutes");
// we use minutes instead of milliseconds, it doesn't make sense that
// people want to cache a feed for milliseconds, or seconds
// but feel free to change it to suit your needs
const cache: IFeedCache = {
feedType: Name,
feedUrl: FeedUrl,
events: events,
expiry: expiry
};
localStorage.setItem(CacheKey, JSON.stringify(cache));
}
}

View File

@ -5,10 +5,10 @@
* file because that's what the Office UI Fabric team does and * file because that's what the Office UI Fabric team does and
* I kinda liked it. * I kinda liked it.
*/ */
import { DisplayMode } from '@microsoft/sp-core-library'; import { DisplayMode } from "@microsoft/sp-core-library";
import { IWebPartContext } from '@microsoft/sp-webpart-base'; import { IWebPartContext } from "@microsoft/sp-webpart-base";
import { Moment } from 'moment'; import { Moment } from "moment";
import { ICalendarEvent, ICalendarService } from '../../../shared/services/CalendarService'; import { ICalendarEvent, ICalendarService } from "../../../shared/services/CalendarService";
/** /**
* The props for the calendar feed summary component * The props for the calendar feed summary component
@ -42,4 +42,4 @@ export interface IFeedCache {
expiry: Moment; expiry: Moment;
feedType: string; feedType: string;
feedUrl: string; feedUrl: string;
} }

View File

@ -49,6 +49,8 @@ define([], function() {
"ErrorInvalidiCalFeed": "The URL you provided does not appear to be an iCal feed. Are you sure you selected the right feed type?", "ErrorInvalidiCalFeed": "The URL you provided does not appear to be an iCal feed. Are you sure you selected the right feed type?",
"ErrorInvalidWordPressFeed": "The URL you provided does not appear to be a WordPress feed. Are you sure you selected the right feed type?", "ErrorInvalidWordPressFeed": "The URL you provided does not appear to be a WordPress feed. Are you sure you selected the right feed type?",
"AddToCalendarAriaLabel": "Press enter to download the calendar file to your device.", "AddToCalendarAriaLabel": "Press enter to download the calendar file to your device.",
"AddToCalendarButtonLabel": "Add to my calendar" "AddToCalendarButtonLabel": "Add to my calendar",
"AllDayDateFormat": "dddd, MMMM Do YYYY",
"LocalizedTimeFormat": "llll"
} }
}); });

View File

@ -50,6 +50,8 @@ declare interface ICalendarFeedSummaryWebPartStrings {
ErrorInvalidWordPressFeed: string; ErrorInvalidWordPressFeed: string;
AddToCalendarAriaLabel: string; AddToCalendarAriaLabel: string;
AddToCalendarButtonLabel: string; AddToCalendarButtonLabel: string;
AllDayDateFormat: string;
LocalizedTimeFormat: string;
} }
declare module 'CalendarFeedSummaryWebPartStrings' { declare module 'CalendarFeedSummaryWebPartStrings' {

View File

@ -0,0 +1,47 @@
{
"$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.

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,14 +1,15 @@
{ {
"compilerOptions": { "compilerOptions": {
"moduleResolution": "node",
"target": "es5", "target": "es5",
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "node",
"jsx": "react", "jsx": "react",
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"skipLibCheck": true, "skipLibCheck": true,
"outDir": "lib",
"typeRoots": [ "typeRoots": [
"./node_modules/@types", "./node_modules/@types",
"./node_modules/@microsoft" "./node_modules/@microsoft"
@ -22,5 +23,12 @@
"dom", "dom",
"es2015.collection" "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
}
}