mirror of https://github.com/nextcloud/calendar
Merge pull request #5891 from nextcloud/feat/calendar-widget-private-calendar
Feat: calendar widget for private calendars
This commit is contained in:
commit
97fce2ac52
|
@ -73,24 +73,55 @@ class ReferenceProvider extends ADiscoverableReferenceProvider {
|
||||||
public function matchReference(string $referenceText): bool {
|
public function matchReference(string $referenceText): bool {
|
||||||
$start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID);
|
$start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID);
|
||||||
$startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/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 {
|
public function resolveReference(string $referenceText): ?IReference {
|
||||||
if ($this->matchReference($referenceText)) {
|
if ($this->matchReference($referenceText)) {
|
||||||
$token = $this->getCalendarTokenFromLink($referenceText);
|
$type = $this->getType($referenceText);
|
||||||
|
|
||||||
$reference = new Reference($referenceText);
|
$reference = new Reference($referenceText);
|
||||||
$reference->setTitle('calendar');
|
$reference->setTitle('calendar');
|
||||||
$reference->setDescription($token);
|
$reference->setDescription('calendar widget');
|
||||||
$reference->setRichObject(
|
|
||||||
'calendar_widget',
|
switch ($type) {
|
||||||
[
|
case 'public':
|
||||||
'title' => 'calendar',
|
$token = $this->getCalendarTokenFromLink($referenceText);
|
||||||
'token' => $token,
|
$url = $this->getUrlFromLink($token, 'public');
|
||||||
'url' => $referenceText,]
|
$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;
|
return $reference;
|
||||||
}
|
}
|
||||||
|
@ -99,14 +130,31 @@ class ReferenceProvider extends ADiscoverableReferenceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getCalendarTokenFromLink(string $url): ?string {
|
private function getCalendarTokenFromLink(string $url): ?string {
|
||||||
|
|
||||||
|
|
||||||
if (preg_match('/\/p\/([a-zA-Z0-9]+)/', $url, $output_array)) {
|
if (preg_match('/\/p\/([a-zA-Z0-9]+)/', $url, $output_array)) {
|
||||||
return $output_array[1];
|
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 {
|
public function getCachePrefix(string $referenceId): string {
|
||||||
return '';
|
return '';
|
||||||
|
|
|
@ -22,9 +22,10 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FullCalendar ref="fullCalendar"
|
<FullCalendar v-if="calendarOptions"
|
||||||
|
ref="fullCalendar"
|
||||||
:class="isWidget? 'fullcalendar-widget': ''"
|
:class="isWidget? 'fullcalendar-widget': ''"
|
||||||
:options="options" />
|
:options="calendarOptions" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -77,6 +78,10 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Whether or not the user is authenticated
|
* Whether or not the user is authenticated
|
||||||
*/
|
*/
|
||||||
|
@ -89,6 +94,7 @@ export default {
|
||||||
return {
|
return {
|
||||||
updateTodayJob: null,
|
updateTodayJob: null,
|
||||||
updateTodayJobPreviousDate: null,
|
updateTodayJobPreviousDate: null,
|
||||||
|
calendarOptions: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -154,6 +160,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
eventSources() {
|
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))
|
return this.$store.getters.enabledCalendars.map(eventSource(this.$store))
|
||||||
},
|
},
|
||||||
widgetView() {
|
widgetView() {
|
||||||
|
@ -194,6 +207,22 @@ export default {
|
||||||
const calendarApi = this.$refs.fullCalendar.getApi()
|
const calendarApi = this.$refs.fullCalendar.getApi()
|
||||||
calendarApi.gotoDate(getYYYYMMDDFromFirstdayParam(newDate))
|
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() {
|
modificationCount: debounce(function() {
|
||||||
const calendarApi = this.$refs.fullCalendar.getApi()
|
const calendarApi = this.$refs.fullCalendar.getApi()
|
||||||
calendarApi.refetchEvents()
|
calendarApi.refetchEvents()
|
||||||
|
@ -213,7 +242,7 @@ export default {
|
||||||
* we have to register a resize-observer here, that will automatically
|
* we have to register a resize-observer here, that will automatically
|
||||||
* update the fullCalendar size, when the available space changes.
|
* update the fullCalendar size, when the available space changes.
|
||||||
*/
|
*/
|
||||||
mounted() {
|
mounted() {
|
||||||
if (window.ResizeObserver) {
|
if (window.ResizeObserver) {
|
||||||
const resizeObserver = new ResizeObserver(debounce(() => {
|
const resizeObserver = new ResizeObserver(debounce(() => {
|
||||||
this.$refs.fullCalendar
|
this.$refs.fullCalendar
|
||||||
|
@ -225,6 +254,8 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
|
this.calendarOptions = await this.options
|
||||||
|
|
||||||
this.updateTodayJob = setInterval(() => {
|
this.updateTodayJob = setInterval(() => {
|
||||||
const newDate = getYYYYMMDDFromFirstdayParam('now')
|
const newDate = getYYYYMMDDFromFirstdayParam('now')
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,9 @@ registerWidget('calendar_widget', async (el, { richObjectType, richObject, acces
|
||||||
store,
|
store,
|
||||||
propsData: {
|
propsData: {
|
||||||
isWidget: true,
|
isWidget: true,
|
||||||
referenceToken: richObject.token,
|
isPublic: richObject.isPublic,
|
||||||
|
referenceToken: richObject?.token,
|
||||||
|
url: richObject.url,
|
||||||
},
|
},
|
||||||
}).$mount(el)
|
}).$mount(el)
|
||||||
return new NcCustomPickerRenderResult(vueElement.$el, vueElement)
|
return new NcCustomPickerRenderResult(vueElement.$el, vueElement)
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
<CalendarGrid v-if="!showEmptyCalendarScreen"
|
<CalendarGrid v-if="!showEmptyCalendarScreen"
|
||||||
ref="calendarGridWidget"
|
ref="calendarGridWidget"
|
||||||
:is-widget="isWidget"
|
:is-widget="isWidget"
|
||||||
|
:url="url"
|
||||||
:is-authenticated-user="isAuthenticatedUser" />
|
:is-authenticated-user="isAuthenticatedUser" />
|
||||||
<EmptyCalendar v-else />
|
<EmptyCalendar v-else />
|
||||||
|
|
||||||
|
@ -139,14 +140,26 @@ export default {
|
||||||
EditSimple,
|
EditSimple,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
// Is the calendar in a widget ?
|
||||||
isWidget: {
|
isWidget: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
// The reference token for the widget for public share calendars
|
||||||
referenceToken: {
|
referenceToken: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
// Is public share ?
|
||||||
|
isPublic: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
// Url of private calendar
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -263,7 +276,7 @@ export default {
|
||||||
})
|
})
|
||||||
this.$store.dispatch('initializeCalendarJsConfig')
|
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()
|
await initializeClientForPublicView()
|
||||||
const tokens = this.isWidget ? [this.referenceToken] : this.$route.params.tokens.split('-')
|
const tokens = this.isWidget ? [this.referenceToken] : this.$route.params.tokens.split('-')
|
||||||
const calendars = await this.$store.dispatch('getPublicCalendars', { tokens })
|
const calendars = await this.$store.dispatch('getPublicCalendars', { tokens })
|
||||||
|
|
Loading…
Reference in New Issue