mirror of
https://github.com/wekan/wekan.git
synced 2026-11-07 11:02:33 -05:00
Board Settings -> Date has Calendar sync. A board admin gives a calendar collection's address, a user and an app password made for WeKan, and turns it on. Each card with a due date is one event, <cardId>.ics, UID <cardId>@wekan, an hour long, with the card's title and link; a card that loses its due date or is archived has its event removed. Every 15 minutes the events are read back, and a due date moved in the calendar comes back to its card. Sync all now pushes every card at once. The link and its password are in a server-only collection that is never published, and only a board admin can read or change it. Requests go through webhookFetch: the SSRF guard, so a calendar on WeKan's own network is reached only when a site admin lists it under Internal destinations. Text values are escaped as RFC 5545 requires - a semicolon included, which the first version of the test did not catch. Thanks to scriptPilot and xet7 !
183 lines
7.0 KiB
JavaScript
183 lines
7.0 KiB
JavaScript
import { Utils } from '/client/lib/utils';
|
|
import { Meteor } from 'meteor/meteor';
|
|
import { Template } from 'meteor/templating';
|
|
import { ReactiveVar } from 'meteor/reactive-var';
|
|
import { ReactiveCache } from '/imports/reactiveCache';
|
|
import { TAPi18n } from '/imports/i18n';
|
|
import './dateFormatSettings.jade';
|
|
import './dateFormatSettings.css';
|
|
const { availableCalendarSystems } = require('/imports/lib/calendarSystems');
|
|
const { DATE_FORMATS } = require('/models/lib/dateFormatPolicy');
|
|
|
|
Template.dateFormatEditor.onCreated(function () {
|
|
this.boardId = Utils.getCurrentBoard()?._id;
|
|
this.error = new ReactiveVar('');
|
|
this.saving = new ReactiveVar(false);
|
|
this.overrideDraft = new ReactiveVar(null);
|
|
});
|
|
|
|
function preference(instance) {
|
|
return instance.data.scope === 'board'
|
|
? ReactiveCache.getBoard(instance.boardId)
|
|
: ReactiveCache.getCurrentUser()?.profile;
|
|
}
|
|
|
|
function formatLabel(value) {
|
|
const format = DATE_FORMATS.includes(value) ? value : DATE_FORMATS[0];
|
|
return TAPi18n.__(`date-format-${format.replace('-date-only', '').toLowerCase()}`) +
|
|
(format.endsWith('-date-only') ? '' : ` · ${TAPi18n.__('time')}`);
|
|
}
|
|
|
|
Template.dateFormatEditor.helpers({
|
|
weekDays(startDay) {
|
|
return [
|
|
TAPi18n.__('sunday'),
|
|
TAPi18n.__('monday'),
|
|
TAPi18n.__('tuesday'),
|
|
TAPi18n.__('wednesday'),
|
|
TAPi18n.__('thursday'),
|
|
TAPi18n.__('friday'),
|
|
TAPi18n.__('saturday'),
|
|
].map(function(day, index) {
|
|
return { name: day, value: index, isSelected: index === startDay };
|
|
});
|
|
},
|
|
startDayOfWeek() {
|
|
const currentUser = ReactiveCache.getCurrentUser();
|
|
if (currentUser) {
|
|
return currentUser.getStartDayOfWeek();
|
|
} else {
|
|
return window.localStorage.getItem('startDayOfWeek');
|
|
}
|
|
},
|
|
// Supported calendar choices are independent of the interface language.
|
|
calendarSystems() {
|
|
const currentUser = ReactiveCache.getCurrentUser();
|
|
const current = currentUser
|
|
? currentUser.getCalendarSystem()
|
|
: window.localStorage.getItem('calendarSystem') || 'gregorian';
|
|
return availableCalendarSystems().map(system => ({
|
|
...system,
|
|
name: TAPi18n.__(system.labelKey),
|
|
isSelected: system.value === current,
|
|
}));
|
|
},
|
|
isMember() { return Template.instance().data.scope === 'member'; },
|
|
globalEnabled() { return !!ReactiveCache.getCurrentSetting()?.hideDateFormat; },
|
|
globalFormat() { return formatLabel(ReactiveCache.getCurrentSetting()?.globalDateFormat); },
|
|
boardEnabled() {
|
|
const id = Template.instance().boardId;
|
|
return !!(id && ReactiveCache.getBoard(id)?.dateFormatOverride);
|
|
},
|
|
boardFormat() {
|
|
const id = Template.instance().boardId;
|
|
return formatLabel(id && ReactiveCache.getBoard(id)?.dateFormat);
|
|
},
|
|
enabled() {
|
|
const instance = Template.instance();
|
|
return instance.overrideDraft.get() ?? !!preference(instance)?.dateFormatOverride;
|
|
},
|
|
error() { return Template.instance().error.get(); },
|
|
saving() { return Template.instance().saving.get(); },
|
|
formats() {
|
|
const selected = preference(Template.instance())?.dateFormat || DATE_FORMATS[0];
|
|
return DATE_FORMATS.map(value => ({ value, selected: value === selected, label: formatLabel(value) }));
|
|
},
|
|
});
|
|
|
|
Template.dateFormatEditor.events({
|
|
'change .js-date-format-override'(event, instance) {
|
|
instance.overrideDraft.set(event.currentTarget.checked);
|
|
},
|
|
async 'submit .js-date-format-form'(event, instance) {
|
|
event.preventDefault();
|
|
if (instance.saving.get()) return;
|
|
instance.saving.set(true);
|
|
instance.error.set('');
|
|
const format = instance.find('.js-date-format-select').value;
|
|
const enabled = instance.find('.js-date-format-override').checked;
|
|
// Capture all inputs before reactive profile updates can rerender options.
|
|
const member = instance.data.scope === 'member';
|
|
const startDay = member ? Number(instance.find('#start-day-of-week').value) : null;
|
|
const calendar = member ? instance.find('#calendar-system').value : null;
|
|
try {
|
|
if (instance.data.scope === 'board') {
|
|
await Meteor.callAsync('setBoardDateFormat', instance.boardId, format, enabled);
|
|
} else {
|
|
await Meteor.callAsync('changeDateFormat', format, enabled);
|
|
await Meteor.callAsync('changeStartDayOfWeek', startDay);
|
|
await Meteor.callAsync('changeCalendarSystem', calendar);
|
|
}
|
|
Popup.back();
|
|
} catch (error) {
|
|
instance.error.set(error.reason || error.message);
|
|
} finally {
|
|
instance.saving.set(false);
|
|
}
|
|
},
|
|
});
|
|
|
|
// #687: the board's CalDAV link, for board admins (server/caldavSync.js).
|
|
Template.boardDateSettingsPopup.onCreated(function () {
|
|
this.caldav = new ReactiveVar({ url: '', username: '', enabled: false, hasPassword: false });
|
|
this.caldavMessage = new ReactiveVar('');
|
|
const board = Utils.getCurrentBoard();
|
|
if (board && ReactiveCache.getCurrentUser()?.isBoardAdmin()) {
|
|
Meteor.call('getCalDavLink', board._id, (error, link) => { if (!error && link) this.caldav.set(link); });
|
|
}
|
|
});
|
|
|
|
Template.boardDateSettingsPopup.helpers({
|
|
caldav() { return Template.instance().caldav.get(); },
|
|
caldavMessage() { return Template.instance().caldavMessage.get(); },
|
|
isShowWeekOfYear() {
|
|
const user = ReactiveCache.getCurrentUser();
|
|
if (!user) return window.localStorage.getItem('showWeekOfYear') === 'true';
|
|
return user.isShowWeekOfYear();
|
|
},
|
|
});
|
|
|
|
Template.boardDateSettingsPopup.events({
|
|
'click .js-caldav-enabled'(event, tpl) {
|
|
event.preventDefault();
|
|
tpl.caldav.set({ ...tpl.caldav.get(), enabled: !tpl.caldav.get().enabled });
|
|
},
|
|
async 'click .js-caldav-save, submit .js-caldav-link'(event, tpl) {
|
|
event.preventDefault();
|
|
const board = Utils.getCurrentBoard();
|
|
if (!board) return;
|
|
const input = { url: String(tpl.$('.js-caldav-url').val() || ''), username: String(tpl.$('.js-caldav-username').val() || ''),
|
|
enabled: !!tpl.caldav.get().enabled };
|
|
const password = String(tpl.$('.js-caldav-password').val() || '');
|
|
if (password) input.password = password;
|
|
try {
|
|
await Meteor.callAsync('saveCalDavLink', board._id, input);
|
|
tpl.caldav.set(await Meteor.callAsync('getCalDavLink', board._id));
|
|
tpl.$('.js-caldav-password').val('');
|
|
tpl.caldavMessage.set(TAPi18n.__('feature-saved'));
|
|
} catch (error) {
|
|
tpl.caldavMessage.set(TAPi18n.__(error.error) || error.reason || error.message);
|
|
}
|
|
},
|
|
async 'click .js-caldav-sync-now'(event, tpl) {
|
|
event.preventDefault();
|
|
const board = Utils.getCurrentBoard();
|
|
if (!board) return;
|
|
try {
|
|
const result = await Meteor.callAsync('syncCalDavNow', board._id);
|
|
tpl.caldavMessage.set(`${result.pushed} / ${result.total}`);
|
|
} catch (error) {
|
|
tpl.caldavMessage.set(TAPi18n.__(error.error) || error.reason || error.message);
|
|
}
|
|
},
|
|
'click .js-show-week-of-year-toggle'() {
|
|
const user = ReactiveCache.getCurrentUser();
|
|
if (user) {
|
|
user.toggleShowWeekOfYear();
|
|
} else {
|
|
const current = window.localStorage.getItem('showWeekOfYear') === 'true';
|
|
window.localStorage.setItem('showWeekOfYear', String(!current));
|
|
}
|
|
},
|
|
});
|