diff --git a/.eslintrc.json b/.eslintrc.json index 6d0addb49..013b76d80 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -97,7 +97,7 @@ "Avatar": true, "Avatars": true, "BlazeComponent": false, - + "CollectionHooks": false, "ESSearchResults": false, "FastRender": false, @@ -105,7 +105,6 @@ "FS": false, "getSlug": false, "Migrations": false, - "moment": false, "Mousetrap": false, "Picker": false, "Presence": true, diff --git a/.meteor/packages b/.meteor/packages index e9a6af949..3211eb507 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -78,7 +78,6 @@ peerlibrary:blaze-components ejson@1.1.3 logging@1.3.3 wekan-fullcalendar -momentjs:moment@2.29.3 wekan-fontawesome useraccounts:flow-routing-extra diff --git a/.meteor/versions b/.meteor/versions index 9ae1e21f4..8fa670345 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -71,7 +71,6 @@ minimongo@1.9.4 modern-browsers@0.1.10 modules@0.20.0 modules-runtime@0.13.1 -momentjs:moment@2.29.3 mongo@1.16.10 mongo-decimal@0.1.3 mongo-dev-server@1.1.0 @@ -142,7 +141,7 @@ wekan-accounts-lockout@1.1.0 wekan-accounts-oidc@1.0.10 wekan-accounts-sandstorm@0.9.0 wekan-fontawesome@6.4.2 -wekan-fullcalendar@3.10.5 +wekan-fullcalendar@5.11.5 wekan-ldap@0.1.0 wekan-markdown@1.0.9 wekan-oidc@1.1.0 diff --git a/client/components/activities/activities.jade b/client/components/activities/activities.jade index 140335178..346b4a4fb 100644 --- a/client/components/activities/activities.jade +++ b/client/components/activities/activities.jade @@ -189,15 +189,15 @@ template(name="activity") if(currentData.timeKey) | {{_ activity.activityType }} = ' ' - i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }} + i(title=currentData.timeValue).activity-meta {{ displayDate currentData.timeValue 'LLL' }} if (currentData.timeOldValue) = ' ' | {{{_ "previous_as" }}} = ' ' - i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }} + i(title=currentData.timeOldValue).activity-meta {{ displayDate currentData.timeOldValue 'LLL' }} = ' @' else if(currentData.timeValue) | {{_ activity.activityType currentData.timeValue}} if($neq mode 'none') - div(title=activity.createdAt).activity-meta {{ moment activity.createdAt }} + div(title=activity.createdAt).activity-meta {{ displayDate activity.createdAt }} diff --git a/client/components/activities/comments.jade b/client/components/activities/comments.jade index 1860eb4f4..cbf497a67 100644 --- a/client/components/activities/comments.jade +++ b/client/components/activities/comments.jade @@ -32,7 +32,7 @@ template(name="comment") +viewer = text +commentReactions(reactions=reactions commentId=_id) - span(title=createdAt).comment-meta {{ moment createdAt }} + span(title=createdAt).comment-meta {{ displayDate createdAt }} if($eq currentUser._id userId) +editOrDeleteComment else if currentUser.isBoardAdmin diff --git a/client/components/boards/boardArchive.jade b/client/components/boards/boardArchive.jade index 839f183e1..76f3be0a5 100644 --- a/client/components/boards/boardArchive.jade +++ b/client/components/boards/boardArchive.jade @@ -15,7 +15,7 @@ template(name="archivedBoards") i.fa.fa-undo | {{_ 'restore-board'}} = title - span {{ moment archivedAt 'LLL' }} + span {{ displayDate archivedAt 'LLL' }} else li.no-items-message {{_ 'no-archived-boards'}} diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index 1f8919552..499a2167f 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -226,16 +226,22 @@ BlazeComponent.extendComponent({ } // Observe for new popups/menus and set focus (but exclude swimlane content) - const popupObserver = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - mutation.addedNodes.forEach(function(node) { - if (node.nodeType === 1 && - (node.classList.contains('popup') || node.classList.contains('modal') || node.classList.contains('menu')) && - !node.closest('.js-swimlanes') && - !node.closest('.swimlane') && - !node.closest('.list') && - !node.closest('.minicard')) { - setTimeout(function() { focusFirstInteractive(node); }, 10); + const popupObserver = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + mutation.addedNodes.forEach(function (node) { + if ( + node.nodeType === 1 && + (node.classList.contains('popup') || + node.classList.contains('modal') || + node.classList.contains('menu')) && + !node.closest('.js-swimlanes') && + !node.closest('.swimlane') && + !node.closest('.list') && + !node.closest('.minicard') + ) { + setTimeout(function () { + focusFirstInteractive(node); + }, 10); } }); }); @@ -898,30 +904,35 @@ BlazeComponent.extendComponent({ document.documentElement.lang = TAPi18n.getLanguage(); this.autorun(function () { - $('#calendar-view').fullCalendar('refetchEvents'); + const calendarEl = document.getElementById('calendar-view'); + if (calendarEl && calendarEl._wekanCalendar) { + calendarEl._wekanCalendar.refetchEvents(); + } }); }, calendarOptions() { return { id: 'calendar-view', - defaultView: 'month', + initialView: 'dayGridMonth', editable: true, selectable: true, - timezone: 'local', weekNumbers: true, // Use non-localized AM/PM time format to avoid confusing notations like 上/下/中 // Use full 'am'/'pm' instead of single-letter 'a'/'p' for clarity - timeFormat: 'h:mma', - slotLabelFormat: 'h:mma', - extraSmallTimeFormat: 'h(:mm)a', - smallTimeFormat: 'h(:mm)a', - mediumTimeFormat: 'h:mma', - hourFormat: 'ha', - noMeridiemTimeFormat: 'h:mm', - header: { + eventTimeFormat: { + hour: 'numeric', + minute: '2-digit', + meridiem: 'short', + }, + slotLabelFormat: { + hour: 'numeric', + minute: '2-digit', + meridiem: 'short', + }, + headerToolbar: { left: 'title today prev,next', center: - 'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,listMonth', + 'timeGridDay,listDay timeGridWeek,listWeek dayGridMonth,listMonth', right: '', }, buttonText: { @@ -939,12 +950,12 @@ BlazeComponent.extendComponent({ nowIndicator: true, businessHours: { // days of week. an array of zero-based day of week integers (0=Sunday) - dow: [1, 2, 3, 4, 5], // Monday - Friday + daysOfWeek: [1, 2, 3, 4, 5], // Monday - Friday start: '8:00', end: '18:00', }, locale: TAPi18n.getLanguage(), - events(start, end, timezone, callback) { + events(fetchInfo, callback) { const currentBoard = Utils.getCurrentBoard(); const events = []; const pushEvent = function (card, title, start, end, extraCls) { @@ -970,12 +981,12 @@ BlazeComponent.extendComponent({ }); }; currentBoard - .cardsInInterval(start.toDate(), end.toDate()) + .cardsInInterval(fetchInfo.start, fetchInfo.end) .forEach(function (card) { pushEvent(card); }); currentBoard - .cardsDueInBetween(start.toDate(), end.toDate()) + .cardsDueInBetween(fetchInfo.start, fetchInfo.end) .forEach(function (card) { pushEvent( card, @@ -989,36 +1000,36 @@ BlazeComponent.extendComponent({ }); callback(events); }, - eventResize(event, delta, revertFunc) { + eventResize(info) { let isOk = false; - const card = ReactiveCache.getCard(event.id); + const card = ReactiveCache.getCard(info.event.id); if (card) { - card.setEnd(event.end.toDate()); + card.setEnd(info.event.end); isOk = true; } if (!isOk) { - revertFunc(); + info.revert(); } }, - eventDrop(event, delta, revertFunc) { + eventDrop(info) { let isOk = false; - const card = ReactiveCache.getCard(event.id); + const card = ReactiveCache.getCard(info.event.id); if (card) { // TODO: add a flag for allDay events - if (!event.allDay) { + if (!info.event.allDay) { // https://github.com/wekan/wekan/issues/2917#issuecomment-1236753962 - //card.setStart(event.start.toDate()); - //card.setEnd(event.end.toDate()); - card.setDue(event.start.toDate()); + //card.setStart(info.event.start); + //card.setEnd(info.event.end); + card.setDue(info.event.start); isOk = true; } } if (!isOk) { - revertFunc(); + info.revert(); } }, - select: function (startDate) { + select: function (selectionInfo) { const currentBoard = Utils.getCurrentBoard(); const currentUser = ReactiveCache.getCurrentUser(); const modalElement = document.createElement('div'); @@ -1056,7 +1067,7 @@ BlazeComponent.extendComponent({ currentBoard._id, firstList._id, myTitle, - startDate.toDate(), + selectionInfo.start, firstSwimlane._id, function (error, result) { if (error) { diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index f3be79410..277503b29 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -1077,7 +1077,7 @@ template(name="cardMorePopup") option(value="{{_id}}") {{title}} br | {{_ 'added'}} - span.date(title=card.createdAt) {{ moment createdAt 'LLL' }} + span.date(title=card.createdAt) {{ displayDate createdAt 'LLL' }} if currentUser.isBoardAdmin a.js-delete(title="{{_ 'card-delete-notice'}}") {{_ 'delete'}} diff --git a/client/components/cards/checklists.jade b/client/components/cards/checklists.jade index 0cd70f880..c2b60837d 100644 --- a/client/components/cards/checklists.jade +++ b/client/components/cards/checklists.jade @@ -92,7 +92,7 @@ template(name="editChecklistItemForm") .edit-controls.clearfix button.primary.confirm.js-submit-edit-checklist-item-form(type="submit") {{_ 'save'}} a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-edit-checklist-item'}}") - span(title=createdAt) {{ moment createdAt }} + span(title=createdAt) {{ displayDate createdAt }} if canModifyCard a.js-delete-checklist-item {{_ "delete"}}... a.js-convert-checklist-item-to-card diff --git a/client/components/cards/subtasks.jade b/client/components/cards/subtasks.jade index 3ea5ec5e3..f8e711c28 100644 --- a/client/components/cards/subtasks.jade +++ b/client/components/cards/subtasks.jade @@ -51,7 +51,7 @@ template(name="editSubtaskItemForm") .edit-controls.clearfix button.primary.confirm.js-submit-edit-subtask-item-form(type="submit") {{_ 'save'}} a.js-close-inlined-form - span(title=createdAt) {{ moment createdAt }} + span(title=createdAt) {{ displayDate createdAt }} if canModifyCard if currentUser.isBoardAdmin a.js-delete-subtask-item {{_ "delete"}}... diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade index 9434ae1eb..967209374 100644 --- a/client/components/lists/listHeader.jade +++ b/client/components/lists/listHeader.jade @@ -16,7 +16,7 @@ template(name="listHeader") span.cardCount {{cardsCount}} if isMiniScreen h2.list-header-name( - title="{{ moment modifiedAt 'LLL' }}" + title="{{ displayDate modifiedAt 'LLL' }}" class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}") +viewer = title @@ -37,7 +37,7 @@ template(name="listHeader") i.fa.fa-caret-down div(class="{{#if collapsed}}list-rotated{{/if}}") h2.list-header-name( - title="{{ moment modifiedAt 'LLL' }}" + title="{{ displayDate modifiedAt 'LLL' }}" class="{{#unless collapsed}}{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}{{/unless}}") +viewer = title @@ -193,7 +193,7 @@ template(name="listMorePopup") i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}") input.inline-input(type="text" readonly value="{{ rootUrl }}") | {{_ 'added'}} - span.date(title=list.createdAt) {{ moment createdAt 'LLL' }} + span.date(title=list.createdAt) {{ displayDate createdAt 'LLL' }} //unless currentUser.isWorker // if currentUser.isBoardAdmin diff --git a/client/components/main/myCards.jade b/client/components/main/myCards.jade index e2e4ffd73..7d068c43f 100644 --- a/client/components/main/myCards.jade +++ b/client/components/main/myCards.jade @@ -96,7 +96,7 @@ template(name="myCards") | {{labelName board label}} td if card.dueAt - | {{ moment card.dueAt 'LLL' }} + | {{ displayDate card.dueAt 'LLL' }} template(name="myCardsViewChangePopup") if currentUser diff --git a/client/components/notifications/notification.js b/client/components/notifications/notification.js index 77cc9fa4b..27a7ff793 100644 --- a/client/components/notifications/notification.js +++ b/client/components/notifications/notification.js @@ -1,4 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; +import { formatDateByUserPreference } from '/imports/lib/dateUtils'; Template.notification.events({ 'click .read-status .materialCheckBox'() { @@ -38,9 +39,15 @@ Template.notification.helpers({ const user = ReactiveCache.getCurrentUser(); if (!user) return ''; - const dateFormat = user.getDateFormat ? user.getDateFormat() : 'L'; - const timeFormat = user.getTimeFormat ? user.getTimeFormat() : 'LT'; + const dateObj = new Date(activity.createdAt); + if (Number.isNaN(dateObj.getTime())) return ''; - return moment(activity.createdAt).format(`${dateFormat} ${timeFormat}`); + const dateFormat = user.getDateFormat ? user.getDateFormat() : 'YYYY-MM-DD'; + const datePart = formatDateByUserPreference(dateObj, dateFormat, false); + const timePart = dateObj.toLocaleTimeString([], { + hour: 'numeric', + minute: '2-digit', + }); + return `${datePart} ${timePart}`.trim(); }, }); diff --git a/client/components/settings/peopleBody.jade b/client/components/settings/peopleBody.jade index 0234d6074..a44f07c55 100644 --- a/client/components/settings/peopleBody.jade +++ b/client/components/settings/peopleBody.jade @@ -190,9 +190,9 @@ template(name="orgRow") else td {{ orgData.orgWebsite }} if orgData.orgIsActive - td {{ moment orgData.createdAt 'LLL' }} + td {{ displayDate orgData.createdAt 'LLL' }} else - td {{ moment orgData.createdAt 'LLL' }} + td {{ displayDate orgData.createdAt 'LLL' }} td if orgData.orgIsActive | {{_ 'yes'}} @@ -224,9 +224,9 @@ template(name="teamRow") else td {{ teamData.teamWebsite }} if teamData.teamIsActive - td {{ moment teamData.createdAt 'LLL' }} + td {{ displayDate teamData.createdAt 'LLL' }} else - td {{ moment teamData.createdAt 'LLL' }} + td {{ displayDate teamData.createdAt 'LLL' }} td if teamData.teamIsActive | {{_ 'yes'}} @@ -284,9 +284,9 @@ template(name="peopleRow") span.text-green.js-toggle-lock-status.emoji-icon(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}") i.fa.fa-unlock if userData.loginDisabled - td {{ moment userData.createdAt 'LLL' }} + td {{ displayDate userData.createdAt 'LLL' }} else - td {{ moment userData.createdAt 'LLL' }} + td {{ displayDate userData.createdAt 'LLL' }} if userData.loginDisabled td input.selectUserChkBox(type="checkbox", disabled="disabled", id="{{userData._id}}") diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js index 29c6976e1..8736e823c 100644 --- a/client/components/settings/settingBody.js +++ b/client/components/settings/settingBody.js @@ -18,9 +18,9 @@ import { cronMigrationEtaSeconds, cronMigrationElapsedSeconds, cronMigrationCurrentNumber, - cronMigrationCurrentName + cronMigrationCurrentName, } from '/imports/cronMigrationClient'; - +import { format } from '/imports/lib/dateUtils'; BlazeComponent.extendComponent({ onCreated() { @@ -66,7 +66,6 @@ BlazeComponent.extendComponent({ } }, - setError(error) { this.error.set(error); }, @@ -82,7 +81,9 @@ BlazeComponent.extendComponent({ return this.accountSetting && this.accountSetting.get(); }, isTableVisibilityModeSetting() { - return this.tableVisibilityModeSetting && this.tableVisibilityModeSetting.get(); + return ( + this.tableVisibilityModeSetting && this.tableVisibilityModeSetting.get() + ); }, isAnnouncementSetting() { return this.announcementSetting && this.announcementSetting.get(); @@ -174,7 +175,7 @@ BlazeComponent.extendComponent({ const steps = cronMigrationSteps.get() || []; return steps.map((step, idx) => ({ ...step, - index: idx + 1 + index: idx + 1, })); }, @@ -237,7 +238,9 @@ BlazeComponent.extendComponent({ isUpdatingMigrationDropdown() { const status = this.migrationStatus(); - return status && status.startsWith('Updating Select Migration dropdown menu'); + return ( + status && status.startsWith('Updating Select Migration dropdown menu') + ); }, migrationErrors() { @@ -251,7 +254,7 @@ BlazeComponent.extendComponent({ formatDateTime(date) { if (!date) return ''; - return moment(date).format('YYYY-MM-DD HH:mm:ss'); + return format(date, 'YYYY-MM-DD HH:mm:ss'); }, formatDurationSeconds(seconds) { @@ -331,18 +334,22 @@ BlazeComponent.extendComponent({ }); } else { // Run specific migration - Meteor.call('cron.startSpecificMigration', selectedIndex - 1, (error, result) => { - this.setLoading(false); - if (error) { - alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason); - } else if (result && result.skipped) { - cronIsMigrating.set(false); - cronMigrationStatus.set(TAPi18n.__('migration-not-needed')); - alert(TAPi18n.__('migration-not-needed')); - } else { - alert(TAPi18n.__('migration-started')); - } - }); + Meteor.call( + 'cron.startSpecificMigration', + selectedIndex - 1, + (error, result) => { + this.setLoading(false); + if (error) { + alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason); + } else if (result && result.skipped) { + cronIsMigrating.set(false); + cronMigrationStatus.set(TAPi18n.__('migration-not-needed')); + alert(TAPi18n.__('migration-not-needed')); + } else { + alert(TAPi18n.__('migration-started')); + } + }, + ); } }, @@ -490,9 +497,7 @@ BlazeComponent.extendComponent({ checkField(selector) { const value = $(selector).val(); if (!value || value.trim() === '') { - $(selector) - .parents('li.smtp-form') - .addClass('has-error'); + $(selector).parents('li.smtp-form').addClass('has-error'); throw Error('blank field'); } else { return value; @@ -514,7 +519,8 @@ BlazeComponent.extendComponent({ }, toggleForgotPassword() { this.setLoading(true); - const forgotPasswordClosed = ReactiveCache.getCurrentSetting().disableForgotPassword; + const forgotPasswordClosed = + ReactiveCache.getCurrentSetting().disableForgotPassword; Settings.update(ReactiveCache.getCurrentSetting()._id, { $set: { disableForgotPassword: !forgotPasswordClosed }, }); @@ -522,7 +528,8 @@ BlazeComponent.extendComponent({ }, toggleRegistration() { this.setLoading(true); - const registrationClosed = ReactiveCache.getCurrentSetting().disableRegistration; + const registrationClosed = + ReactiveCache.getCurrentSetting().disableRegistration; Settings.update(ReactiveCache.getCurrentSetting()._id, { $set: { disableRegistration: !registrationClosed }, }); @@ -629,11 +636,11 @@ BlazeComponent.extendComponent({ .join(',') .split(','); const boardsToInvite = []; - $('.js-toggle-board-choose .materialCheckBox.is-checked').each(function() { + $('.js-toggle-board-choose .materialCheckBox.is-checked').each(function () { boardsToInvite.push($(this).data('id')); }); const validEmails = []; - emails.forEach(email => { + emails.forEach((email) => { if (email && SimpleSchema.RegEx.Email.test(email.trim())) { validEmails.push(email.trim()); } @@ -656,12 +663,8 @@ BlazeComponent.extendComponent({ try { const host = this.checkField('#mail-server-host'); const port = this.checkField('#mail-server-port'); - const username = $('#mail-server-username') - .val() - .trim(); - const password = $('#mail-server-password') - .val() - .trim(); + const username = $('#mail-server-username').val().trim(); + const password = $('#mail-server-password').val().trim(); const from = this.checkField('#mail-server-from'); const tls = $('#mail-server-tls.is-checked').length > 0; Settings.update(ReactiveCache.getCurrentSetting()._id, { @@ -686,21 +689,37 @@ BlazeComponent.extendComponent({ $('li').removeClass('has-error'); const productName = ($('#product-name').val() || '').trim(); - const customLoginLogoImageUrl = ($('#custom-login-logo-image-url').val() || '').trim(); - const customLoginLogoLinkUrl = ($('#custom-login-logo-link-url').val() || '').trim(); + const customLoginLogoImageUrl = ( + $('#custom-login-logo-image-url').val() || '' + ).trim(); + const customLoginLogoLinkUrl = ( + $('#custom-login-logo-link-url').val() || '' + ).trim(); const customHelpLinkUrl = ($('#custom-help-link-url').val() || '').trim(); - const textBelowCustomLoginLogo = ($('#text-below-custom-login-logo').val() || '').trim(); - const automaticLinkedUrlSchemes = ($('#automatic-linked-url-schemes').val() || '').trim(); - const customTopLeftCornerLogoImageUrl = ($('#custom-top-left-corner-logo-image-url').val() || '').trim(); - const customTopLeftCornerLogoLinkUrl = ($('#custom-top-left-corner-logo-link-url').val() || '').trim(); - const customTopLeftCornerLogoHeight = ($('#custom-top-left-corner-logo-height').val() || '').trim(); + const textBelowCustomLoginLogo = ( + $('#text-below-custom-login-logo').val() || '' + ).trim(); + const automaticLinkedUrlSchemes = ( + $('#automatic-linked-url-schemes').val() || '' + ).trim(); + const customTopLeftCornerLogoImageUrl = ( + $('#custom-top-left-corner-logo-image-url').val() || '' + ).trim(); + const customTopLeftCornerLogoLinkUrl = ( + $('#custom-top-left-corner-logo-link-url').val() || '' + ).trim(); + const customTopLeftCornerLogoHeight = ( + $('#custom-top-left-corner-logo-height').val() || '' + ).trim(); const oidcBtnText = ($('#oidcBtnTextvalue').val() || '').trim(); const mailDomainName = ($('#mailDomainNamevalue').val() || '').trim(); const legalNotice = ($('#legalNoticevalue').val() || '').trim(); const hideLogoChange = $('input[name=hideLogo]:checked').val() === 'true'; - const hideCardCounterListChange = $('input[name=hideCardCounterList]:checked').val() === 'true'; - const hideBoardMemberListChange = $('input[name=hideBoardMemberList]:checked').val() === 'true'; + const hideCardCounterListChange = + $('input[name=hideCardCounterList]:checked').val() === 'true'; + const hideBoardMemberListChange = + $('input[name=hideBoardMemberList]:checked').val() === 'true'; const displayAuthenticationMethod = $('input[name=displayAuthenticationMethod]:checked').val() === 'true'; const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val(); @@ -740,7 +759,9 @@ BlazeComponent.extendComponent({ toggleSupportPage() { this.setLoading(true); - const supportPageEnabled = !$('.js-toggle-support .materialCheckBox').hasClass('is-checked'); + const supportPageEnabled = !$( + '.js-toggle-support .materialCheckBox', + ).hasClass('is-checked'); $('.js-toggle-support .materialCheckBox').toggleClass('is-checked'); $('.support-content').toggleClass('hide'); Settings.update(Settings.findOne()._id, { @@ -751,7 +772,9 @@ BlazeComponent.extendComponent({ toggleSupportPublic() { this.setLoading(true); - const supportPagePublic = !$('.js-toggle-support-public .materialCheckBox').hasClass('is-checked'); + const supportPagePublic = !$( + '.js-toggle-support-public .materialCheckBox', + ).hasClass('is-checked'); $('.js-toggle-support-public .materialCheckBox').toggleClass('is-checked'); Settings.update(Settings.findOne()._id, { $set: { supportPagePublic }, @@ -761,7 +784,9 @@ BlazeComponent.extendComponent({ toggleCustomHead() { this.setLoading(true); - const customHeadEnabled = !$('.js-toggle-custom-head .materialCheckBox').hasClass('is-checked'); + const customHeadEnabled = !$( + '.js-toggle-custom-head .materialCheckBox', + ).hasClass('is-checked'); $('.js-toggle-custom-head .materialCheckBox').toggleClass('is-checked'); $('.custom-head-settings').toggleClass('hide'); Settings.update(ReactiveCache.getCurrentSetting()._id, { @@ -772,7 +797,9 @@ BlazeComponent.extendComponent({ toggleCustomManifest() { this.setLoading(true); - const customManifestEnabled = !$('.js-toggle-custom-manifest .materialCheckBox').hasClass('is-checked'); + const customManifestEnabled = !$( + '.js-toggle-custom-manifest .materialCheckBox', + ).hasClass('is-checked'); $('.js-toggle-custom-manifest .materialCheckBox').toggleClass('is-checked'); $('.custom-manifest-settings').toggleClass('hide'); Settings.update(ReactiveCache.getCurrentSetting()._id, { @@ -829,7 +856,9 @@ BlazeComponent.extendComponent({ const errorMsg = e.message; // If error is "unexpected non-whitespace character after JSON data" - if (errorMsg.includes('unexpected non-whitespace character after JSON data')) { + if ( + errorMsg.includes('unexpected non-whitespace character after JSON data') + ) { try { // Try to find and extract valid JSON by finding matching braces/brackets const trimmed = content.trim(); @@ -896,8 +925,12 @@ BlazeComponent.extendComponent({ toggleCustomAssetLinks() { this.setLoading(true); - const customAssetLinksEnabled = !$('.js-toggle-custom-assetlinks .materialCheckBox').hasClass('is-checked'); - $('.js-toggle-custom-assetlinks .materialCheckBox').toggleClass('is-checked'); + const customAssetLinksEnabled = !$( + '.js-toggle-custom-assetlinks .materialCheckBox', + ).hasClass('is-checked'); + $('.js-toggle-custom-assetlinks .materialCheckBox').toggleClass( + 'is-checked', + ); $('.custom-assetlinks-settings').toggleClass('hide'); Settings.update(ReactiveCache.getCurrentSetting()._id, { $set: { customAssetLinksEnabled }, @@ -978,8 +1011,10 @@ BlazeComponent.extendComponent({ 'click button.js-save': this.saveMailServerInfo, 'click button.js-send-smtp-test-email': this.sendSMTPTestEmail, 'click a.js-toggle-hide-logo': this.toggleHideLogo, - 'click a.js-toggle-hide-card-counter-list': this.toggleHideCardCounterList, - 'click a.js-toggle-hide-board-member-list': this.toggleHideBoardMemberList, + 'click a.js-toggle-hide-card-counter-list': + this.toggleHideCardCounterList, + 'click a.js-toggle-hide-board-member-list': + this.toggleHideBoardMemberList, 'click button.js-save-layout': this.saveLayout, 'click a.js-toggle-support': this.toggleSupportPage, 'click a.js-toggle-support-public': this.toggleSupportPublic, @@ -988,9 +1023,10 @@ BlazeComponent.extendComponent({ 'click a.js-toggle-custom-manifest': this.toggleCustomManifest, 'click button.js-custom-head-save': this.saveCustomHeadSettings, 'click a.js-toggle-custom-assetlinks': this.toggleCustomAssetLinks, - 'click button.js-custom-assetlinks-save': this.saveCustomAssetLinksSettings, - 'click a.js-toggle-display-authentication-method': this - .toggleDisplayAuthenticationMethod, + 'click button.js-custom-assetlinks-save': + this.saveCustomAssetLinksSettings, + 'click a.js-toggle-display-authentication-method': + this.toggleDisplayAuthenticationMethod, }, ]; }, @@ -1018,15 +1054,23 @@ BlazeComponent.extendComponent({ // Brute force lockout settings method moved to lockedUsersBody.js allowEmailChange() { - return AccountSettings.findOne('accounts-allowEmailChange')?.booleanValue || false; + return ( + AccountSettings.findOne('accounts-allowEmailChange')?.booleanValue || + false + ); }, allowUserNameChange() { - return AccountSettings.findOne('accounts-allowUserNameChange')?.booleanValue || false; + return ( + AccountSettings.findOne('accounts-allowUserNameChange')?.booleanValue || + false + ); }, allowUserDelete() { - return AccountSettings.findOne('accounts-allowUserDelete')?.booleanValue || false; + return ( + AccountSettings.findOne('accounts-allowUserDelete')?.booleanValue || false + ); }, // Lockout settings helper methods moved to lockedUsersBody.js @@ -1054,7 +1098,8 @@ BlazeComponent.extendComponent({ 'click button.js-accounts-save': this.saveAccountsChange, }, { - 'click button.js-all-boards-hide-activities': this.allBoardsHideActivities, + 'click button.js-all-boards-hide-activities': + this.allBoardsHideActivities, }, ]; }, @@ -1069,7 +1114,9 @@ BlazeComponent.extendComponent({ }); }, allowPrivateOnly() { - return TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue; + return TableVisibilityModeSettings.findOne( + 'tableVisibilityMode-allowPrivateOnly', + ).booleanValue; }, allBoardsHideActivities() { Meteor.call('setAllBoardsHideActivities', (err, ret) => { @@ -1091,10 +1138,12 @@ BlazeComponent.extendComponent({ events() { return [ { - 'click button.js-tableVisibilityMode-save': this.saveTableVisibilityChange, + 'click button.js-tableVisibilityMode-save': + this.saveTableVisibilityChange, }, { - 'click button.js-all-boards-hide-activities': this.allBoardsHideActivities, + 'click button.js-all-boards-hide-activities': + this.allBoardsHideActivities, }, ]; }, @@ -1114,9 +1163,7 @@ BlazeComponent.extendComponent({ }, saveMessage() { - const message = $('#admin-announcement') - .val() - .trim(); + const message = $('#admin-announcement').val().trim(); Announcements.update(Announcements.findOne()._id, { $set: { body: message }, }); @@ -1162,18 +1209,14 @@ BlazeComponent.extendComponent({ saveAccessibility() { this.setLoading(true); - const title = $('#admin-accessibility-title') - .val() - .trim(); - const content = $('#admin-accessibility-content') - .val() - .trim(); + const title = $('#admin-accessibility-title').val().trim(); + const content = $('#admin-accessibility-content').val().trim(); try { AccessibilitySettings.update(AccessibilitySettings.findOne()._id, { $set: { title: title, - body: content + body: content, }, }); } catch (e) { @@ -1209,7 +1252,7 @@ BlazeComponent.extendComponent({ }, }).register('accessibilitySettings'); -Template.selectAuthenticationMethod.onCreated(function() { +Template.selectAuthenticationMethod.onCreated(function () { this.authenticationMethods = new ReactiveVar([]); Meteor.call('getAuthenticationsEnabled', (_, result) => { @@ -1220,8 +1263,8 @@ Template.selectAuthenticationMethod.onCreated(function() { { value: 'password' }, // Gets only the authentication methods availables ...Object.entries(result) - .filter(e => e[1]) - .map(e => ({ value: e[0] })), + .filter((e) => e[1]) + .map((e) => ({ value: e[0] })), ]); } }); @@ -1244,4 +1287,3 @@ Template.selectSpinnerName.helpers({ return Template.instance().data.spinnerName === match; }, }); - diff --git a/client/components/sidebar/sidebarArchives.jade b/client/components/sidebar/sidebarArchives.jade index 0cad38dac..287533e09 100644 --- a/client/components/sidebar/sidebarArchives.jade +++ b/client/components/sidebar/sidebarArchives.jade @@ -20,7 +20,7 @@ template(name="archivesSidebar") p.quiet if this.archivedAt | {{_ 'archived-at' }} - | | {{ moment this.archivedAt 'LLL' }} + | | {{ displayDate this.archivedAt 'LLL' }} br a.js-restore-card {{_ 'restore'}} if currentUser.isBoardAdmin @@ -51,7 +51,7 @@ template(name="archivesSidebar") p.quiet if this.archivedAt | {{_ 'archived-at' }} - | | {{ moment this.archivedAt 'LLL' }} + | | {{ displayDate this.archivedAt 'LLL' }} br a.js-restore-list {{_ 'restore'}} if currentUser.isBoardAdmin @@ -80,7 +80,7 @@ template(name="archivesSidebar") p.quiet if this.archivedAt | {{_ 'archived-at' }} - | | {{ moment this.archivedAt 'LLL' }} + | | {{ displayDate this.archivedAt 'LLL' }} br a.js-restore-swimlane {{_ 'restore'}} if currentUser.isBoardAdmin diff --git a/client/config/blazeHelpers.js b/client/config/blazeHelpers.js index beef694fa..6d74b658e 100644 --- a/client/config/blazeHelpers.js +++ b/client/config/blazeHelpers.js @@ -7,25 +7,25 @@ import { } from '/imports/lib/customHeadDefaults'; import { Blaze } from 'meteor/blaze'; import { Session } from 'meteor/session'; -import { - formatDateTime, - formatDate, - formatTime, - getISOWeek, - isValidDate, - isBefore, - isAfter, - isSame, - add, - subtract, - startOf, - endOf, - format, - parseDate, - now, - createDate, - fromNow, - calendar +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar, } from '/imports/lib/dateUtils'; Blaze.registerHelper('currentBoard', () => { @@ -85,7 +85,7 @@ Blaze.registerHelper('currentUser', () => { return ret; }); -Blaze.registerHelper('getUser', userId => ReactiveCache.getUser(userId)); +Blaze.registerHelper('getUser', (userId) => ReactiveCache.getUser(userId)); Blaze.registerHelper('concat', (...args) => args.slice(0, -1).join('')); @@ -101,23 +101,17 @@ Blaze.registerHelper('isTouchScreenOrShowDesktopDragHandles', () => Utils.isTouchScreenOrShowDesktopDragHandles(), ); -Blaze.registerHelper('moment', (...args) => { +Blaze.registerHelper('displayDate', (...args) => { args.pop(); // hash const [date, formatStr] = args; return format(new Date(date), formatStr ?? 'LLLL'); }); -Blaze.registerHelper('canModifyCard', () => - Utils.canModifyCard(), -); +Blaze.registerHelper('canModifyCard', () => Utils.canModifyCard()); -Blaze.registerHelper('canMoveCard', () => - Utils.canMoveCard(), -); +Blaze.registerHelper('canMoveCard', () => Utils.canMoveCard()); -Blaze.registerHelper('canModifyBoard', () => - Utils.canModifyBoard(), -); +Blaze.registerHelper('canModifyBoard', () => Utils.canModifyBoard()); Blaze.registerHelper('add', (a, b) => a + b); diff --git a/imports/lib/dateUtils.js b/imports/lib/dateUtils.js index a36ee469d..46df69382 100644 --- a/imports/lib/dateUtils.js +++ b/imports/lib/dateUtils.js @@ -43,7 +43,11 @@ export function formatDate(date) { * @param {boolean} includeTime - Whether to include time (HH:MM) * @returns {string} Formatted date string */ -export function formatDateByUserPreference(date, format = 'YYYY-MM-DD', includeTime = true) { +export function formatDateByUserPreference( + date, + format = 'YYYY-MM-DD', + includeTime = true, +) { const d = new Date(date); if (isNaN(d.getTime())) return ''; @@ -108,7 +112,7 @@ export function getISOWeek(date) { const firstThursday = target.valueOf(); target.setMonth(0, 1); if (target.getDay() !== 4) { - target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7); + target.setMonth(0, 1 + ((4 - target.getDay() + 7) % 7)); } return 1 + Math.ceil((firstThursday - target) / 604800000); // 604800000 = 7 * 24 * 3600 * 1000 @@ -141,16 +145,31 @@ export function isBefore(date1, date2, unit = 'millisecond') { case 'year': return d1.getFullYear() < d2.getFullYear(); case 'month': - return d1.getFullYear() < d2.getFullYear() || - (d1.getFullYear() === d2.getFullYear() && d1.getMonth() < d2.getMonth()); + return ( + d1.getFullYear() < d2.getFullYear() || + (d1.getFullYear() === d2.getFullYear() && d1.getMonth() < d2.getMonth()) + ); case 'day': - return d1.getFullYear() < d2.getFullYear() || - (d1.getFullYear() === d2.getFullYear() && d1.getMonth() < d2.getMonth()) || - (d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() < d2.getDate()); + return ( + d1.getFullYear() < d2.getFullYear() || + (d1.getFullYear() === d2.getFullYear() && + d1.getMonth() < d2.getMonth()) || + (d1.getFullYear() === d2.getFullYear() && + d1.getMonth() === d2.getMonth() && + d1.getDate() < d2.getDate()) + ); case 'hour': - return d1.getTime() < d2.getTime() && Math.floor(d1.getTime() / (1000 * 60 * 60)) < Math.floor(d2.getTime() / (1000 * 60 * 60)); + return ( + d1.getTime() < d2.getTime() && + Math.floor(d1.getTime() / (1000 * 60 * 60)) < + Math.floor(d2.getTime() / (1000 * 60 * 60)) + ); case 'minute': - return d1.getTime() < d2.getTime() && Math.floor(d1.getTime() / (1000 * 60)) < Math.floor(d2.getTime() / (1000 * 60)); + return ( + d1.getTime() < d2.getTime() && + Math.floor(d1.getTime() / (1000 * 60)) < + Math.floor(d2.getTime() / (1000 * 60)) + ); default: return d1.getTime() < d2.getTime(); } @@ -184,13 +203,25 @@ export function isSame(date1, date2, unit = 'millisecond') { case 'year': return d1.getFullYear() === d2.getFullYear(); case 'month': - return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth(); + return ( + d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() + ); case 'day': - return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate(); + return ( + d1.getFullYear() === d2.getFullYear() && + d1.getMonth() === d2.getMonth() && + d1.getDate() === d2.getDate() + ); case 'hour': - return Math.floor(d1.getTime() / (1000 * 60 * 60)) === Math.floor(d2.getTime() / (1000 * 60 * 60)); + return ( + Math.floor(d1.getTime() / (1000 * 60 * 60)) === + Math.floor(d2.getTime() / (1000 * 60 * 60)) + ); case 'minute': - return Math.floor(d1.getTime() / (1000 * 60)) === Math.floor(d2.getTime() / (1000 * 60)); + return ( + Math.floor(d1.getTime() / (1000 * 60)) === + Math.floor(d2.getTime() / (1000 * 60)) + ); default: return d1.getTime() === d2.getTime(); } @@ -350,6 +381,8 @@ export function format(date, format = 'L') { return `${year}-${month}-${day}`; case 'YYYY-MM-DD HH:mm': return `${year}-${month}-${day} ${hours}:${minutes}`; + case 'YYYY-MM-DD HH:mm:ss': + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; case 'HH:mm': return `${hours}:${minutes}`; default: @@ -384,7 +417,7 @@ export function parseDate(dateString, formats = [], strict = true) { 'DD/MM/YYYY HH:mm', 'DD/MM/YYYY', 'DD-MM-YYYY HH:mm', - 'DD-MM-YYYY' + 'DD-MM-YYYY', ]; const allFormats = [...formats, ...commonFormats]; @@ -408,12 +441,12 @@ export function parseDate(dateString, formats = [], strict = true) { function parseWithFormat(dateString, format) { // Simple format parsing - can be extended as needed const formatMap = { - 'YYYY': '\\d{4}', - 'MM': '\\d{2}', - 'DD': '\\d{2}', - 'HH': '\\d{2}', - 'mm': '\\d{2}', - 'ss': '\\d{2}' + YYYY: '\\d{4}', + MM: '\\d{2}', + DD: '\\d{2}', + HH: '\\d{2}', + mm: '\\d{2}', + ss: '\\d{2}', }; let regex = format; @@ -425,11 +458,21 @@ function parseWithFormat(dateString, format) { if (!match) return null; const groups = match.slice(1); - let year, month, day, hour = 0, minute = 0, second = 0; + let year, + month, + day, + hour = 0, + minute = 0, + second = 0; let groupIndex = 0; for (let i = 0; i < format.length; i++) { - if (format[i] === 'Y' && format[i + 1] === 'Y' && format[i + 2] === 'Y' && format[i + 3] === 'Y') { + if ( + format[i] === 'Y' && + format[i + 1] === 'Y' && + format[i + 2] === 'Y' && + format[i + 3] === 'Y' + ) { year = parseInt(groups[groupIndex++]); i += 3; } else if (format[i] === 'M' && format[i + 1] === 'M') { @@ -501,11 +544,15 @@ export function fromNow(date, now = new Date()) { const diffYears = Math.floor(diffDays / 365); if (diffSeconds < 60) return 'a few seconds ago'; - if (diffMinutes < 60) return `${diffMinutes} minute${diffMinutes !== 1 ? 's' : ''} ago`; - if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`; + if (diffMinutes < 60) + return `${diffMinutes} minute${diffMinutes !== 1 ? 's' : ''} ago`; + if (diffHours < 24) + return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`; if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`; - if (diffWeeks < 4) return `${diffWeeks} week${diffWeeks !== 1 ? 's' : ''} ago`; - if (diffMonths < 12) return `${diffMonths} month${diffMonths !== 1 ? 's' : ''} ago`; + if (diffWeeks < 4) + return `${diffWeeks} week${diffWeeks !== 1 ? 's' : ''} ago`; + if (diffMonths < 12) + return `${diffMonths} month${diffMonths !== 1 ? 's' : ''} ago`; return `${diffYears} year${diffYears !== 1 ? 's' : ''} ago`; } diff --git a/package-lock.json b/package-lock.json index eda9a5d7b..2eb1fb617 100644 --- a/package-lock.json +++ b/package-lock.json @@ -176,12 +176,12 @@ "integrity": "sha512-DAz2ZDtUn7dd0Zol1wdKkhSG4U+OwlDcGzeu1t8XwWh9SKtfTaIaMYTqTvJfAg2B3ilIHp2k64c5mqOiRq5lWQ==" }, "@wekanteam/exceljs": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@wekanteam/exceljs/-/exceljs-4.6.0.tgz", - "integrity": "sha512-R5var++3oPGTbfPrswOuQQEP8XsookaErND1vHkVkpnCuirCAcmEiLLdcakAJHFQVwxDANpN4lYzS1qSXSXCPg==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@wekanteam/exceljs/-/exceljs-4.5.1.tgz", + "integrity": "sha512-qEWJKSjExu7YJ07YSp3BVj8UvVz1hQR7yh18XdxOn7Wu41wXjbcFpXuMr8GNtj11mE33z5xdUyADcrKLJVfVLQ==", "requires": { "archiver": "^5.0.0", - "dayjs": "^1.8.34", + "dayjs": "^1.11.19", "fast-csv": "^4.3.1", "jszip": "^3.10.1", "readable-stream": "^3.6.0", @@ -2235,9 +2235,9 @@ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "requires": { "brace-expansion": "^1.1.7" } @@ -2489,9 +2489,9 @@ } }, "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", + "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", "requires": { "brace-expansion": "^2.0.1" } diff --git a/packages/wekan-fullcalendar/package.js b/packages/wekan-fullcalendar/package.js index 38e8d64fc..d0f922f2c 100644 --- a/packages/wekan-fullcalendar/package.js +++ b/packages/wekan-fullcalendar/package.js @@ -1,21 +1,30 @@ Package.describe({ - name: 'wekan-fullcalendar', - summary: "Full-sized drag & drop event calendar (jQuery plugin)", - version: "3.10.5", - git: "https://github.com/fullcalendar/fullcalendar.git" + name: 'wekan-fullcalendar', + summary: 'Full-sized drag & drop event calendar (jQuery plugin)', + version: '5.11.5', + git: 'https://github.com/fullcalendar/fullcalendar.git', +}); + +Npm.depends({ + '@fullcalendar/core': '5.11.5', + '@fullcalendar/daygrid': '5.11.5', + '@fullcalendar/interaction': '5.11.5', + '@fullcalendar/list': '5.11.5', + '@fullcalendar/timegrid': '5.11.5', }); Package.onUse(function(api) { - api.use([ - 'momentjs:moment', - 'templating' - ], 'client'); - api.addFiles([ - 'template.html', - 'template.js', - 'fullcalendar/fullcalendar.js', - 'fullcalendar/fullcalendar.css', - 'fullcalendar/locale-all.js', - 'fullcalendar/gcal.js', - ], 'client'); + api.versionsFrom(['2.16', '3.0']); + api.use(['ecmascript', 'templating', 'tracker'], 'client'); + api.addFiles( + [ + '.npm/package/node_modules/@fullcalendar/common/main.min.css', + '.npm/package/node_modules/@fullcalendar/daygrid/main.min.css', + '.npm/package/node_modules/@fullcalendar/timegrid/main.min.css', + '.npm/package/node_modules/@fullcalendar/list/main.min.css', + 'template.html', + 'template.js', + ], + 'client', + ); }); diff --git a/packages/wekan-fullcalendar/template.js b/packages/wekan-fullcalendar/template.js index a41d21d3d..069aa748c 100644 --- a/packages/wekan-fullcalendar/template.js +++ b/packages/wekan-fullcalendar/template.js @@ -1,11 +1,86 @@ -window.moment = moment; +import { Template } from 'meteor/templating'; +import { Tracker } from 'meteor/tracker'; -Template.fullcalendar.rendered = function() { - var div = this.$(this.firstNode); - if(this.data != null) { - //jquery takes care of undefined values, no need to check here - div.attr('id', this.data.id); - div.addClass(this.data.class); +const FullCalendarCore = require('@fullcalendar/core/main.cjs.js'); +const FullCalendarDayGrid = require('@fullcalendar/daygrid/main.cjs.js'); +const FullCalendarInteraction = require('@fullcalendar/interaction/main.cjs.js'); +const FullCalendarList = require('@fullcalendar/list/main.cjs.js'); +const FullCalendarTimeGrid = require('@fullcalendar/timegrid/main.cjs.js'); +const FullCalendarLocalesAll = require('@fullcalendar/core/locales-all.js'); + +Template.fullcalendar.onRendered(function () { + const container = this.find('div'); + + this.autorunHandle = Tracker.autorun(() => { + const data = Template.currentData() || {}; + let preservedViewType = null; + let preservedDate = null; + + if (!container) { + return; } - div.fullCalendar(this.data); -}; + + container.id = data.id || ''; + container.className = data.class || ''; + + const options = { ...data }; + delete options.id; + delete options.class; + if (options.defaultView && !options.initialView) { + options.initialView = options.defaultView; + } + delete options.defaultView; + if (options.header && !options.headerToolbar) { + options.headerToolbar = options.header; + } + delete options.header; + + if (!options.locales && FullCalendarLocalesAll && FullCalendarLocalesAll.default) { + options.locales = FullCalendarLocalesAll.default; + } + + if (this.calendar) { + // Keep the user's current view/date when reactive data updates. + if (this.calendar.view && this.calendar.view.type) { + preservedViewType = this.calendar.view.type; + } + if (this.calendar.getDate) { + preservedDate = this.calendar.getDate(); + } + this.calendar.destroy(); + this.calendar = null; + } + + if (preservedViewType && !options.initialView) { + options.initialView = preservedViewType; + } + if (preservedDate && !options.initialDate) { + options.initialDate = preservedDate; + } + + this.calendar = new FullCalendarCore.Calendar(container, { + plugins: [ + FullCalendarDayGrid.default, + FullCalendarInteraction.default, + FullCalendarList.default, + FullCalendarTimeGrid.default, + ], + ...options, + }); + + // Allow callers to manually access and refetch without jQuery plugin API. + container._wekanCalendar = this.calendar; + this.calendar.render(); + }); +}); + +Template.fullcalendar.onDestroyed(function () { + if (this.autorunHandle) { + this.autorunHandle.stop(); + this.autorunHandle = null; + } + if (this.calendar) { + this.calendar.destroy(); + this.calendar = null; + } +});