diff --git a/README.md b/README.md index d941b06..c691cdf 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/config/package-solution.json b/config/package-solution.json index bc5aa56..f0532bd 100644 --- a/config/package-solution.json +++ b/config/package-solution.json @@ -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 }, diff --git a/package.json b/package.json index bf44054..1d546f8 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/webparts/recurringEvents/RecurringEventsDataService.ts b/src/webparts/recurringEvents/RecurringEventsDataService.ts index e11bc0d..57419b3 100644 --- a/src/webparts/recurringEvents/RecurringEventsDataService.ts +++ b/src/webparts/recurringEvents/RecurringEventsDataService.ts @@ -13,12 +13,15 @@ export class RecurringEventsDataService { public getLists(): Promise { 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 || '') }))); } diff --git a/src/webparts/recurringEvents/RecurringEventsTypes.ts b/src/webparts/recurringEvents/RecurringEventsTypes.ts index a7f895d..79ad851 100644 --- a/src/webparts/recurringEvents/RecurringEventsTypes.ts +++ b/src/webparts/recurringEvents/RecurringEventsTypes.ts @@ -4,6 +4,8 @@ export interface IEventsListOption { id: string; title: string; serverRelativeUrl: string; + defaultViewUrl: string; + defaultNewFormUrl: string; } export interface IRecurringEvent { diff --git a/src/webparts/recurringEvents/RecurringEventsWebPart.ts b/src/webparts/recurringEvents/RecurringEventsWebPart.ts index 4225215..b492cc7 100644 --- a/src/webparts/recurringEvents/RecurringEventsWebPart.ts +++ b/src/webparts/recurringEvents/RecurringEventsWebPart.ts @@ -50,14 +50,19 @@ export default class RecurringEventsWebPart extends BaseClientSideWebPart { 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 = 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 => { diff --git a/src/webparts/recurringEvents/components/IRecurringEventsProps.ts b/src/webparts/recurringEvents/components/IRecurringEventsProps.ts index 7d3212f..35597c7 100644 --- a/src/webparts/recurringEvents/components/IRecurringEventsProps.ts +++ b/src/webparts/recurringEvents/components/IRecurringEventsProps.ts @@ -7,4 +7,9 @@ export interface IRecurringEventsProps { locale: string; allDayLabel: string; recurringLabel: string; + emptyMessage: string; + newEventLabel: string; + newEventUrl: string; + seeAllLabel: string; + seeAllUrl: string; } diff --git a/src/webparts/recurringEvents/components/RecurringEvents.module.scss b/src/webparts/recurringEvents/components/RecurringEvents.module.scss index d94e3a8..e053cc0 100644 --- a/src/webparts/recurringEvents/components/RecurringEvents.module.scss +++ b/src/webparts/recurringEvents/components/RecurringEvents.module.scss @@ -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; } +} diff --git a/src/webparts/recurringEvents/components/RecurringEvents.tsx b/src/webparts/recurringEvents/components/RecurringEvents.tsx index b11275f..be56d6c 100644 --- a/src/webparts/recurringEvents/components/RecurringEvents.tsx +++ b/src/webparts/recurringEvents/components/RecurringEvents.tsx @@ -7,8 +7,16 @@ export default class RecurringEvents extends React.Component { return (
+ {this.props.title &&

{this.props.title}

} - {this.props.layout === 'compact' ? this.renderCompact() : this.renderFilmstrip()} + {!this.props.events.length + ?
{this.props.emptyMessage}
+ : (this.props.layout === 'compact' ? this.renderCompact() : this.renderFilmstrip())}
); } @@ -19,19 +27,14 @@ export default class RecurringEvents extends React.Component (
  • - - {!event.imageUrl && } - - - {this.renderDateBadge(event)} - - {event.title} - {this.formatDateLine(event)} - {event.location && {event.location}} - {event.recurring && {this.props.recurringLabel}} - + {this.renderDateBadge(event)} + + {event.title} + {this.formatDateLine(event)} + {event.location && {event.location}} + {event.recurring && }
  • ))} @@ -83,5 +86,4 @@ export default class RecurringEvents extends React.Component= 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); });