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": {
"version": "1.5.0",
"isCreatingSolution": true,
"environment": "spo",
"version": "1.7.0",
"libraryName": "react-calendar-feed",
"libraryId": "dd42aa00-b07d-48a2-8896-cc2f8c0d3fae",
"environment": "spo",
"isCreatingSolution": true,
"packageManager": "npm",
"isDomainIsolated": false,
"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
![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
@ -47,6 +47,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
## Disclaimer
@ -63,7 +64,7 @@ Version|Date|Comments
- 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.
- 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.
@ -81,5 +82,6 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
- 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

@ -17,4 +17,4 @@
"ControlStrings": "node_modules/@pnp/spfx-controls-react/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"
}

View File

@ -3,8 +3,10 @@
"solution": {
"name": "react-calendar-feed-client-side-solution",
"id": "dd42aa00-b07d-48a2-8896-cc2f8c0d3fae",
"version": "1.0.1.2",
"includeClientSideAssets": true
"version": "3.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false,
"iconPath": "images/appicon.png"
},
"paths": {
"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",
"version": "1.1.0",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
@ -11,34 +11,35 @@
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "~1.4.1",
"@microsoft/sp-lodash-subset": "~1.4.1",
"@microsoft/sp-office-ui-fabric-core": "~1.4.1",
"@microsoft/sp-webpart-base": "~1.4.1",
"@pnp/spfx-controls-react": "^1.3.0",
"@pnp/spfx-property-controls": "^1.6.0",
"@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",
"@types/es6-promise": "0.0.33",
"@types/react": "15.6.6",
"@types/react-dom": "15.5.6",
"@types/webpack-env": ">=1.12.1 <1.14.0",
"ical.js": "^1.2.2",
"@types/react": "16.4.2",
"@types/react-dom": "16.0.5",
"@types/webpack-env": "1.13.1",
"ical.js": "^1.3.0",
"ics-js": "^0.10.2",
"jquery": "^3.3.1",
"moment": "^2.22.1",
"react": "15.6.2",
"react-dom": "15.6.2",
"react-slick": "^0.23.1",
"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.2",
"xml-js": "^1.6.8",
"xml2js": "^0.4.19"
},
"devDependencies": {
"@microsoft/sp-build-web": "~1.4.1",
"@microsoft/sp-module-interfaces": "~1.4.1",
"@microsoft/sp-webpart-workbench": "~1.4.1",
"@types/chai": ">=3.4.34 <3.6.0",
"@types/mocha": ">=2.2.33 <2.6.0",
"ajv": "~5.2.2",
"gulp": "~3.9.1"
"@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",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2"
}
}

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import * as moment from 'moment';
import { css } from 'office-ui-fabric-react/lib/Utilities';
import * as React from 'react';
import styles from './DateBox.module.scss';
import { DateBoxSize, IDateBoxProps, IDateBoxState } from './DateBox.types';
import * as moment from "moment";
import { css } from "office-ui-fabric-react/lib/Utilities";
import * as React from "react";
import styles from "./DateBox.module.scss";
import { DateBoxSize, IDateBoxProps, IDateBoxState } from "./DateBox.types";
/**
* 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>);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import { ActionButton, IButtonProps } from 'office-ui-fabric-react/lib/Button';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { css } from 'office-ui-fabric-react/lib/Utilities';
import * as React from 'react';
import { IPagingProps, IPagingState } from '.';
import styles from './Paging.module.scss';
import * as strings from 'CalendarFeedSummaryWebPartStrings';
import { ActionButton, IButtonProps } from "office-ui-fabric-react/lib/Button";
import { Icon } from "office-ui-fabric-react/lib/Icon";
import { css } from "office-ui-fabric-react/lib/Utilities";
import * as React from "react";
import { IPagingProps, IPagingState } from ".";
import styles from "./Paging.module.scss";
import * as strings from "CalendarFeedSummaryWebPartStrings";
/**
* 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> {
public render(): React.ReactElement<IPagingProps> {
const { totalItems, itemsCountPerPage, currentPage } = this.props;
const { currentPage } = this.props;
// calculate the page situation
const numberOfPages: number = this._getNumberOfPages();

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
/**
* Exchange Public Calendar Service
*/
import { ICalendarService } from '..';
import { ICalendarEvent } from '../ICalendarEvent';
import { iCalCalendarService } from '../iCalCalendarService';
import { ICalendarService } from "..";
import { ICalendarEvent } from "../ICalendarEvent";
import { iCalCalendarService } from "../iCalCalendarService";
// tslint:disable-next-line:class-name
export class ExchangePublicCalendarService extends iCalCalendarService implements ICalendarService {
@ -13,8 +13,8 @@ export class ExchangePublicCalendarService extends iCalCalendarService implement
}
public getEvents = (): Promise<ICalendarEvent[]> => {
// Exchange public calendar shares are really ICS calendars.
// we allow users to pass either the .html URL or
// exchange public calendar shares are really ICS calendars.
// we allow users to pass either the .html URL or
// the .ics, but we really need the .ics file
const htmlExtension:string = ".html";
@ -25,15 +25,5 @@ export class ExchangePublicCalendarService extends iCalCalendarService implement
}
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 { CalendarEventRange, ICalendarEvent } from '.';
import { IWebPartContext } from "@microsoft/sp-webpart-base";
import { CalendarEventRange, ICalendarEvent } from ".";
export interface ICalendarService {
Context: IWebPartContext;
@ -9,4 +9,4 @@ export interface ICalendarService {
CacheDuration: number;
Name: string;
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.
* Don't freak out, it didn't just disappear.
*/
import * as moment from 'moment';
import { BaseCalendarService } from '../BaseCalendarService';
import { ICalendarEvent } from '../ICalendarEvent';
import { ICalendarService } from '../ICalendarService';
import * as moment from "moment";
import { BaseCalendarService } from "../BaseCalendarService";
import { ICalendarEvent } from "../ICalendarEvent";
import { ICalendarService } from "../ICalendarService";
const today: Date = new Date();
const sampleEvents: ICalendarEvent[] = [
{
"title": "This event will be tomorrow",
@ -155,4 +154,4 @@ export class MockCalendarService extends BaseCalendarService implements ICalenda
}, 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.
* 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 { escape, unescape } from '@microsoft/sp-lodash-subset';
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";
export class RSSCalendarService extends BaseCalendarService implements ICalendarService {
constructor() {
@ -51,7 +51,6 @@ export class RSSCalendarService extends BaseCalendarService implements ICalendar
let title: string = this._getElementValue(item, "title");
let link: string = this._getElementValue(item, "link");
let pubDate: Date = new Date(this._getElementValue(item, "pubDate"));
let category: string = this._getElementValue(item, "category");
let description: string = this._getElementValue(item, "description");
return {
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);
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
*/
import { ICalendarService } from '..';
import { BaseCalendarService } from '../BaseCalendarService';
import { ICalendarEvent } from '../ICalendarEvent';
import { IWordPressFullCalendarEventResponse } from './IWordPressFullCalendarEventResponse';
import { ICalendarService } from "..";
import { BaseCalendarService } from "../BaseCalendarService";
import { ICalendarEvent } from "../ICalendarEvent";
import { IWordPressFullCalendarEventResponse } from "./IWordPressFullCalendarEventResponse";
export class WordPressFullCalendarService extends BaseCalendarService implements ICalendarService {
constructor() {
@ -39,4 +39,4 @@ export class WordPressFullCalendarService extends BaseCalendarService implements
});
}
}
}

View File

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

View File

@ -1,11 +1,11 @@
/**
* ExtensionService
*/
import { HttpClientResponse } from '@microsoft/sp-http';
import * as ICAL from 'ical.js';
import { ICalendarService } from '..';
import { BaseCalendarService } from '../BaseCalendarService';
import { ICalendarEvent } from '../ICalendarEvent';
import { HttpClientResponse } from "@microsoft/sp-http";
import * as ICAL from "ical.js";
import { ICalendarService } from "..";
import { BaseCalendarService } from "../BaseCalendarService";
import { ICalendarEvent } from "../ICalendarEvent";
// tslint:disable-next-line:class-name
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 './ICalendarService';
export * from './CalendarEventRange';
export * from './CalendarServiceProviderList';
export * from "./ICalendarEvent";
export * from "./ICalendarService";
export * from "./CalendarEventRange";
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",
"id": "b83bb343-bc5e-460c-9efd-52de06d32ffa",
"id": "0459b501-da31-43c7-9a9a-d5c59cc2d667",
"alias": "CalendarFeedSummaryWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"preconfiguredEntries": [
{
"groupId": "cf066440-0614-43d6-98ae-0b31cf14c7c3", // Text, media and content
"preconfiguredEntries": [{
"groupId": "cf066440-0614-43d6-98ae-0b31cf14c7c3",
"group": {
"default": "Text, media and content"
},
@ -24,16 +19,13 @@
"description": {
"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",
"properties": {
"title": "Upcoming Events",
"dateRange": 4, /* Year */
"dateRange": 4,
"maxEvents": 4,
"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
import { BaseClientSideWebPart, IPropertyPaneConfiguration, IPropertyPaneDropdownOption, PropertyPaneDropdown } from '@microsoft/sp-webpart-base';
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';
import * as strings from 'CalendarFeedSummaryWebPartStrings';
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { CalendarEventRange, DateRange, ICalendarService } from '../../shared/services/CalendarService';
import { CalendarServiceProviderList } from '../../shared/services/CalendarService/CalendarServiceProviderList';
import { ICalendarFeedSummaryWebPartProps } from './CalendarFeedSummaryWebPart.types';
import CalendarFeedSummary from './components/CalendarFeedSummary';
import { ICalendarFeedSummaryProps } from './components/CalendarFeedSummary.types';
import { BaseClientSideWebPart, IPropertyPaneConfiguration, IPropertyPaneDropdownOption, PropertyPaneDropdown } from "@microsoft/sp-webpart-base";
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";
import * as strings from "CalendarFeedSummaryWebPartStrings";
import * as React from "react";
import * as ReactDom from "react-dom";
import { CalendarEventRange, DateRange, ICalendarService } from "../../shared/services/CalendarService";
import { CalendarServiceProviderList } from "../../shared/services/CalendarService/CalendarServiceProviderList";
import { ICalendarFeedSummaryWebPartProps } from "./CalendarFeedSummaryWebPart.types";
import CalendarFeedSummary from "./components/CalendarFeedSummary";
import { ICalendarFeedSummaryProps } from "./components/CalendarFeedSummary.types";
// this is the same width that the SharePoint events web parts use to render as narrow
const MaxMobileWidth: number = 480;
@ -39,7 +38,7 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
*/
public render(): void {
// 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)
const element: React.ReactElement<ICalendarFeedSummaryProps> = React.createElement(
@ -61,14 +60,6 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
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
* people are typing in the feed URL.
@ -231,7 +222,6 @@ export default class CalendarFeedSummaryWebPart extends BaseClientSideWebPart<IC
*/
private _getDataProvider(): ICalendarService {
const {
feedType,
feedUrl,
useCORS,
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
@ -11,4 +11,4 @@ export interface ICalendarFeedSummaryWebPartProps {
dateRange: DateRange; // date range to retrieve events
useCORS: boolean; // use CORS proxy when retrieving events
cacheDuration: number; // how long to cache events for
}
}

View File

@ -1,17 +1,17 @@
import { DisplayMode } from '@microsoft/sp-core-library';
import { SPComponentLoader } from '@microsoft/sp-loader';
import { Placeholder } from '@pnp/spfx-controls-react/lib/Placeholder';
import { WebPartTitle } from '@pnp/spfx-controls-react/lib/WebPartTitle';
import * as strings from 'CalendarFeedSummaryWebPartStrings';
import * as moment from 'moment';
import { FocusZone, FocusZoneDirection, List, Spinner, css } from 'office-ui-fabric-react';
import * as React from 'react';
import { CarouselContainer } from '../../../shared/components/CarouselContainer';
import { EventCard } from '../../../shared/components/EventCard';
import { Paging } from '../../../shared/components/Paging';
import { CalendarServiceProviderType, ICalendarEvent, ICalendarService } from '../../../shared/services/CalendarService';
import styles from './CalendarFeedSummary.module.scss';
import { ICalendarFeedSummaryProps, ICalendarFeedSummaryState, IFeedCache } from './CalendarFeedSummary.types';
import { DisplayMode } from "@microsoft/sp-core-library";
import { SPComponentLoader } from "@microsoft/sp-loader";
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import * as strings from "CalendarFeedSummaryWebPartStrings";
import * as moment from "moment";
import { FocusZone, FocusZoneDirection, List, Spinner, css } from "office-ui-fabric-react";
import * as React from "react";
import { CarouselContainer } from "../../../shared/components/CarouselContainer";
import { EventCard } from "../../../shared/components/EventCard";
import { Paging } from "../../../shared/components/Paging";
import { CalendarServiceProviderType, ICalendarEvent, ICalendarService } from "../../../shared/services/CalendarService";
import styles from "./CalendarFeedSummary.module.scss";
import { ICalendarFeedSummaryProps, ICalendarFeedSummaryState, IFeedCache } from "./CalendarFeedSummary.types";
// the key used when caching events
const CacheKey: string = "calendarFeedSummary";
@ -30,8 +30,8 @@ export default class CalendarFeedSummary extends React.Component<ICalendarFeedSu
};
// 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.6.0/slick-theme.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.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 ||
prevProvider.Name !== currProvider.Name ||
prevProvider.FeedUrl !== currProvider.FeedUrl ||
prevProvider.Name !== currProvider.Name ||
prevProvider.EventRange.DateRange !== currProvider.EventRange.DateRange ||
prevProvider.UseCORS !== currProvider.UseCORS;
if (settingsHaveChanged) {
@ -88,13 +90,7 @@ export default class CalendarFeedSummary extends React.Component<ICalendarFeedSu
public render(): React.ReactElement<ICalendarFeedSummaryProps> {
const {
isConfigured,
isNarrow,
} = this.props;
const {
events,
isLoading,
error
} = this.state;
// if we're not configured, show the placeholder
if (!isConfigured) {
@ -230,7 +226,6 @@ export default class CalendarFeedSummary extends React.Component<ICalendarFeedSu
*/
private _renderNarrowList(): JSX.Element {
const {
isLoading,
events,
currentPage
} = this.state;
@ -288,10 +283,10 @@ export default class CalendarFeedSummary extends React.Component<ICalendarFeedSu
*/
private _renderNormalList(): JSX.Element {
const {
events,
isLoading } = this.state;
events } = this.state;
const isEditMode: boolean = this.props.displayMode === DisplayMode.Edit;
console.log("EVENTS", events);
return (<div>
<div>
<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
* I kinda liked it.
*/
import { DisplayMode } from '@microsoft/sp-core-library';
import { IWebPartContext } from '@microsoft/sp-webpart-base';
import { Moment } from 'moment';
import { ICalendarEvent, ICalendarService } from '../../../shared/services/CalendarService';
import { DisplayMode } from "@microsoft/sp-core-library";
import { IWebPartContext } from "@microsoft/sp-webpart-base";
import { Moment } from "moment";
import { ICalendarEvent, ICalendarService } from "../../../shared/services/CalendarService";
/**
* The props for the calendar feed summary component
@ -42,4 +42,4 @@ export interface IFeedCache {
expiry: Moment;
feedType: 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?",
"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.",
"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;
AddToCalendarAriaLabel: string;
AddToCalendarButtonLabel: string;
AllDayDateFormat: string;
LocalizedTimeFormat: string;
}
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": {
"moduleResolution": "node",
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
@ -22,5 +23,12 @@
"dom",
"es2015.collection"
]
}
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

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