Merge pull request #5891 from nextcloud/feat/calendar-widget-private-calendar

Feat: calendar widget for private calendars
This commit is contained in:
Hamza 2024-04-17 11:12:38 +02:00 committed by GitHub
commit 97fce2ac52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 113 additions and 19 deletions

View File

@ -73,24 +73,55 @@ class ReferenceProvider extends ADiscoverableReferenceProvider {
public function matchReference(string $referenceText): bool {
$start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID);
$startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . Application::APP_ID);
if (preg_match('/^' . preg_quote($start, '/') . '\/p\/[a-zA-Z0-9]+$/i', $referenceText) === 1 || preg_match('/^' . preg_quote($startIndex, '/') . '\/p\/[a-zA-Z0-9]+$/i', $referenceText) === 1) {
return true;
}
$start = $this->urlGenerator->getAbsoluteURL('/remote.php/dav/calendars');
if (preg_match('/^' . preg_quote($start, '/') . '\/[a-zA-Z0-9-]+\/[a-zA-Z0-9-]+\/$/i', $referenceText) === 1) {
return true;
}
return preg_match('/^' . preg_quote($start, '/') . '\/p\/[a-zA-Z0-9]+$/i', $referenceText) === 1 || preg_match('/^' . preg_quote($startIndex, '/') . '\/p\/[a-zA-Z0-9]+$/i', $referenceText) === 1;
return false;
}
public function resolveReference(string $referenceText): ?IReference {
if ($this->matchReference($referenceText)) {
$token = $this->getCalendarTokenFromLink($referenceText);
$type = $this->getType($referenceText);
$reference = new Reference($referenceText);
$reference->setTitle('calendar');
$reference->setDescription($token);
$reference->setRichObject(
'calendar_widget',
[
'title' => 'calendar',
'token' => $token,
'url' => $referenceText,]
);
$reference->setDescription('calendar widget');
switch ($type) {
case 'public':
$token = $this->getCalendarTokenFromLink($referenceText);
$url = $this->getUrlFromLink($token, 'public');
$reference->setRichObject(
'calendar_widget',
[
'title' => 'calendar',
'token' => $token,
'isPublic' => true,
'url' => $url,
]
);
break;
case 'private':
$url = $this->getUrlFromLink($referenceText, 'private');
$reference->setRichObject(
'calendar_widget',
[
'title' => 'calendar',
'isPublic' => false,
'url' => $url,
]
);
break;
default:
return null;
}
return $reference;
}
@ -99,14 +130,31 @@ class ReferenceProvider extends ADiscoverableReferenceProvider {
}
private function getCalendarTokenFromLink(string $url): ?string {
if (preg_match('/\/p\/([a-zA-Z0-9]+)/', $url, $output_array)) {
return $output_array[1];
}
return $url;
return null;
}
private function getUrlFromLink(string $data, string $type): ?string {
if ($type === 'public') {
return "{$this->urlGenerator->getWebroot()}/remote.php/dav/public-calendars/{$data}/";
} elseif ($type === 'private' && preg_match('/\/remote.php\/dav\/calendars\/([a-zA-Z0-9-]+)\/([a-zA-Z0-9-]+)\//', $data, $output_array)) {
return $this->urlGenerator->getWebroot().$output_array[0];
}
return null;
}
private function getType(string $url): string {
if (preg_match('/\/p\/([a-zA-Z0-9]+)/', $url) === 1) {
return 'public';
}
if (preg_match('/\/dav\/calendars\/([^\/]+)\/([^\/]+)/', $url) === 1) {
return 'private';
}
return 'unknown';
}
public function getCachePrefix(string $referenceId): string {
return '';

View File

@ -22,9 +22,10 @@
-->
<template>
<FullCalendar ref="fullCalendar"
<FullCalendar v-if="calendarOptions"
ref="fullCalendar"
:class="isWidget? 'fullcalendar-widget': ''"
:options="options" />
:options="calendarOptions" />
</template>
<script>
@ -77,6 +78,10 @@ export default {
type: Boolean,
default: false,
},
url: {
type: String,
required: false,
},
/**
* Whether or not the user is authenticated
*/
@ -89,6 +94,7 @@ export default {
return {
updateTodayJob: null,
updateTodayJobPreviousDate: null,
calendarOptions: null,
}
},
computed: {
@ -154,6 +160,13 @@ export default {
}
},
eventSources() {
if (this.isWidget) {
const calendar = this.$store.getters.getCalendarByUrl(this.url)
if (!calendar) {
return []
}
return [calendar].map(eventSource(this.$store))
}
return this.$store.getters.enabledCalendars.map(eventSource(this.$store))
},
widgetView() {
@ -194,6 +207,22 @@ export default {
const calendarApi = this.$refs.fullCalendar.getApi()
calendarApi.gotoDate(getYYYYMMDDFromFirstdayParam(newDate))
},
eventSources(sources, oldSources) {
const newSources = sources.filter(source => !oldSources.map(oldSource => oldSource.id).includes(source.id))
const removedSources = oldSources.filter(oldSource => !sources.map(source => source.id).includes(oldSource.id))
// Hackity hack! Unfortunately, calendarOptions.eventSources is not reactive ...
// Ref https://fullcalendar.io/docs/Calendar-addEventSource
// TODO: Find a better/safer way to prevent duplicated event sources
const calendarApi = this.$refs.fullCalendar.getApi()
for (const source of newSources) {
calendarApi.addEventSource(source)
}
const eventSources = calendarApi.getEventSources()
for (const source of removedSources) {
eventSources.find(x => x.id === source.id)?.remove()
}
},
modificationCount: debounce(function() {
const calendarApi = this.$refs.fullCalendar.getApi()
calendarApi.refetchEvents()
@ -213,7 +242,7 @@ export default {
* we have to register a resize-observer here, that will automatically
* update the fullCalendar size, when the available space changes.
*/
mounted() {
mounted() {
if (window.ResizeObserver) {
const resizeObserver = new ResizeObserver(debounce(() => {
this.$refs.fullCalendar
@ -225,6 +254,8 @@ export default {
}
},
async created() {
this.calendarOptions = await this.options
this.updateTodayJob = setInterval(() => {
const newDate = getYYYYMMDDFromFirstdayParam('now')

View File

@ -20,7 +20,9 @@ registerWidget('calendar_widget', async (el, { richObjectType, richObject, acces
store,
propsData: {
isWidget: true,
referenceToken: richObject.token,
isPublic: richObject.isPublic,
referenceToken: richObject?.token,
url: richObject.url,
},
}).$mount(el)
return new NcCustomPickerRenderResult(vueElement.$el, vueElement)

View File

@ -27,6 +27,7 @@
<CalendarGrid v-if="!showEmptyCalendarScreen"
ref="calendarGridWidget"
:is-widget="isWidget"
:url="url"
:is-authenticated-user="isAuthenticatedUser" />
<EmptyCalendar v-else />
@ -139,14 +140,26 @@ export default {
EditSimple,
},
props: {
// Is the calendar in a widget ?
isWidget: {
type: Boolean,
default: false,
},
// The reference token for the widget for public share calendars
referenceToken: {
type: String,
required: false,
},
// Is public share ?
isPublic: {
type: Boolean,
required: false,
},
// Url of private calendar
url: {
type: String,
required: false,
},
},
data() {
return {
@ -263,7 +276,7 @@ export default {
})
this.$store.dispatch('initializeCalendarJsConfig')
if (this.$route?.name.startsWith('Public') || this.$route?.name.startsWith('Embed') || this.isWidget) {
if (this.$route?.name.startsWith('Public') || this.$route?.name.startsWith('Embed') || this.isPublic) {
await initializeClientForPublicView()
const tokens = this.isWidget ? [this.referenceToken] : this.$route.params.tokens.split('-')
const calendars = await this.$store.dispatch('getPublicCalendars', { tokens })