Support for localization

This commit is contained in:
Nanddeep Nachan 2020-07-10 14:19:07 +00:00
parent daa67c1fa7
commit 7727eafd28
13 changed files with 849 additions and 307 deletions

View File

@ -64,6 +64,8 @@ The sample also provisions sample data to the "Timeline" list, which can be used
Below NPM package is used to develop this sample. Below NPM package is used to develop this sample.
1. @pnp/sp (https://www.npmjs.com/package/@pnp/sp) 1. @pnp/sp (https://www.npmjs.com/package/@pnp/sp)
2. @pnp/spfx-controls-react (https://pnp.github.io/sp-dev-fx-controls-react/)
3. @pnp/spfx-property-controls (https://pnp.github.io/sp-dev-fx-property-controls/)
## Used SharePoint Framework Version ## Used SharePoint Framework Version
@ -121,3 +123,5 @@ This sample web part displays list of events in chronological order with data st
[figure5]: ./assets/layout-horizontal.png [figure5]: ./assets/layout-horizontal.png
[figure6]: ./assets/list-schema.png [figure6]: ./assets/list-schema.png
[figure7]: ./assets/list-sample-data.png [figure7]: ./assets/list-sample-data.png
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-timeline" />

View File

@ -13,6 +13,8 @@
}, },
"externals": {}, "externals": {},
"localizedResources": { "localizedResources": {
"TimelineWebPartStrings": "lib/webparts/timeline/loc/{locale}.js" "TimelineWebPartStrings": "lib/webparts/timeline/loc/{locale}.js",
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,8 @@
"@microsoft/sp-property-pane": "1.10.0", "@microsoft/sp-property-pane": "1.10.0",
"@microsoft/sp-webpart-base": "1.10.0", "@microsoft/sp-webpart-base": "1.10.0",
"@pnp/sp": "^2.0.6", "@pnp/sp": "^2.0.6",
"@pnp/spfx-controls-react": "1.19.0",
"@pnp/spfx-property-controls": "1.19.0",
"@types/es6-promise": "0.0.33", "@types/es6-promise": "0.0.33",
"@types/react": "16.8.8", "@types/react": "16.8.8",
"@types/react-dom": "16.8.3", "@types/react-dom": "16.8.3",

View File

@ -17,14 +17,14 @@ export default class TimelineService {
/** /**
* Get timeline activity by id. * Get timeline activity by id.
* @param listTitle * @param listId
* @param id * @param id
*/ */
public async getTimelineActivity(listTitle: string, id: number): Promise<ITimelineActivity> { public async getTimelineActivity(listId: string, id: number): Promise<ITimelineActivity> {
let returnTimelineActivity: ITimelineActivity = undefined; let returnTimelineActivity: ITimelineActivity = undefined;
try { try {
let activity: any = await sp.web.lists.getByTitle(listTitle).items.usingCaching().getById(id) let activity: any = await sp.web.lists.getById(listId).items.usingCaching().getById(id)
.select("Id", "Title", "SPFxTimelineLink", "SPFxTimelineDate", "SPFxTimelinePicture", "SPFxTimelineDescription") .select("Id", "Title", "SPFxTimelineLink", "SPFxTimelineDate", "SPFxTimelinePicture", "SPFxTimelineDescription")
.get(); .get();
@ -46,15 +46,15 @@ export default class TimelineService {
/** /**
* Get all timeline activities * Get all timeline activities
* @param listTitle * @param listId
* @param sortOrder * @param sortOrder
*/ */
public async getTimelineActivities(listTitle: string, sortOrder: string): Promise<ITimelineActivity[]> { public async getTimelineActivities(listId: string, sortOrder: string): Promise<ITimelineActivity[]> {
let returnTimelineActivities: ITimelineActivity[] = []; let returnTimelineActivities: ITimelineActivity[] = [];
let sortOrderAsc: boolean = (sortOrder === "asc"); let sortOrderAsc: boolean = (sortOrder === "asc");
try { try {
let activities: any[] = await sp.web.lists.getByTitle(listTitle).items let activities: any[] = await sp.web.lists.getById(listId).items
.select("Id", "Title", "SPFxTimelineLink", "SPFxTimelineDate", "SPFxTimelinePicture", "SPFxTimelineDescription") .select("Id", "Title", "SPFxTimelineLink", "SPFxTimelineDate", "SPFxTimelinePicture", "SPFxTimelineDescription")
.orderBy("SPFxTimelineDate", sortOrderAsc) .orderBy("SPFxTimelineDate", sortOrderAsc)
.get(); .get();
@ -81,10 +81,10 @@ export default class TimelineService {
/** /**
* Adds timeline activity to SP list. * Adds timeline activity to SP list.
* @param listTitle * @param listId
* @param newTimelineActivity * @param newTimelineActivity
*/ */
public async addTimelineActivity(listTitle: string, newTimelineActivity: ITimelineActivity) { public async addTimelineActivity(listId: string, newTimelineActivity: ITimelineActivity) {
try { try {
let addData: ITypedHash<any> = { let addData: ITypedHash<any> = {
Title: newTimelineActivity.activityTitle, Title: newTimelineActivity.activityTitle,
@ -110,7 +110,7 @@ export default class TimelineService {
}; };
} }
await sp.web.lists.getByTitle(listTitle).items.add(addData); await sp.web.lists.getById(listId).items.add(addData);
} }
catch (error) { catch (error) {
console.log(error); console.log(error);
@ -120,10 +120,10 @@ export default class TimelineService {
/** /**
* Updates timeline activity to SP list by id. * Updates timeline activity to SP list by id.
* @param listTitle * @param listId
* @param updateTimelineActivity * @param updateTimelineActivity
*/ */
public async updateTimelineActivity(listTitle: string, updateTimelineActivity: ITimelineActivity) { public async updateTimelineActivity(listId: string, updateTimelineActivity: ITimelineActivity) {
try { try {
let updateItem: ITypedHash<any> = { let updateItem: ITypedHash<any> = {
Title: updateTimelineActivity.activityTitle, Title: updateTimelineActivity.activityTitle,
@ -153,7 +153,7 @@ export default class TimelineService {
}; };
} }
await sp.web.lists.getByTitle(listTitle).items.getById(updateTimelineActivity.id).update(updateItem).then((value: any) => { await sp.web.lists.getById(listId).items.getById(updateTimelineActivity.id).update(updateItem).then((value: any) => {
console.log(value); console.log(value);
}); });
} }
@ -165,12 +165,12 @@ export default class TimelineService {
/** /**
* Deletes timeline activity from SP list. * Deletes timeline activity from SP list.
* @param listTitle * @param listId
* @param deleteTimelineActivity * @param deleteTimelineActivity
*/ */
public async deleteTimelineActivity(listTitle: string, deleteTimelineActivity: ITimelineActivity) { public async deleteTimelineActivity(listId: string, deleteTimelineActivity: ITimelineActivity) {
try { try {
await sp.web.lists.getByTitle(listTitle).items.getById(deleteTimelineActivity.id).delete(); await sp.web.lists.getById(listId).items.getById(deleteTimelineActivity.id).delete();
} }
catch (error) { catch (error) {
return Promise.reject(error); return Promise.reject(error);

View File

@ -22,11 +22,9 @@
"officeFabricIconFontName": "TimelineProgress", "officeFabricIconFontName": "TimelineProgress",
"properties": { "properties": {
"description": "Timeline Events", "description": "Timeline Events",
"listName": "Timeline",
"layout": "Vertical", "layout": "Vertical",
"showImage": true, "showImage": true,
"showDescription": true, "showDescription": true,
"dateFormat": "MM/DD/yyyy",
"sortOrder": "asc" "sortOrder": "asc"
} }
}] }]

View File

@ -11,7 +11,7 @@ import * as strings from 'TimelineWebPartStrings';
import Timeline from './components/Timeline'; import Timeline from './components/Timeline';
import { ITimelineProps } from './components/ITimelineProps'; import { ITimelineProps } from './components/ITimelineProps';
import TimelineService from '../../services/TimelineService'; import TimelineService from '../../services/TimelineService';
import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown'; import { PropertyFieldListPicker, PropertyFieldListPickerOrderBy, ISPList } from '@pnp/spfx-property-controls/lib/PropertyFieldListPicker';
export interface ITimelineWebPartProps { export interface ITimelineWebPartProps {
description: string; description: string;
@ -71,30 +71,44 @@ export default class TimelineWebPart extends BaseClientSideWebPart <ITimelineWeb
PropertyPaneTextField('description', { PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel label: strings.DescriptionFieldLabel
}), }),
PropertyPaneTextField('listName', { PropertyFieldListPicker('listName', {
label: strings.ListNameFieldLabel label: strings.ListNameFieldLabel,
selectedList: this.properties.listName,
includeHidden: false,
orderBy: PropertyFieldListPickerOrderBy.Title,
disabled: false,
onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
properties: this.properties,
context: this.context,
onGetErrorMessage: null,
deferredValidationTime: 0,
key: 'listPickerFieldId',
baseTemplate: 100
}), }),
PropertyPaneDropdown('layout', { PropertyPaneDropdown('layout', {
label: strings.LayoutFieldLabel, label: strings.LayoutFieldLabel,
options: [ options: [
{ key: 'Vertical', text: 'Vertical' }, { key: 'Vertical', text: strings.VerticalLabel },
{ key: 'Horizontal', text: 'Horizontal' } { key: 'Horizontal', text: strings.HorizontalLabel }
] ]
}), }),
PropertyPaneToggle('showImage', { PropertyPaneToggle('showImage', {
label: strings.ShowImageFieldLabel,checked:true label: strings.ShowImageFieldLabel,
checked: true
}), }),
PropertyPaneToggle('showDescription', { PropertyPaneToggle('showDescription', {
label: strings.ShowDescriptionFieldLabel, checked: true label: strings.ShowDescriptionFieldLabel,
checked: true
}), }),
PropertyPaneTextField('dateFormat', { PropertyPaneTextField('dateFormat', {
label: strings.DateFormatFieldLabel label: strings.DateFormatFieldLabel,
value: strings.DateFormatText
}), }),
PropertyPaneDropdown('sortOrder', { PropertyPaneDropdown('sortOrder', {
label: strings.SortOrderFieldLabel, label: strings.SortOrderFieldLabel,
options: [ options: [
{ key: 'asc', text: 'Ascending' }, { key: 'asc', text: strings.AscendingLabel },
{ key: 'desc', text: 'Descending' } { key: 'desc', text: strings.DescendingLabel }
] ]
}) })
] ]

View File

@ -1,5 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import styles from './timelineEvent.module.scss'; import styles from './timelineEvent.module.scss';
import * as strings from 'TimelineWebPartStrings';
import { IEventProps } from './ITimeLineEventProps'; import { IEventProps } from './ITimeLineEventProps';
import { IEventState } from './ITimeLineEventState'; import { IEventState } from './ITimeLineEventState';
import { ITimelineActivity } from '../../../../models/ITimelineActivity'; import { ITimelineActivity } from '../../../../models/ITimelineActivity';
@ -219,13 +220,13 @@ export class TimelineEvent extends React.Component<IEventProps, IEventState> {
<div> <div>
<Dialog <Dialog
isOpen={this.props.showPanel} isOpen={this.props.showPanel}
closeButtonAriaLabel="Close" closeButtonAriaLabel={strings.CloseLabel}
dialogContentProps={{ dialogContentProps={{
type: DialogType.normal, type: DialogType.normal,
title: title:
this.props.panelMode == 2 this.props.panelMode == 2
? "Edit Timeline Event" ? strings.EditEventLabel
: "Create Timeline Event", : strings.AddEventLabel,
showCloseButton: true, showCloseButton: true,
}} }}
onDismiss={this.hidePanel} onDismiss={this.hidePanel}
@ -235,7 +236,7 @@ export class TimelineEvent extends React.Component<IEventProps, IEventState> {
> >
<div> <div>
<TextField <TextField
label="Title" label={strings.TitleLabel}
required required
value={ value={
this.state.eventData this.state.eventData
@ -247,7 +248,7 @@ export class TimelineEvent extends React.Component<IEventProps, IEventState> {
/> />
</div> </div>
<Label> <Label>
Date {strings.DateLabel}
</Label> </Label>
<div <div
@ -339,14 +340,14 @@ export class TimelineEvent extends React.Component<IEventProps, IEventState> {
<div> <div>
<TextField <TextField
label="Description" label={strings.DescriptionLabel}
value={this.state.activityDescription} onChange={this.onDescriptionChange} value={this.state.activityDescription} onChange={this.onDescriptionChange}
multiline multiline
/> />
</div> </div>
<div> <div>
<TextField <TextField
label="Picture URL" label={strings.PictureURLLabel}
value={ value={
this.state.eventData this.state.eventData
? this.state.eventData.activityPictureUrl ? this.state.eventData.activityPictureUrl["Url"] : '' ? this.state.eventData.activityPictureUrl ? this.state.eventData.activityPictureUrl["Url"] : ''
@ -358,7 +359,7 @@ export class TimelineEvent extends React.Component<IEventProps, IEventState> {
</div> </div>
<div> <div>
<TextField <TextField
label="Link URL" label={strings.LinkURLLabel}
value={ value={
this.state.eventData this.state.eventData
? this.state.eventData.activityLink ? this.state.eventData.activityLink["Url"] : '' ? this.state.eventData.activityLink ? this.state.eventData.activityLink["Url"] : ''
@ -369,7 +370,7 @@ export class TimelineEvent extends React.Component<IEventProps, IEventState> {
/> />
</div> </div>
<DialogFooter> <DialogFooter>
<PrimaryButton onClick={this.onSave} text={this.props.panelMode == 2 ? "Update Event" : "Create Event"} /> <PrimaryButton onClick={this.onSave} text={this.props.panelMode == 2 ? strings.UpdateEventLabel : strings.AddEventLabel} />
<DefaultButton onClick={this.hidePanel} text="Cancel" /> <DefaultButton onClick={this.hidePanel} text="Cancel" />
</DialogFooter> </DialogFooter>
</Dialog> </Dialog>

View File

@ -1,27 +1,15 @@
@import "~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss"; @import "~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss";
:global(.ms-Dialog-inner) { .dialogOverride {
padding-left: 50px !important;
padding-right: 50px !important;
}
:global(.ms-Dialog-title) { :global(.ms-Dialog-title) {
color: white !important; color: white !important;
background-color: $ms-color-themePrimary !important; background-color: $ms-color-themePrimary !important;
font-size: 16px;
font-weight: bold;
padding: 18px !important;
} }
:global(i.ms-Button-icon[data-icon-name="Cancel"]) { :global(i.ms-Button-icon[data-icon-name="Cancel"]) {
width: 25px;
height: 25px;
background-repeat: no-repeat;
background-size: 23px 23px;
color: white !important; color: white !important;
} }
.dialogOverride {
@media (max-width: $ms-screen-max-md) { @media (max-width: $ms-screen-max-md) {
// need to remove justify center when on a small screen so dialog can use full width // need to remove justify center when on a small screen so dialog can use full width
:global(.ms-Dialog) { :global(.ms-Dialog) {

View File

@ -1,10 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import styles from './Timeline.module.scss'; import styles from './Timeline.module.scss';
import * as strings from 'TimelineWebPartStrings';
import { ITimelineProps } from './ITimelineProps'; import { ITimelineProps } from './ITimelineProps';
import { ITimelineState } from './ITimelineState'; import { ITimelineState } from './ITimelineState';
import { escape } from '@microsoft/sp-lodash-subset'; import { escape } from '@microsoft/sp-lodash-subset';
import TimelineService from '../../../services/TimelineService'; import TimelineService from '../../../services/TimelineService';
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
import TimelineActivity from "./TimelineActivity"; import TimelineActivity from "./TimelineActivity";
import { ITimelineActivity } from "../../../models/ITimelineActivity"; import { ITimelineActivity } from "../../../models/ITimelineActivity";
import { SPPermission } from '@microsoft/sp-page-context'; import { SPPermission } from '@microsoft/sp-page-context';
@ -36,9 +37,25 @@ export default class Timeline extends React.Component<ITimelineProps, ITimelineS
} }
} }
private _onConfigure = () => {
// Context of the web part
this.props.context.propertyPane.open();
}
public render(): React.ReactElement<ITimelineProps> { public render(): React.ReactElement<ITimelineProps> {
return ( return (
<div className={styles.timeline}> <div className={styles.timeline}>
{
this.state.timelineActivities.length == 0 &&
<Placeholder iconName='Edit'
iconText={strings.ConfigureWebPartLabel}
description={strings.ConfigureDescription}
buttonLabel={strings.ConfigureLabel}
onConfigure={this._onConfigure} />
}
{this.state.timelineActivities.length > 0 &&
<>
<h1>{this.props.description}</h1> <h1>{this.props.description}</h1>
<div className={this.props.layout == "Vertical" ? `${styles.timelineContainerVertical}` : `${styles.timelineContainerHorizontal}`}> <div className={this.props.layout == "Vertical" ? `${styles.timelineContainerVertical}` : `${styles.timelineContainerHorizontal}`}>
{ {
@ -59,6 +76,8 @@ export default class Timeline extends React.Component<ITimelineProps, ITimelineS
); );
})} })}
</div> </div>
</>
}
</div> </div>
); );
} }
@ -66,13 +85,17 @@ export default class Timeline extends React.Component<ITimelineProps, ITimelineS
public componentDidMount(): void { public componentDidMount(): void {
this.TimelineService.getTimelineActivities(this.props.listName, this.props.sortOrder).then((activities: ITimelineActivity[]) => { this.TimelineService.getTimelineActivities(this.props.listName, this.props.sortOrder).then((activities: ITimelineActivity[]) => {
this.setState({ timelineActivities: activities }); this.setState({ timelineActivities: activities });
}).catch((error: any) => {
this.setState({ timelineActivities: [] });
}); });
} }
public componentWillReceiveProps(nextProps: ITimelineProps) { public componentWillReceiveProps(nextProps: ITimelineProps) {
if (this.props.sortOrder !== nextProps.sortOrder) { if (this.props.sortOrder !== nextProps.sortOrder || this.props.listName !== nextProps.listName) {
this.TimelineService.getTimelineActivities(this.props.listName, nextProps.sortOrder).then((activities: ITimelineActivity[]) => { this.TimelineService.getTimelineActivities(nextProps.listName, nextProps.sortOrder).then((activities: ITimelineActivity[]) => {
this.setState({ timelineActivities: activities }); this.setState({ timelineActivities: activities });
}).catch((error: any) => {
this.setState({ timelineActivities: [] });
}); });
} }
} }

View File

@ -1,5 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import styles from './Timeline.module.scss'; import styles from './Timeline.module.scss';
import * as strings from 'TimelineWebPartStrings';
import { escape } from '@microsoft/sp-lodash-subset'; import { escape } from '@microsoft/sp-lodash-subset';
import { ITimelineActivity } from "../../../models"; import { ITimelineActivity } from "../../../models";
import { Card, ICardTokens, ICardSectionStyles, ICardSectionTokens } from '@uifabric/react-cards'; import { Card, ICardTokens, ICardSectionStyles, ICardSectionTokens } from '@uifabric/react-cards';
@ -178,14 +179,14 @@ export default class TimelineActivity extends React.Component<IActivityProps, IA
{ {
this.props.canEdit && this.props.canEdit &&
<div className={this.state.layout == "Vertical" ? `${styles.timelineAddVertical}` : `${styles.timelineAddHorizontal}`}> <div className={this.state.layout == "Vertical" ? `${styles.timelineAddVertical}` : `${styles.timelineAddHorizontal}`}>
<IconButton iconProps={addToIcon} title="Add Timeline Event" ariaLabel="Add Activity" className={styles.addToButton} onClick={this.createEvent} /> <IconButton iconProps={addToIcon} title={strings.AddEventLabel} ariaLabel={strings.AddEventLabel} className={styles.addToButton} onClick={this.createEvent} />
</div> </div>
} }
<Dialog type={DialogType.normal} <Dialog type={DialogType.normal}
hidden={!this.state.showDeleteDialog} hidden={!this.state.showDeleteDialog}
title='Delete event?' title={strings.DeleteEventLabel}
subText='Do you want to delete this event?' subText={strings.DeleteEventConfirmationLabel}
isBlocking={true} isBlocking={true}
containerClassName={'ms-dialogMainOverride'}> containerClassName={'ms-dialogMainOverride'}>
<DialogFooter> <DialogFooter>
@ -220,7 +221,6 @@ export default class TimelineActivity extends React.Component<IActivityProps, IA
)} )}
<div className={styles.timelineCard}> <div className={styles.timelineCard}>
<Card <Card
aria-label="Clickable horizontal card "
horizontal horizontal
tokens={cardTokens} tokens={cardTokens}
> >
@ -229,7 +229,7 @@ export default class TimelineActivity extends React.Component<IActivityProps, IA
<Card.Item fill> <Card.Item fill>
<Image <Image
src={activity.activityPictureUrl ? activity.activityPictureUrl["Url"] : ''} src={activity.activityPictureUrl ? activity.activityPictureUrl["Url"] : ''}
alt="Placeholder image." alt={activity.activityTitle}
width="100px" width="100px"
height="100px" height="100px"
/> />
@ -259,7 +259,7 @@ export default class TimelineActivity extends React.Component<IActivityProps, IA
> >
{canEdit && {canEdit &&
<IconButton <IconButton
id="ContextualMenuButton1" id="ContextualMenuButtonMore"
text="" text=""
split={false} split={false}
iconProps={{ iconName: "MoreVertical" }} iconProps={{ iconName: "MoreVertical" }}
@ -270,7 +270,7 @@ export default class TimelineActivity extends React.Component<IActivityProps, IA
items: [ items: [
{ {
key: "Edit", key: "Edit",
name: "Edit", name: strings.EditEventLabel,
onClick: (event) => { onClick: (event) => {
this.setState({ selectedEvent: activity }); this.setState({ selectedEvent: activity });
this.editEvent(); this.editEvent();
@ -282,7 +282,7 @@ export default class TimelineActivity extends React.Component<IActivityProps, IA
}, },
{ {
key: "Delete", key: "Delete",
name: "Delete", name: strings.DeleteEventLabel,
onClick: (event) => { onClick: (event) => {
this.setState({ this.setState({
selectedEvent: activity, selectedEvent: activity,

View File

@ -8,6 +8,25 @@ define([], function() {
"ShowImageFieldLabel": "Show Image", "ShowImageFieldLabel": "Show Image",
"ShowDescriptionFieldLabel": "Show Description", "ShowDescriptionFieldLabel": "Show Description",
"DateFormatFieldLabel": "Date Format", "DateFormatFieldLabel": "Date Format",
"SortOrderFieldLabel": "Sort Direction" "SortOrderFieldLabel": "Sort Direction",
"EditEventLabel": "Edit Event",
"UpdateEventLabel": "Update Event",
"AddEventLabel": "Add Event",
"DeleteEventLabel": "Delete Event",
"DeleteEventConfirmationLabel": "Do you want to delete this event?",
"CloseLabel": "Close",
"TitleLabel": "Title",
"DateLabel": "Date",
"DescriptionLabel": "Description",
"PictureURLLabel": "Picture URL",
"LinkURLLabel": "Link URL",
"ConfigureWebPartLabel": "Configure your timeline",
"ConfigureDescription": "Please select the list with timeline information",
"ConfigureLabel": "Configure",
"AscendingLabel": "Ascending",
"DescendingLabel": "Descending",
"VerticalLabel": "Vertical",
"HorizontalLabel": "Horizontal",
"DateFormatText": "MM/DD/yyyy"
} }
}); });

View File

@ -8,6 +8,25 @@ declare interface ITimelineWebPartStrings {
ShowDescriptionFieldLabel: string; ShowDescriptionFieldLabel: string;
DateFormatFieldLabel: string; DateFormatFieldLabel: string;
SortOrderFieldLabel: string; SortOrderFieldLabel: string;
EditEventLabel: string;
UpdateEventLabel: string;
AddEventLabel: string;
DeleteEventLabel: string;
DeleteEventConfirmationLabel: string;
CloseLabel: string;
TitleLabel: string;
DateLabel: string;
DescriptionLabel: string;
PictureURLLabel: string;
LinkURLLabel: string;
ConfigureWebPartLabel: string;
ConfigureDescription: string;
ConfigureLabel: string;
AscendingLabel: string;
DescendingLabel: string;
VerticalLabel: string;
HorizontalLabel: string;
DateFormatText: string;
} }
declare module 'TimelineWebPartStrings' { declare module 'TimelineWebPartStrings' {