Änderungen von Dokument Calendar Macro

Zuletzt geändert von xwikiadmin am 2025/12/11 06:31

Von Version 2.1
bearbeitet von xwikiadmin
am 2023/04/25 10:51
Änderungskommentar: Install extension [com.xwiki.mocca-calendar:application-mocca-calendar-ui/2.12]
Auf Version 9.1
bearbeitet von xwikiadmin
am 2025/12/11 06:31
Änderungskommentar: Migrated property [executionIsolated] from class [XWiki.WikiMacroClass]

Zusammenfassung

Details

XWiki.JavaScriptExtension[0]
Code
... ... @@ -1,5 +1,11 @@
1 -define('moccaCalendar', ['jquery', 'fullcalendar', 'moment'], function(jQuery, fullCalendar, moment) {
1 +require.config({
2 + paths: {
3 + purify: "$services.webjars.url('org.webjars.npm:dompurify', 'dist/purify.js')"
4 + },
5 +});
2 2  
7 +define('moccaCalendar', ['jquery', 'fullcalendar', 'moment', 'purify'], function(jQuery, fullCalendar, moment, purify) {
8 +
3 3  // Make sure the XWiki 'namespace' and the ModalPopup class exist.
4 4  if (typeof(XWiki) == "undefined" || typeof(XWiki.widgets) == "undefined" || typeof(XWiki.widgets.ModalPopup) == "undefined") {
5 5   if (typeof console != "undefined" && typeof console.warn == "function") {
... ... @@ -10,8 +10,6 @@
10 10   XWiki.MoccaCalendar = {};
11 11   }
12 12  
13 -#template('colorThemeInit.vm')
14 -
15 15   XWiki.MoccaCalendar.Helper = Class.create({
16 16   initialize: function(calendar, dateFormat, jsonServiceUrl, createEventBaseUrl, updateEventUrl, newPageNameUrl, dateCheckUrl, deleteEventInstanceUrl, newPageParams, formToken) {
17 17   this.calendar = calendar;
... ... @@ -34,6 +34,9 @@
34 34   this.timeFormat = '';
35 35   this.dayFormat = dateFormat;
36 36   }
41 + document.addEventListener('calendarImportCompleted', () => {
42 + calendar.fullCalendar('refetchEvents');
43 + });
37 37   },
38 38   displayError: function(errorMessage) {
39 39   if (!errorMessage) {
... ... @@ -121,6 +121,12 @@
121 121   document.cookie = cookieName + value + "; path=/";
122 122   };
123 123  
131 + function removeColorPickers() {
132 + jQuery('#mocca-event-edit-form .color-picker').each(function(index) {
133 + jQuery('#' + jQuery(this).data('colpickId')).remove();
134 + });
135 + }
136 +
124 124   //
125 125   // the dialog to show / edit events
126 126   //
... ... @@ -136,7 +136,7 @@
136 136   // call constructor from ModalPopup with params content, shortcuts, options
137 137   $super(
138 138   // this element will end up as 'this.content'
139 - new Element('form', {'class' : 'xform'}),
152 + new Element('form', {'class' : 'xform', 'id' : 'mocca-event-edit-form'}),
140 140   {
141 141   "show": {method: this.showDialog, keys: []},
142 142   "close": {method: this.closeDialog, keys: ['Esc']}
... ... @@ -143,10 +143,10 @@
143 143   },
144 144   {
145 145   verticalPosition: "top",
146 - backgroundColor: "$theme.pageHeaderBackgroundColor",
147 147   title : this.interactionParameters.editMode ? "$escapetool.javascript($services.localization.render('MoccaCalendar.calendarevent.create'))" : "$escapetool.javascript($services.localization.render('MoccaCalendar.calendarevent.view'))",
148 148   removeOnClose : true,
149 149   onClose : function() {
162 + removeColorPickers();
150 150   document.stopObserving('xwiki:moccacalendar:editmode');
151 151   document.stopObserving('xwiki:moccacalendar:instancesaved');
152 152   }
... ... @@ -187,6 +187,7 @@
187 187   // and we only listen for the results to close the dialog
188 188   document.observe('xwiki:moccacalendar:instancesaved', function(event) {
189 189   this.savedBox.show();
203 + removeColorPickers();
190 190   this.closeDialog();
191 191   this.helper.calendar.fullCalendar('refetchEvents');
192 192   }.bind(this));
... ... @@ -282,12 +282,32 @@
282 282   } else {
283 283   saveUrl = this.interactionParameters.event.saveUrl;
284 284   }
299 + this.content.title.value = purify.sanitize(this.content.title.value)
285 285   this.content.writeAttribute('action', saveUrl + '&xpage=plain&ajax=true');
301 + let formData = new FormData(this.content);
302 + if (formData.get('MoccaCalendar.MoccaCalendarEventClass_0_allDay') == 1) {
303 + this.content.querySelectorAll('input.datetime').forEach(dateInput => {
304 + // Convert the date fields from the displayed format to the one accepted by the backend.
305 + const dateObj = moment(dateInput.value, moment().toMomentFormatString(dateInput.getAttribute('data-format')));
306 + if (dateInput.id == "MoccaCalendar.MoccaCalendarEventClass_0_endDate") {
307 + // For the end date, set the hour to 23:59 instead of 00:00.
308 + dateObj.hours(23);
309 + dateObj.minutes(59);
310 + }
311 + const calendarEventObjectDateFormat = moment().toMomentFormatString(this.helper.dateFormat);
312 + // Add a hidden element to the DOM to avoid flickering.
313 + const tempElement = dateInput.clone();
314 + tempElement.addClassName('hidden');
315 + tempElement.value = dateObj.isValid() ? dateObj.format(calendarEventObjectDateFormat) : "";
316 + dateInput.parentElement.insertBefore(tempElement, dateInput);
317 + });
318 + }
286 286   this.content.request({
287 287   onSuccess: function() {
288 288   this.saving = false;
289 289   this.savingBox.hide();
290 290   this.savedBox.show();
324 + removeColorPickers();
291 291   this.closeDialog();
292 292   this.helper.calendar.fullCalendar('refetchEvents');
293 293   }.bind(this),
... ... @@ -384,18 +384,20 @@
384 384   var buttons = new Element('div', {'class':'buttons'});
385 385   var oldSkin = ! $('body').hasClassName('skin-flamingo'); // FIXME: bad b/w compat hack
386 386   formcontent.insert(buttons);
421 + // When you are in a read only calendar you only need the close button for the modal and you MUST Not fire
422 + // the xwiki:moccacalendar:editloaded event because you will get a bunch of errors.
387 387   if (this.interactionParameters.editMode) {
388 388   var that = this;
389 - buttons.insert(this.createButton("submit","$escapetool.javascript($services.localization.render('save'))","","save-moccacalendar-event","btn"));
390 - buttons.down('#save-moccacalendar-event').observe('click', this.submitForm.bind(this));
425 + if(this.helper.createEventBaseUrl.indexOf('disabled=disabled') === -1) {
426 + buttons.insert(this.createButton("submit","$escapetool.javascript($services.localization.render('save'))","","save-moccacalendar-event","btn"));
427 + buttons.down('#save-moccacalendar-event').observe('click', this.submitForm.bind(this));
428 + // MOCCACAL-128: the event template has a space in the title to avoid showing the page name, but we want to remove this on load
429 + var titleField = formcontent.down('input[name="title"]')
430 + if (titleField && titleField.getAttribute('value').trim() == '') { titleField.setAttribute('value','') }
431 + document.fire('xwiki:moccacalendar:editloaded');
432 + }
391 391   buttons.insert('<span class="buttonwrapper"><a href="#" id="cancel-moccacalendar-event" class="secondary button">' + "$escapetool.javascript($services.localization.render('cancel'))" + '</a></span>');
392 392   buttons.down('#cancel-moccacalendar-event').observe('click', this.closeDialog.bind(this));
393 -
394 - // MOCCACAL-128: the event template has a space in the title to avoid showing the page name, but we want to remove this on load
395 - var titleField = formcontent.down('input[name="title"]')
396 - if (titleField && titleField.getAttribute('value').trim() == '') { titleField.setAttribute('value','') }
397 -
398 - document.fire('xwiki:moccacalendar:editloaded');
399 399   $("MoccaCalendarEvent.MoccaCalendarEventClass_0_title").focus();
400 400   } else {
401 401   var leftGroup = new Element('div', {'class':'btn-group btn-group-left'});
... ... @@ -410,13 +410,19 @@
410 410   }
411 411   // FIXME: this should be a plain link, but then it has no styles
412 412   if (oldSkin) {
413 - leftGroup.insert(this.createButton("button","$escapetool.javascript($services.localization.render('view'))","","view-moccacalendar-event","btn"));
449 + if (this.interactionParameters.event.readOnly != true) {
450 + leftGroup.insert(this.createButton("button","$escapetool.javascript($services.localization.render('view'))","","view-moccacalendar-event","btn"));
451 + }
414 414   leftGroup.insert('<span class="buttonwrapper"><a href="#" id="cancel-moccacalendar-event" class="secondary button">' + "$escapetool.javascript($services.localization.render('cancel'))" + '</a></span>');
415 415   } else {
416 - leftGroup.insert(' <button class="button btn btn-default" id="view-moccacalendar-event"><span class="glyphicon glyphicon-file"></span> '+"$escapetool.javascript($services.localization.render('view'))"+'</button>');
454 + if (this.interactionParameters.event.readOnly != true) {
455 + leftGroup.insert(' <button class="button btn btn-default" id="view-moccacalendar-event"><span class="glyphicon glyphicon-file"></span> '+"$escapetool.javascript($services.localization.render('view'))"+'</button>');
456 + }
417 417   leftGroup.insert('<button class="button btn btn-default" id="cancel-moccacalendar-event"><span class="glyphicon glyphicon-remove"></span> '+"$escapetool.javascript($services.localization.render('cancel'))"+'</button>');
418 418   }
419 - leftGroup.down("#view-moccacalendar-event").observe('click', function(e) { e.stop(); window.location.href = this.interactionParameters.event.viewUrl; }.bind(this));
459 + if (this.interactionParameters.event.readOnly != true) {
460 + leftGroup.down("#view-moccacalendar-event").observe('click', function(e) { e.stop(); window.location.href = this.interactionParameters.event.viewUrl; }.bind(this));
461 + }
420 420   leftGroup.down('#cancel-moccacalendar-event').observe('click', this.closeDialog.bind(this));
421 421   if (this.interactionParameters.event.canDelete) {
422 422   var rightGroup = new Element('div', {'class':'btn-group btn-group-right'});
... ... @@ -456,6 +456,9 @@
456 456   script.remove();
457 457   });
458 458   content.insert('<div id="formcontent"><div id="forminnercontent">' + container.innerHTML + '</div></div>');
501 +
502 + var parentModalPopup = jQuery('.xdialog-box-moccacal-modal-popup').toArray();
503 + document.fire('xwiki:dom:updated', {elements: parentModalPopup});
459 459   },
460 460   handleDatetimeFieldChange : function(event, element) {
461 461   if (this.saving) { return; }
... ... @@ -716,10 +716,22 @@
716 716  
717 717  });
718 718  
764 +define('mocca-calendar-notification', {
765 + prefix: 'MoccaCalendar.notification.',
766 + keys: [
767 + 'import.inprogress',
768 + 'import.done',
769 + 'import.error',
770 + 'import.filetoolarge',
771 + 'addObject.error'
772 + ]
773 +});
774 +
719 719  /**
720 - * Deleting an event
776 + * Delete event and calendar import actions.
721 721   */
722 -require(['jquery'], function($) {
778 +require(['jquery', 'xwiki-meta', 'xwiki-job-runner', 'xwiki-l10n!mocca-calendar-notification'],
779 + function($, xwikiMeta, JobRunner, l10n) {
723 723   /**
724 724   * Events triggered before deleteEvents modal is shown: save the button that triggers
725 725   * the modal, close MoccaCalendarPopup, update the name of event on displayed text.
... ... @@ -754,4 +754,105 @@
754 754   var calendarPopup = relatedTarget.data('calendarPopup');
755 755   new XWiki.MoccaCalendar.MoccaCalendarPopup(calendarPopup.interactionParameters, calendarPopup.helper);
756 756   });
814 +
815 + $(document).on('click', '.add-calendar-object-container a', function(event) {
816 + event.preventDefault();
817 + const addObjectButton = $('#add-calendar-object');
818 + const target = addObjectButton.data('target');
819 + // To be adapted to the standard XWiki rest endpoint for object creation after
820 + // XWIKI-20704: NullPointerException (NPE) when accessing objects with ComputedField properties from REST is fixed.
821 + var documentReference = XWiki.Model.resolve('MoccaCalendar.Code.MoccaCalendarObjectCreator',
822 + XWiki.EntityType.DOCUMENT);
823 + var targetUrl = new XWiki.Document(documentReference).getURL();
824 + var params = {
825 + 'documentRef': target
826 + };
827 + $.ajax({
828 + url: targetUrl,
829 + type: 'POST',
830 + data: params,
831 + success: function (response) {
832 + window.location.reload();
833 + },
834 + error: function (xhr, status, error) {
835 + console.error('Failed to add the MoccaCalendarClass object', error);
836 + var notification = new XWiki.widgets.Notification(l10n.get('addObject.error'), 'error');
837 + }
838 + });
839 + });
840 +
841 + // Trigger the upload manually when the button is clicked.
842 + $(document).on('click', '.import-calendar-file-button', function(event) {
843 + event.preventDefault();
844 + var calId = $(event.target).data('calid');
845 + var form = $(`#import-calendar-file-${calId} form`).get(0);
846 + startUploading(form, calId);
847 + });
848 +
849 + // Start uploading this file by creating a new XHR object with the file data.
850 + var startUploading = function (form, calId) {
851 + var input = form.down(`#import-ical-file-input-${calId}`);
852 + var inputFile = input.files[0];
853 + if (inputFile && inputFile.size < input.dataset.maxFileSize) {
854 + var select = form.down(`#import-calendar-parent-${calId}`);
855 + var params = {};
856 + params[select.name] = select.value;
857 + let action = form.action + "?" + $.param(params);
858 + // Create XMLHttpRequest object and POST the data
859 + var request = new XMLHttpRequest();
860 + request.open('POST', action);
861 + request.onload = function () {
862 + if (request.status === 202 || request.status === 302) {
863 + checkImportJob(select.value);
864 + } else {
865 + new XWiki.widgets.Notification(l10n.get('import.error'),'error');
866 + }
867 + };
868 + request.send(inputFile);
869 + } else {
870 + var notification = new XWiki.widgets.Notification(l10n.get('import.filetoolarge'), 'error');
871 + }
872 + };
873 +
874 + const checkImportJob = function(selectedCalendar) {
875 + let documentReference = XWiki.Model.resolve('MoccaCalendar.Code.ImportJobResource', XWiki.EntityType.DOCUMENT);
876 + var targetURL = new XWiki.Document(documentReference).getURL('get');
877 + var targetJobId = ['moccacalendar', 'import', selectedCalendar];
878 + var data = [];
879 + data.push(
880 + {name: 'outputSyntax', value: 'plain'},
881 + {name: 'sheet', value: 'MoccaCalendar.Code.ImportJobResource'},
882 + {name: 'action', value: 'jobStatus'},
883 + {name: 'jobId', value: targetJobId.join('/')},
884 + {name: 'data', value: 'jobStatus'},
885 + {name: 'form_token', value: xwikiMeta.form_token}
886 + );
887 + var notification = new XWiki.widgets.Notification(l10n.get('import.inprogress'), 'inprogress');
888 + $('.import-calendar-file-button').prop('disabled', true);
889 + return Promise.resolve(new JobRunner({
890 + createStatusRequest: function(jobId) {
891 + return {
892 + url: targetURL,
893 + data: {
894 + outputSyntax: 'plain',
895 + sheet: 'MoccaCalendar.Code.ImportJobResource',
896 + data: 'jobStatus',
897 + jobId: jobId.join('/')
898 + }
899 + };
900 + }
901 + }).run(targetURL, data)).then((response) => {
902 + if (response.error !== null) {
903 + throw new Error(response.error.message);
904 + } else {
905 + document.dispatchEvent(new Event('calendarImportCompleted'));
906 + notification.replace(new XWiki.widgets.Notification(l10n.get('import.done'),'done'));
907 + }
908 + }).catch((reason) => {
909 + notification.replace(new XWiki.widgets.Notification(l10n.get('import.error'),'error'));
910 + return Promise.reject(reason);
911 + }).finally(() => {
912 + $('.import-calendar-file-button').prop('disabled', false);
913 + });
914 + };
757 757  });
XWiki.StyleSheetExtension[0]
Code
... ... @@ -26,7 +26,12 @@
26 26   margin-top: 0.6em;
27 27  }
28 28  .xdialog-box-moccacal-modal-popup {
29 + background-color: @modal-content-bg;
30 + border-color: @modal-content-border-color;
29 29   width: 600px;
32 + top: 3vh !important;
33 + bottom: 3vh !important;
34 + max-height: 94vh;
30 30  }
31 31  
32 32  /* Make sure the date time picker is shown on top of the event modal. */
... ... @@ -79,3 +79,56 @@
79 79  .skin-flamingo .gadget .fc h2 {
80 80   font-size: 140%;
81 81  }
87 +
88 +.import-form {
89 + padding: 2em;
90 +}
91 +
92 +.import-form label {
93 + margin-bottom: 0.8em;
94 + font-weight: bold;
95 +}
96 +
97 +.import-form select,
98 +.import-form input {
99 + width: 100%;
100 + margin-bottom: 1.6em;
101 + border-radius: 0.4em;
102 +}
103 +
104 +.import-form input {
105 + padding: 0.6em 1em;
106 + border: 1px solid #ccc;
107 +}
108 +
109 +#recurrentDays dd {
110 + display: flex;
111 + gap: 0.3rem;
112 + margin-top: 1rem;
113 + justify-content: space-between;
114 +}
115 +
116 +#recurrentDays .xwiki-form-listclass {
117 + display: inline-flex;
118 + align-items: center;
119 + justify-content: center;
120 + padding: 0.8rem 1.1rem;
121 + background-color: @btn-default-bg;
122 + color: @btn-default-color;
123 + border-radius: 6px;
124 + cursor: pointer;
125 + user-select: none;
126 + transition: all 0.2s ease;
127 + position: relative;
128 +}
129 +
130 +#recurrentDays .xwiki-form-listclass input[type="checkbox"] {
131 + position: absolute;
132 + opacity: 0;
133 + pointer-events: none;
134 +}
135 +
136 +#recurrentDays .xwiki-form-listclass:has(input[type="checkbox"]:checked) {
137 + background-color: @btn-primary-bg;
138 + color: @btn-primary-color;
139 +}
Inhalt parsen
... ... @@ -1,0 +1,1 @@
1 +Nein
Content Type
... ... @@ -1,1 +1,1 @@
1 -CSS
1 +LESS
XWiki.WikiMacroClass[0]
Makro-Code
... ... @@ -1,4 +1,11 @@
1 1  {{velocity output="false"}}
2 +#set ($parameterMap = {})
3 +#if ($themeDoc)
4 + ## If a theme doc is found, we add the theme to the ssx requests to make sure that the cache is properly updated.
5 + ## This code can be removed when the following platform issue is fixed:
6 + ## XWIKI-18668: The changes on the color theme are not taken into account for various ss*x resources
7 + #set ($discard = $parameterMap.put('colorTheme', $themeDocFullName))
8 +#end
2 2  $xwiki.jsx.use("Calendar.FullCalendar", {'defer': false, 'minify': false})
3 3  $xwiki.jsx.use("MoccaCalendar.Code.Macro", {'defer': false, 'v' : '2.7'})
4 4  $xwiki.jsx.use("MoccaCalendar.Code.DatePickerExtension", {'defer': false})
... ... @@ -5,8 +5,8 @@
5 5  $xwiki.jsx.use("MoccaCalendar.MoccaCalendarEventSheet")
6 6  $xwiki.jsx.use("MoccaCalendar.Code.MoccaCalendarEventModificationClass")
7 7  #dateTimePicker_import()
8 -$xwiki.ssx.use("Calendar.FullCalendar")
9 -$xwiki.ssx.use("MoccaCalendar.Code.Macro")
15 +$xwiki.ssx.use("Calendar.FullCalendar", $parameterMap)
16 +$xwiki.ssx.use("MoccaCalendar.Code.Macro", $parameterMap)
10 10  #set($calcounter = $request.getAttribute('MoccaCalendar.Code.Macro:counter'))
11 11  #if(!$calcounter) #set($calcounter = 0) #else #set($calcounter = $calcounter + 1) #end
12 12  #set($discard = $request.setAttribute('MoccaCalendar.Code.Macro:counter', $calcounter))
... ... @@ -67,6 +67,33 @@
67 67  #if(!$date)
68 68   #set($date = "")
69 69  #end
77 +#set($gCal = "$!xcontext.macro.params.gCal")
78 +#set($gApiKey = $xcontext.macro.params.gApiKey)
79 +#if(!$gApiKey)
80 + #set($gApiKey = "")
81 +#end
82 +#set($iCal = $xcontext.macro.params.iCal)
83 +#if(!$iCal)
84 + #set ($iCal = "")
85 +#end
86 +## Prepare the fields if the subscribed class is present
87 +#set ($subscribed = $doc.getObject('MoccaCalendar.MoccaCalendarSubscribeClass'))
88 +#if ($subscribed)
89 + #set ($subscribediCal = $subscribed.getValue('iCal'))
90 + #set ($subscribedgCals = $subscribed.getValue('gCal'))
91 + #set ($gApiKeySubscribed = $subscribed.getValue('gApiKey'))
92 + #set ($subscribedColor = $subscribed.getValue('color'))
93 + #set ($subscribedTextColor = $subscribed.getValue('textColor'))
94 + #set ($disableCreationEvent = 'disabled')
95 +#else
96 + #set ($subscribediCals = '')
97 + #set ($subscribedgCals = '')
98 + #set ($gApiKeySubscribed = '')
99 + #set ($subscribedColor = 'blue')
100 + #set ($subscribedTextColor = 'white')
101 + #set ($disableCreationEvent = "")
102 +#end
103 +
70 70  ## If the date format is not set in MoccaCalendarEventClass, we are going to use the date format from the current wiki.
71 71  #set($dateFormat = $!xwiki.getClass("MoccaCalendar.MoccaCalendarEventClass").get("startDate").getProperty('dateFormat').value)
72 72  #if("$!dateFormat" == "")
... ... @@ -124,23 +124,155 @@
124 124   </div>
125 125   </div>
126 126  #end
127 -{{/velocity}}
128 128  
129 -{{include reference="Licenses.Code.VelocityMacros"/}}
162 +#macro(importCalendarFileModal)
163 + <div class="modal fade" id="import-calendar-file-$calcounter" tabindex="-1" role="dialog">
164 + <div class="modal-dialog">
165 + <div class="modal-content">
166 + <div class="modal-header">
167 + <button type="button" class="close" data-dismiss="modal">&times;</button>
168 + <h4 class="modal-title">
169 + $escapetool.xml($services.localization.render('MoccaCalendar.import.modal.title'))
170 + </h4>
171 + </div>
172 + <div class="modal-body">
173 + #set ($escapedCalendarSpace = $escapetool.xml($doc.getSpace()))
174 + #set ($escapedCalendarName = $escapetool.xml($doc.getDocumentReference().getName()))
175 + #set ($actionURL = "$request.getContextPath()/rest/moccacalendar/import")
176 + #set ($xwikiCalendarDoc = $xwiki.getDocument($calendarDoc))
177 + #set ($calendarObject = $xwikiCalendarDoc.getObject('MoccaCalendar.MoccaCalendarClass'))
178 + <form class="xform" action="${actionURL}" method="post">
179 + <div class="import-form">
180 + #set ($calendarReferences = [])
181 + #if ($filter == 'wiki')
182 + #set ($calendarReferences = $services.moccacalendar.getAllCalendars())
183 + #elseif ($filter == 'space')
184 + #set ($calendarReferences = $services.moccacalendar.getAllCalendarsInDocumentSpace($services.model.resolveDocument($calendarDoc)))
185 + #end
186 + #if ($calendarReferences.size() > 0)
187 + <label for="import-calendar-parent-${calcounter}">
188 + $escapetool.xml($services.localization.render('MoccaCalendar.calendar'))</label>
189 + <select id="import-calendar-parent-${calcounter}" name="parentCalendar">
190 + #foreach ($item in $calendarReferences)
191 + #set ($itemdoc = $xwiki.getDocument($item))
192 + #if ($!{itemdoc} && ${itemdoc.hasAccessLevel("edit")})
193 + #set ($selected="")
194 + #if ($itemdoc.getId() == $doc.getId())
195 + #set ($selected=" selected='selected'")
196 + #end
197 + <option value="$escapetool.xml($services.model.serialize($itemdoc.getDocumentReference(),'default'))"$selected>
198 + $itemdoc.getDisplayTitle()</option>
199 + #end
200 + #end
201 + </select>
202 + #else
203 + <select id="import-calendar-parent-${calcounter}" name="parentCalendar" hidden>
204 + <option value="$escapetool.xml($services.model.serialize($services.model.resolveDocument($calendarDoc),'default'))" selected></option>
205 + </select>
206 + #end
207 + <label for="import-ical-file-input-${calcounter}">$escapetool.xml($services.localization.render(
208 + 'MoccaCalendar.import.modal.file.label'))</label>
209 + <input type="file" id="import-ical-file-input-${calcounter}" accept=".ics" name="importedfile"
210 + data-max-file-size="$!escapetool.xml($xwiki.getSpacePreference('upload_maxsize'))">
211 + </div>
212 + </form>
213 + </div>
214 + <div class="modal-footer">
215 + <input type="button" class="btn btn-primary import-calendar-file-button" data-calid="${calcounter}"
216 + value="$escapetool.xml($services.localization.render('MoccaCalendar.import.modal.button.start'))">
217 + <input type="button" class="btn btn-default"
218 + value="$escapetool.xml($services.localization.render('cancel'))" data-dismiss="modal">
219 + </div>
220 + </div>
221 + </div>
222 + </div>
223 +#end
224 +#macro (addCalendarObject $docRef)
225 + {{html clean=false wiki=true}}
226 + <input type="hidden" id="add-calendar-object" data-target="$docRef">
130 130  
228 + {{info cssClass="add-calendar-object-container"}}
229 + $escapetool.xml($services.localization.render('rendering.macro.moccacalendar.addObject.description'))
230 + [[**$escapetool.xml($services.localization.render('rendering.macro.moccacalendar.addObject.button'))**>>$docRef]]
231 + {{/info}}
232 + {{/html}}
233 +#end
234 +{{/velocity}}
235 +
131 131  {{velocity}}
237 +#macro(getSubscribedCalendars)
238 + #set ($query = ", BaseObject as obj where doc.fullName = obj.name and obj.className = 'MoccaCalendar.MoccaCalendarSubscribeClass'")
239 + #set ($calendarDocumentsReferences = $services.query.hql($query).execute())
240 + #set ($properties = [])
241 + #foreach($reference in $calendarDocumentsReferences)
242 + #set ($document = $xwiki.getDocument($reference))
243 + #set ($class = $document.getObject('MoccaCalendar.MoccaCalendarSubscribeClass'))
244 + #if ($class.getValue('globalDisplay') == '1')
245 + #set ($discard = $properties.add({
246 + 'iCal': $class.getValue('iCal'),
247 + 'gCal': $class.getValue('gCal'),
248 + 'gApiKey': $class.getValue('gApiKey'),
249 + 'color': $class.getValue('color'),
250 + 'textColor': $class.getValue('textColor')
251 + }))
252 + #end
253 + #end
254 +#end
255 +
256 +## Macro for handling iCal sources
257 +#macro(handleICalSources $iCalList $color $textColor)
258 + #set($iCals = $stringtool.split($iCalList, ','))
259 + #foreach($iCal in $iCals)
260 + #set($json = $xwiki.getDocument('Calendar.ICalToJSON').getURL('get', "iCal=$escapetool.url($stringtool.strip($iCal))"))
261 + {
262 + url: "$json&outputSyntax=plain&startfield=datetime",
263 + backgroundColor: "$color",
264 + textColor: "$textColor"
265 + }
266 + #if($foreach.count < $iCals.size())
267 + ,
268 + #end
269 + #end
270 +#end
271 +
272 +## Macro for handling Google Calendar sources
273 +#macro(handleGoogleCalendarSources $gCalList $apiKey $color, $textColor)
274 + #set($googleCalendars = $stringtool.split($gCalList, ','))
275 + #foreach($calendar in $googleCalendars)
276 + {
277 + googleCalendarId: "$stringtool.strip($calendar)",
278 + googleCalendarApiKey: "$apiKey",
279 + backgroundColor: "$color",
280 + textColor: "$textColor"
281 + }
282 + #if($foreach.count < $googleCalendars.size())
283 + ,
284 + #end
285 + #end
286 +#end
287 +
288 +
132 132  ## We need to check if there is a valid license because the macro is registered even if the user doesn't have view right
133 133  ## on the macro definition page. See XWIKI-14828: Rendering macros defined in wiki pages are available to users that
134 134  ## don't have view right on those pages.
135 135  #set ($mainReference = $services.model.createDocumentReference('', 'MoccaCalendar', 'MoccaCalendarClass'))
136 136  #if (!$services.licensing.licensor.hasLicensureForEntity($mainReference))
137 - {{error}}#getMissingLicenseMessage('moccacalendar.extension.name'){{/error}}
294 + {{missingLicenseMessage extensionName="moccacalendar.extension.name"/}}
138 138  #else
139 -#if($xcontext.action=='view'){{html clean="false" wiki="false"}}
296 +#if($xcontext.action=='view')
297 +#set ($xwikiCalendarDoc = $xwiki.getDocument($calendarDoc))
298 +#set ($calendarObject = $xwikiCalendarDoc.getObject('MoccaCalendar.MoccaCalendarClass'))
299 +#if ($filter == 'page' && !$calendarObject)
300 + #addCalendarObject($calendarDoc)
301 +#end
302 +{{html clean="false" wiki="false"}}
140 140  #if($canCreateEvents)
141 141  ## create event link
142 142  <div class="calendar-buttons">
143 143  <span class="buttonwrapper">
307 +<button data-toggle="modal" data-target="#import-calendar-file-$calcounter"
308 + title="$escapetool.xml($services.localization.render('MoccaCalendar.calendarevent.import.title'))">
309 + $escapetool.html($services.localization.render('MoccaCalendar.calendarevent.import'))</button>
144 144  <button class="btn btn-success" id="calendar${calcounter}-btn"><span class="glyphicon glyphicon-plus"></span> $escapetool.html($services.localization.render('MoccaCalendar.calendarevent.create'))</button>
145 145  </span>
146 146  </div>
... ... @@ -148,7 +148,14 @@
148 148  #end
149 149  <div id="calendar${calcounter}"></div>
150 150  <script type="text/javascript">
151 -require(['xwiki-meta', 'jquery', 'moccaCalendar'], function(xwikiMeta, jQuery) {
317 +require.config({
318 + paths: {
319 + purify: "$services.webjars.url('org.webjars.npm:dompurify', 'dist/purify.js')"
320 + },
321 +});
322 +
323 +
324 +require(['xwiki-meta', 'jquery', 'moment','purify', 'gcal', 'moccaCalendar'], function(xwikiMeta, jQuery, moment, purify) {
152 152   jQuery(document).ready(function() {
153 153   var defaultView = XWiki.MoccaCalendar.Helper.getCalendarView("$!escapetool.javascript($defaultView)");
154 154  
... ... @@ -171,21 +171,79 @@
171 171  
172 172   // page is now ready, initialize the calendar...
173 173   var calendar = jQuery('#calendar${calcounter}').fullCalendar({
174 - events: {
175 - url : "$!escapetool.javascript($jsonUrl)",
176 - type: 'GET',
177 - data : function() {
178 - // as we also get called before the calendar is fully initialized
179 - // we cannot get the current view reliably from the calendar itself
180 - // instead use our trusty helper
181 - return jQuery.extend(defaultEventData, {'outputView': XWiki.MoccaCalendar.Helper.getCalendarView(defaultView)} );
182 - },
183 - error: function() {
184 - calendarHelper.displayError();
185 - },
186 - traditional: true
187 - },
347 + eventSources: [
348 + ## Source where the xwiki events are stored.
349 + {
350 + url: "$!escapetool.javascript($jsonUrl)",
351 + type: 'GET',
352 + data: function() {
353 + // as we also get called before the calendar is fully initialized
354 + // we cannot get the current view reliably from the calendar itself
355 + // instead use our trusty helper
356 + return jQuery.extend(defaultEventData, {'outputView': XWiki.MoccaCalendar.Helper.getCalendarView(defaultView)} );
357 + },
358 + error: function() {
359 + calendarHelper.displayError();
360 + },
361 + traditional: true
362 + }
363 + ## Subscribed iCal sources
364 + #if($subscribediCal)
365 + ,
366 + #handleICalSources($subscribediCal, $subscribedColor, $subscribedTextColor)
367 + #end
368 + ## Subscribed Google Calendar sources
369 + #if($!subscribedgCals != "")
370 + ,
371 + #handleGoogleCalendarSources($subscribedgCals, $gApiKeySubscribed, $subscribedColor, $subscribedTextColor)
372 + #end
373 + ## Global events
374 + #if($doc.getFullName() == 'MoccaCalendar.WebHome')
375 + #getSubscribedCalendars()
376 + #foreach($property in $properties)
377 + ,
378 + #handleICalSources($property['iCal'], $property['color'], $property['textColor'])
379 + ,
380 + #handleGoogleCalendarSources($property['gCal'], $property['gApiKey'], $property['color'], $property['textColor'])
381 + #end
382 + #end
383 + ## In case the user uses the calendar macro directly and dosen't use the UI to create it.
384 + ## iCal sources
385 + #if($!iCal != "")
386 + ,
387 + #handleICalSources($iCal 'black')
388 + #end
389 + ## Google Calendar public sources
390 + #if($!gCal != "")
391 + ,
392 + #handleGoogleCalendarSources($gCal $gApiKey 'blue')
393 + #end
394 + ],
188 188   eventClick: function(calEvent, jsEvent, view) {
396 + // Prepare the read only events for the modal.
397 + if (!calEvent.viewUrl){
398 + // The XWiki default format for date is yyyy/MM/dd, which translates into 2024/01/17. However, for some weird
399 + // reason, when using the date.format() from JavaScript, it translates into 2024/01/Mon. We need to use the
400 + // moment().toMomentFormatString(format) to get the right format.
401 + const dateFormat = moment().toMomentFormatString(calendarHelper.dateFormat);
402 +
403 + // Get the sheet URL.
404 + const sheetURL = new XWiki.Document('MoccaCalendarEventSheet', 'MoccaCalendar').getURL('get');
405 + // Parameters for the modal content.
406 + const params = {
407 + title: purify.sanitize(calEvent.title),
408 + start: calEvent.start.format(dateFormat),
409 + end: calEvent.end ? calEvent.end.format(dateFormat) : null,
410 + allDay: calEvent.allDay ? "$escapetool.xml($services.localization.render('moccacalendar.subscribedevent.allday.yes'))" :
411 + "$escapetool.xml($services.localization.render('moccacalendar.subscribedevent.allday.no'))",
412 + description: purify.sanitize(calEvent.description),
413 + readOnly: 'true'
414 + };
415 + const queryString = jQuery.param(params);
416 + const requestURL = `${sheetURL}?${queryString}`;
417 + calEvent.url = requestURL;
418 + calEvent.readOnly=true;
419 + }
189 189   new XWiki.MoccaCalendar.MoccaCalendarPopup({editMode: false, event: calEvent}, calendarHelper);
190 190   jsEvent.preventDefault();
191 191   return false;
... ... @@ -254,7 +254,7 @@
254 254   });
255 255   jQuery('#calendar${calcounter}-btn').click( function(e) { calendarHelper.showCreateEvent(); e.preventDefault(); });
256 256   // helper to be used in callback above
257 -#set($newPageParams = "template=MoccaCalendar.MoccaCalendarEventTemplate&parentFROM=${escapetool.url($calendarDoc)}&form_token=${services.csrf.getToken()}&ocalcaction=create")
488 +#set($newPageParams = "template=MoccaCalendar.MoccaCalendarEventTemplate&parentFROM=${escapetool.url($calendarDoc)}&calendarParentFilter=${escapetool.url($filter)}&form_token=${services.csrf.getToken()}&ocalcaction=create&disabled=$disableCreationEvent")
258 258  #set($randomDocUrl = $xwiki.getURL("randomPage${util.generateRandomString(10)}",'edit',$newPageParams))
259 259  #set($updateUrlParams="?xpage=plain&outputSyntax=plain&calendarDoc=${escapetool.url(${calendarDoc})}&")
260 260   var calendarHelper = new XWiki.MoccaCalendar.Helper(calendar,
... ... @@ -271,6 +271,7 @@
271 271   });
272 272  });
273 273  </script>
505 +#importCalendarFileModal
274 274  #if($calcounter == 0)
275 275   #showDeleteEventsModal
276 276  #end
Standardkategorie
... ... @@ -1,1 +1,0 @@
1 -Content
Default categories
... ... @@ -1,0 +1,1 @@
1 +Content
XWiki.WikiMacroParameterClass[11]
Parameter-Name
... ... @@ -1,0 +1,1 @@
1 +gCal
Parameter verpflichtend
... ... @@ -1,0 +1,1 @@
1 +Nein
XWiki.WikiMacroParameterClass[12]
Parameter-Name
... ... @@ -1,0 +1,1 @@
1 +gApiKey
Parameter verpflichtend
... ... @@ -1,0 +1,1 @@
1 +Nein
XWiki.WikiMacroParameterClass[13]
Parameter-Name
... ... @@ -1,0 +1,1 @@
1 +iCal
Parameter verpflichtend
... ... @@ -1,0 +1,1 @@
1 +Nein