Füge neue Ereignis- und Alle anzeigen-Links hinzu, aktualisiere die Version und erweitere die Datenstruktur für die Ereignisse
This commit is contained in:
@@ -8,6 +8,7 @@ SPFx-1.4.1-WebPart für SharePoint Server Subscription Edition. Es orientiert si
|
||||
- serverseitige Expansion von Serienterminen über `Lists.asmx` und `ExpandRecurrence=TRUE`
|
||||
- Berücksichtigung normaler Termine, Serienvorkommen, geänderter Ausnahmen und gelöschter Vorkommen
|
||||
- Layouts **Filmstreifen** und **Kompakt**
|
||||
- SharePoint-nahe Ereigniszeilen mit Datumsfeld sowie **Neues Ereignis**- und **Alle anzeigen**-Links
|
||||
- Filterung nach Kategorie und Zeitraum ab heute
|
||||
- Anzeige von Datum, Uhrzeit, Ganztägigkeit, Ort, Bild und Serienkennzeichnung
|
||||
- responsive Darstellung mit SharePoint-Schrift und Themefarben
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"name": "modern-recurring-events-webpart",
|
||||
"title": "modern-recurring-events-webpart",
|
||||
"id": "f1082b4a-bbdf-4d31-852d-97e0685c20e2",
|
||||
"version": "1.0.0.0",
|
||||
"version": "1.0.1.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "modern-recurring-events-webpart",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": { "node": ">=6.9.0 <9.0.0" },
|
||||
|
||||
@@ -13,12 +13,15 @@ export class RecurringEventsDataService {
|
||||
|
||||
public getLists(): Promise<IEventsListOption[]> {
|
||||
const url: string = this.webUrl +
|
||||
'/_api/web/lists?$select=Id,Title,RootFolder/ServerRelativeUrl&$expand=RootFolder&$filter=Hidden eq false and BaseTemplate eq 106&$orderby=Title';
|
||||
'/_api/web/lists?$select=Id,Title,DefaultViewUrl,DefaultNewFormUrl,RootFolder/ServerRelativeUrl' +
|
||||
'&$expand=RootFolder&$filter=Hidden eq false and BaseTemplate eq 106&$orderby=Title';
|
||||
return this.getJson(url).then((payload: any): IEventsListOption[] => // tslint:disable-line:no-any
|
||||
this.results(payload).map((list: any): IEventsListOption => ({ // tslint:disable-line:no-any
|
||||
id: this.cleanGuid(list.Id),
|
||||
title: String(list.Title || list.Id),
|
||||
serverRelativeUrl: String(list.RootFolder && list.RootFolder.ServerRelativeUrl || '')
|
||||
serverRelativeUrl: String(list.RootFolder && list.RootFolder.ServerRelativeUrl || ''),
|
||||
defaultViewUrl: String(list.DefaultViewUrl || ''),
|
||||
defaultNewFormUrl: String(list.DefaultNewFormUrl || '')
|
||||
})));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ export interface IEventsListOption {
|
||||
id: string;
|
||||
title: string;
|
||||
serverRelativeUrl: string;
|
||||
defaultViewUrl: string;
|
||||
defaultNewFormUrl: string;
|
||||
}
|
||||
|
||||
export interface IRecurringEvent {
|
||||
|
||||
@@ -50,14 +50,19 @@ export default class RecurringEventsWebPart extends BaseClientSideWebPart<IRecur
|
||||
this.service.getEvents(list, range, this.properties.category || '', normalizeMaxEvents(this.properties.maxEvents))
|
||||
.then((events: IRecurringEvent[]): void => {
|
||||
if (this.disposed || version !== this.requestVersion) { return; }
|
||||
if (!events.length) { this.renderStatus(strings.EmptyMessage, false); return; }
|
||||
const listUrl: string = list.serverRelativeUrl.replace(/\/$/, '');
|
||||
const element: React.ReactElement<IRecurringEventsProps> = React.createElement(RecurringEvents, {
|
||||
events: events,
|
||||
title: this.properties.title || strings.DefaultTitle,
|
||||
layout: this.properties.layout === 'compact' ? 'compact' : 'filmstrip',
|
||||
locale: this.context.pageContext.cultureInfo.currentUICultureName || 'en-us',
|
||||
allDayLabel: strings.AllDayLabel,
|
||||
recurringLabel: strings.RecurringLabel
|
||||
recurringLabel: strings.RecurringLabel,
|
||||
emptyMessage: strings.EmptyMessage,
|
||||
newEventLabel: strings.NewEventLabel,
|
||||
newEventUrl: list.defaultNewFormUrl || listUrl + '/NewForm.aspx',
|
||||
seeAllLabel: strings.SeeAllLabel,
|
||||
seeAllUrl: list.defaultViewUrl || listUrl
|
||||
});
|
||||
ReactDom.render(element, this.domElement);
|
||||
}).catch((error: Error): void => {
|
||||
|
||||
@@ -7,4 +7,9 @@ export interface IRecurringEventsProps {
|
||||
locale: string;
|
||||
allDayLabel: string;
|
||||
recurringLabel: string;
|
||||
emptyMessage: string;
|
||||
newEventLabel: string;
|
||||
newEventUrl: string;
|
||||
seeAllLabel: string;
|
||||
seeAllUrl: string;
|
||||
}
|
||||
|
||||
@@ -4,79 +4,31 @@
|
||||
color: "[theme:bodyText, default:#323130]";
|
||||
font-family: "Segoe UI", "Segoe UI Web (West European)", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: "[theme:bodyText, default:#323130]";
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
line-height: 32px;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
overflow-x: auto;
|
||||
padding: 0 0 8px;
|
||||
scroll-snap-type: x proximity;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: "[theme:white, default:#ffffff]";
|
||||
border: 1px solid "[theme:neutralLight, default:#edebe9]";
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 calc((100% - 32px) / 3);
|
||||
min-width: 240px;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.cardLink, .compactLink {
|
||||
color: "[theme:bodyText, default:#323130]";
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover { color: "[theme:bodyText, default:#323130]"; }
|
||||
&:focus { outline: 2px solid "[theme:focusBorder, default:#605e5c]"; outline-offset: -2px; }
|
||||
}
|
||||
|
||||
.cardLink { display: block; height: 100%; }
|
||||
.image, .imageFallback {
|
||||
background-color: "[theme:themePrimary, default:#0078d4]";
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
display: block;
|
||||
height: 150px;
|
||||
position: relative;
|
||||
}
|
||||
.imageFallback { background: linear-gradient(135deg, "[theme:themePrimary, default:#0078d4]", "[theme:themeDark, default:#005a9e]"); }
|
||||
.calendarIcon { color: #fff; font-size: 52px; left: 20px; opacity: .8; position: absolute; top: 16px; }
|
||||
.cardBody { display: flex; min-height: 112px; padding: 16px; }
|
||||
.cardContent, .compactContent { display: block; min-width: 0; }
|
||||
|
||||
.dateBadge {
|
||||
color: "[theme:themePrimary, default:#0078d4]";
|
||||
display: flex;
|
||||
flex: 0 0 48px;
|
||||
flex-direction: column;
|
||||
margin-right: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.month { font-size: 12px; font-weight: 600; line-height: 18px; text-transform: uppercase; }
|
||||
.day { color: "[theme:bodyText, default:#323130]"; font-size: 28px; font-weight: 300; line-height: 32px; }
|
||||
.title { display: block; font-size: 17px; font-weight: 600; line-height: 22px; overflow-wrap: break-word; }
|
||||
.dateLine, .location { color: "[theme:neutralSecondary, default:#605e5c]"; display: block; font-size: 13px; line-height: 19px; margin-top: 4px; }
|
||||
.recurring { color: "[theme:themePrimary, default:#0078d4]"; display: block; font-size: 12px; margin-top: 6px; }
|
||||
|
||||
.commandBar { align-items: center; display: flex; justify-content: space-between; margin-bottom: 18px; min-height: 32px; }
|
||||
.commandLink { align-items: center; color: "[theme:themePrimary, default:#0078d4]"; display: inline-flex; font-size: 14px; min-height: 32px; padding: 0 4px; text-decoration: none; }
|
||||
.commandLink:hover { color: "[theme:themeDark, default:#005a9e]"; text-decoration: underline; }
|
||||
.commandLink:focus { outline: 2px solid "[theme:focusBorder, default:#605e5c]"; outline-offset: 1px; }
|
||||
.addIcon { font-size: 22px; font-weight: 300; line-height: 20px; margin-right: 7px; }
|
||||
.heading { color: "[theme:bodyText, default:#323130]"; font-size: 24px; font-weight: 300; line-height: 32px; margin: 0 0 14px; }
|
||||
.empty { color: "[theme:neutralSecondary, default:#605e5c]"; font-size: 14px; padding: 16px 8px; }
|
||||
.filmstrip { display: flex; gap: 18px; list-style: none; margin: 0; overflow-x: auto; padding: 0 0 8px; scroll-snap-type: x proximity; }
|
||||
.card { background: "[theme:white, default:#ffffff]"; border-bottom: 1px solid "[theme:neutralLight, default:#edebe9]"; box-sizing: border-box; flex: 0 0 280px; scroll-snap-align: start; }
|
||||
.compact { list-style: none; margin: 0; padding: 0; }
|
||||
.compactItem { border-bottom: 1px solid "[theme:neutralLight, default:#edebe9]"; }
|
||||
.compactLink { align-items: center; min-height: 72px; padding: 12px 8px; }
|
||||
.compactContent { flex: 1 1 auto; }
|
||||
.recurringIcon { color: "[theme:themePrimary, default:#0078d4]"; flex: 0 0 30px; font-size: 20px; text-align: center; }
|
||||
.cardLink, .compactLink { align-items: center; color: "[theme:bodyText, default:#323130]"; display: flex; min-height: 72px; padding: 10px 8px; text-decoration: none; }
|
||||
.cardLink:hover, .compactLink:hover { background: "[theme:neutralLighterAlt, default:#faf9f8]"; color: "[theme:bodyText, default:#323130]"; }
|
||||
.cardLink:focus, .compactLink:focus { outline: 2px solid "[theme:focusBorder, default:#605e5c]"; outline-offset: -2px; }
|
||||
.cardContent, .compactContent { display: block; flex: 1 1 auto; min-width: 0; }
|
||||
.dateBadge { color: "[theme:themePrimary, default:#0078d4]"; display: flex; flex: 0 0 48px; flex-direction: column; margin-right: 12px; text-align: center; }
|
||||
.month { font-size: 11px; font-weight: 600; line-height: 16px; text-transform: uppercase; }
|
||||
.day { color: "[theme:bodyText, default:#323130]"; font-size: 22px; font-weight: 300; line-height: 28px; }
|
||||
.title { display: block; font-size: 14px; font-weight: 600; line-height: 20px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.dateLine, .location { color: "[theme:neutralSecondary, default:#605e5c]"; display: block; font-size: 12px; line-height: 17px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.recurringIcon { color: "[theme:themePrimary, default:#0078d4]"; flex: 0 0 28px; font-size: 18px; text-align: center; }
|
||||
.status { color: "[theme:bodyText, default:#323130]"; padding: 16px; }
|
||||
.error { color: "[theme:errorText, default:#a80000]"; padding: 16px; }
|
||||
|
||||
@media (max-width: 900px) { .card { flex-basis: calc((100% - 16px) / 2); } }
|
||||
@media (max-width: 560px) { .card { flex-basis: 88%; } .heading { font-size: 21px; margin-bottom: 14px; } }
|
||||
@media (max-width: 560px) {
|
||||
.card { flex-basis: 88%; }
|
||||
.heading { font-size: 21px; }
|
||||
}
|
||||
|
||||
@@ -7,8 +7,16 @@ export default class RecurringEvents extends React.Component<IRecurringEventsPro
|
||||
public render(): React.ReactElement<IRecurringEventsProps> {
|
||||
return (
|
||||
<section className={styles.recurringEvents} aria-label={this.props.title}>
|
||||
<div className={styles.commandBar}>
|
||||
<a className={styles.commandLink} href={this.props.newEventUrl}>
|
||||
<span className={styles.addIcon} aria-hidden='true'>+</span>{this.props.newEventLabel}
|
||||
</a>
|
||||
<a className={styles.commandLink} href={this.props.seeAllUrl}>{this.props.seeAllLabel}</a>
|
||||
</div>
|
||||
{this.props.title && <h2 className={styles.heading}>{this.props.title}</h2>}
|
||||
{this.props.layout === 'compact' ? this.renderCompact() : this.renderFilmstrip()}
|
||||
{!this.props.events.length
|
||||
? <div className={styles.empty} role='status'>{this.props.emptyMessage}</div>
|
||||
: (this.props.layout === 'compact' ? this.renderCompact() : this.renderFilmstrip())}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -19,19 +27,14 @@ export default class RecurringEvents extends React.Component<IRecurringEventsPro
|
||||
{this.props.events.map((event: IRecurringEvent): JSX.Element => (
|
||||
<li className={styles.card} key={event.key}>
|
||||
<a className={styles.cardLink} href={event.detailsUrl} aria-label={this.eventLabel(event)}>
|
||||
<span className={event.imageUrl ? styles.image : styles.imageFallback}
|
||||
style={event.imageUrl ? { backgroundImage: 'url("' + this.cssUrl(event.imageUrl) + '")' } : {}}>
|
||||
{!event.imageUrl && <span className={styles.calendarIcon} aria-hidden='true'>▦</span>}
|
||||
</span>
|
||||
<span className={styles.cardBody}>
|
||||
{this.renderDateBadge(event)}
|
||||
<span className={styles.cardContent}>
|
||||
<span className={styles.title}>{event.title}</span>
|
||||
<span className={styles.dateLine}>{this.formatDateLine(event)}</span>
|
||||
{event.location && <span className={styles.location}>{event.location}</span>}
|
||||
{event.recurring && <span className={styles.recurring}>{this.props.recurringLabel}</span>}
|
||||
</span>
|
||||
{this.renderDateBadge(event)}
|
||||
<span className={styles.cardContent}>
|
||||
<span className={styles.title}>{event.title}</span>
|
||||
<span className={styles.dateLine}>{this.formatDateLine(event)}</span>
|
||||
{event.location && <span className={styles.location}>{event.location}</span>}
|
||||
</span>
|
||||
{event.recurring && <span className={styles.recurringIcon}
|
||||
title={this.props.recurringLabel} aria-hidden='true'>↻</span>}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
@@ -83,5 +86,4 @@ export default class RecurringEvents extends React.Component<IRecurringEventsPro
|
||||
return parts.join('. ');
|
||||
}
|
||||
|
||||
private cssUrl(value: string): string { return String(value || '').replace(/["'()\\\r\n]/g, ''); }
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ define([], function() {
|
||||
LoadErrorMessage: 'Die Ereignisse konnten nicht geladen werden.',
|
||||
DefaultTitle: 'Ereignisse',
|
||||
AllDayLabel: 'Ganztägig',
|
||||
RecurringLabel: 'Serientermin'
|
||||
RecurringLabel: 'Serientermin',
|
||||
NewEventLabel: 'Neues Ereignis',
|
||||
SeeAllLabel: 'Alle anzeigen'
|
||||
};
|
||||
});
|
||||
|
||||
@@ -24,6 +24,8 @@ define([], function() {
|
||||
LoadErrorMessage: 'The events could not be loaded.',
|
||||
DefaultTitle: 'Events',
|
||||
AllDayLabel: 'All day',
|
||||
RecurringLabel: 'Recurring event'
|
||||
RecurringLabel: 'Recurring event',
|
||||
NewEventLabel: 'New event',
|
||||
SeeAllLabel: 'See all'
|
||||
};
|
||||
});
|
||||
|
||||
@@ -24,6 +24,8 @@ declare interface IRecurringEventsWebPartStrings {
|
||||
DefaultTitle: string;
|
||||
AllDayLabel: string;
|
||||
RecurringLabel: string;
|
||||
NewEventLabel: string;
|
||||
SeeAllLabel: string;
|
||||
}
|
||||
declare module 'RecurringEventsWebPartStrings' {
|
||||
const strings: IRecurringEventsWebPartStrings;
|
||||
|
||||
@@ -34,6 +34,10 @@ assert(webPart.indexOf("PropertyPaneDropdown('category'") >= 0 && webPart.indexO
|
||||
'Event filtering controls are missing.');
|
||||
assert(component.indexOf("role='list'") >= 0 && component.indexOf('aria-label={this.eventLabel(event)}') >= 0,
|
||||
'Accessible event semantics are missing.');
|
||||
assert(component.indexOf('newEventUrl') >= 0 && component.indexOf('seeAllUrl') >= 0,
|
||||
'New Event and See All commands are missing.');
|
||||
assert(webPart.indexOf('list.defaultNewFormUrl') >= 0 && webPart.indexOf('list.defaultViewUrl') >= 0,
|
||||
'Event-list command URLs are missing.');
|
||||
['bodyText', 'white', 'neutralLight', 'themePrimary', 'themeDark', 'focusBorder', 'errorText'].forEach(function(slot) {
|
||||
assert(styles.indexOf('[theme:' + slot + ', default:') >= 0, 'SharePoint theme slot is missing: ' + slot);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user