Änderungen von Dokument Calendar Macro
Zuletzt geändert von xwikiadmin am 2025/12/11 06:30
Von Version 3.1
bearbeitet von xwikiadmin
am 2023/10/26 09:30
am 2023/10/26 09:30
Änderungskommentar:
Migrated property [type] from class [XWiki.WikiMacroParameterClass]
Auf Version 9.1
bearbeitet von xwikiadmin
am 2025/12/11 06:30
am 2025/12/11 06:30
Änderungskommentar:
Migrated property [executionIsolated] from class [XWiki.WikiMacroClass]
Zusammenfassung
-
Objekte (3 geändert, 3 hinzugefügt, 0 gelöscht)
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 - * Delet ingan event776 + * 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 - CSS1 +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">×</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