mirror of
https://github.com/wekan/wekan.git
synced 2026-11-07 11:02:33 -05:00
Opening a card writes the user document (profile.cardLastViews, #3078). The board view (Utils.storedBoardView and allowBoardView, which every list's card loop reads) and the helpers every minicard runs read the WHOLE user document, so that one write re-ran every list's card loop, and Blaze then re-evaluated every minicard: 37,452 Tracker invalidations for one card open on a 40-card board. Measured in the running app, it is now 390. client/lib/currentUserWith.js reads the current user with only the named fields; minimongo re-runs a field-limited query only when a projected field changes. The board view, feature preview, unread comments, folds, drag handles, label text, dependency layers, date format, week number, mobile mode and the admin-only custom field check now use it. setCardLastViewed $sets one entry instead of rewriting the whole map, which is also capped at 5,000. Thanks to markusst1982 and xet7 ! Fixes #6745.
140 lines
6.4 KiB
JavaScript
140 lines
6.4 KiB
JavaScript
import { Utils } from '/client/lib/utils';
|
||
const { resolveDateFormat } = require('/models/lib/dateFormatPolicy');
|
||
import { ReactiveCache } from '/imports/reactiveCache';
|
||
import { currentUserWith } from '/client/lib/currentUserWith';
|
||
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
|
||
import { formatJalaliDate, gregorianToJalali } from '/imports/lib/jalaliDate';
|
||
import { TAPi18n } from '/imports/i18n';
|
||
const { CALENDAR_SYSTEMS, formatNativeCalendarDate, nativeCalendarParts } = require('/imports/lib/calendarSystems');
|
||
const { calendarMonthRange, shiftedDay } = require('/imports/lib/calendarMonth');
|
||
|
||
// #6745: every date badge on every minicard formats through here, so read only
|
||
// the date fields of the user - a card open (a user write) must not re-run them.
|
||
const DATE_USER_FIELDS = ['profile.calendarSystem', 'profile.dateFormat', 'profile.dateFormatOverride'];
|
||
|
||
export function hasDateFormatPreference() {
|
||
return !!(currentUserWith(DATE_USER_FIELDS)?.profile?.dateFormatOverride ||
|
||
Utils.getCurrentBoard()?.dateFormatOverride || ReactiveCache.getCurrentSetting()?.hideDateFormat);
|
||
}
|
||
|
||
export function dateDisplayPreferences() {
|
||
const setting = ReactiveCache.getCurrentSetting();
|
||
const user = currentUserWith(DATE_USER_FIELDS);
|
||
const board = Utils.getCurrentBoard();
|
||
if (user) {
|
||
return {
|
||
calendarSystem: user.getCalendarSystem ? user.getCalendarSystem() : 'gregorian',
|
||
dateFormat: resolveDateFormat(user.getDateFormat ? user.getDateFormat() : null, setting, board, user.profile?.dateFormatOverride),
|
||
};
|
||
}
|
||
try {
|
||
return {
|
||
calendarSystem: window.localStorage.getItem('calendarSystem') || 'gregorian',
|
||
dateFormat: resolveDateFormat(null, setting, board),
|
||
};
|
||
} catch (error) {
|
||
return { calendarSystem: 'gregorian', dateFormat: resolveDateFormat(null, setting, board) };
|
||
}
|
||
}
|
||
|
||
// Only rendered text belongs here. ISO attributes, picker values and persisted
|
||
// dates must continue to use Gregorian formatting. A supplied callback keeps
|
||
// each existing Gregorian display's locale, precision and layout.
|
||
export function formatDateForDisplay(date, includeTime = true, gregorianFormatter) {
|
||
if (date === null || date === undefined || date === '') return '';
|
||
const value = new Date(date);
|
||
if (Number.isNaN(value.getTime())) return '';
|
||
const { calendarSystem, dateFormat } = dateDisplayPreferences();
|
||
if (calendarSystem === 'jalali') {
|
||
try {
|
||
return formatJalaliDate(value, dateFormat, includeTime);
|
||
} catch (error) {
|
||
// Intl's Persian calendar covers dates beyond the tabular converter's
|
||
// finite range without displaying a different calendar.
|
||
return formatNativeCalendarDate(value, 'persian', dateFormat, includeTime, TAPi18n.getLanguage() || 'en') || '';
|
||
}
|
||
} else if (calendarSystem !== 'gregorian') {
|
||
const system = CALENDAR_SYSTEMS.find(item => item.value === calendarSystem);
|
||
const text = system && formatNativeCalendarDate(
|
||
value, system.intl, dateFormat, includeTime, TAPi18n.getLanguage() || 'en',
|
||
);
|
||
if (system) return text || '';
|
||
}
|
||
return gregorianFormatter && !hasDateFormatPreference() && !dateFormat.endsWith('-date-only')
|
||
? gregorianFormatter(value)
|
||
: formatDateByUserPreference(value, dateFormat, includeTime);
|
||
}
|
||
|
||
// FullCalendar 5.11.5 exposes its displayed wall-calendar fields to formatting
|
||
// callbacks. Construct from those fields instead of shifting its UTC marker
|
||
// through the browser's time zone. Grid placement and navigation stay native.
|
||
export function calendarDateDisplayOptions(calendarId) {
|
||
const { calendarSystem } = dateDisplayPreferences();
|
||
if (calendarSystem === 'gregorian') return {};
|
||
const system = CALENDAR_SYSTEMS.find(item => item.value === calendarSystem);
|
||
if (!system) return {};
|
||
const displayMarker = marker => formatDateForDisplay(
|
||
new Date(marker.year, marker.month, marker.day, 12), false,
|
||
);
|
||
const navigate = direction => {
|
||
const calendar = document.getElementById(calendarId)?._wekanCalendar;
|
||
if (!calendar) return;
|
||
if (!['selectedCalendarMonth', 'selectedCalendarListMonth'].includes(calendar.view.type)) {
|
||
calendar[direction < 0 ? 'prev' : 'next']();
|
||
return;
|
||
}
|
||
const range = calendarMonthRange(calendar.getDate(), system.intl);
|
||
calendar.gotoDate(direction < 0 ? shiftedDay(range.start, -1) : range.end);
|
||
};
|
||
return {
|
||
// Built-in month views have a fixed Gregorian duration that takes
|
||
// precedence over visibleRange. Inherit the duration-free base views.
|
||
initialView: 'selectedCalendarMonth',
|
||
views: {
|
||
selectedCalendarMonth: {
|
||
type: 'dayGrid', monthMode: true, fixedWeekCount: false,
|
||
visibleRange: date => calendarMonthRange(date, system.intl),
|
||
},
|
||
selectedCalendarListMonth: {
|
||
type: 'list', visibleRange: date => calendarMonthRange(date, system.intl),
|
||
},
|
||
},
|
||
customButtons: {
|
||
dayGridMonth: { text: TAPi18n.__('month'), click: () => {
|
||
document.getElementById(calendarId)?._wekanCalendar.changeView('selectedCalendarMonth');
|
||
} },
|
||
listMonth: { text: TAPi18n.__('list'), click: () => {
|
||
document.getElementById(calendarId)?._wekanCalendar.changeView('selectedCalendarListMonth');
|
||
} },
|
||
prev: { text: TAPi18n.__('previous'), click: () => navigate(-1) },
|
||
next: { text: TAPi18n.__('next'), click: () => navigate(1) },
|
||
},
|
||
titleFormat(info) {
|
||
const start = displayMarker(info.start);
|
||
const end = info.end && displayMarker(info.end);
|
||
return end && end !== start ? `${start}${info.defaultSeparator || ' – '}${end}` : start;
|
||
},
|
||
dayCellContent(info) {
|
||
try {
|
||
if (calendarSystem !== 'jalali') {
|
||
const parts = nativeCalendarParts(info.date, system.intl, TAPi18n.getLanguage() || 'en');
|
||
return parts ? parts.day : info.dayNumberText;
|
||
}
|
||
return String(gregorianToJalali(
|
||
info.date.getFullYear(), info.date.getMonth() + 1, info.date.getDate(),
|
||
).jd);
|
||
} catch (error) {
|
||
return info.dayNumberText;
|
||
}
|
||
},
|
||
dayHeaderContent(info) {
|
||
// Month-view column headings contain weekdays, with no date to convert.
|
||
return ['dayGridMonth', 'selectedCalendarMonth'].includes(info.view.type)
|
||
? info.text : formatDateForDisplay(info.date, false);
|
||
},
|
||
listDayFormat(info) { return displayMarker(info.date); },
|
||
// The list header has two date slots. Use one selected-calendar date.
|
||
listDaySideFormat() { return ''; },
|
||
};
|
||
}
|