mirror of
https://github.com/discourse/discourse.git
synced 2025-02-14 14:22:46 +08:00
![David Taylor](/assets/img/avatar_default.png)
`discourse-common` was created in the past to share logic between the 'wizard' app and the main 'discourse' app. Since then, the wizard has been consolidated into the main app, so the separation of `discourse-common` is no longer useful. This commit moves `discourse-common/(lib|utils)/*` into `discourse/lib/*`, adds shims for the imports, and updates existing uses in core.
306 lines
8.4 KiB
JavaScript
306 lines
8.4 KiB
JavaScript
import { renderIcon } from "discourse/lib/icon-library";
|
|
import { i18n } from "discourse-i18n";
|
|
import DateWithZoneHelper from "./date-with-zone-helper";
|
|
|
|
const DATETIME_FORMAT = "LLL";
|
|
const DATE_FORMAT = "LL";
|
|
const FULL_DATETIME_FORMAT = "LLLL";
|
|
const TIME_FORMAT = "h:mm A";
|
|
const DAY_OF_THE_WEEK_FORMAT = "dddd";
|
|
const RANGE_SEPARATOR = "→";
|
|
const TIME_ICON = "clock";
|
|
const SHORT_FORMAT_DAYS_PERIOD = 1;
|
|
|
|
export default class LocalDateBuilder {
|
|
constructor(params = {}, localTimezone) {
|
|
this.time = params.time;
|
|
this.date = params.date;
|
|
this.recurring = params.recurring;
|
|
this.sameLocalDayAsFrom = params.sameLocalDayAsFrom;
|
|
this.timezones = Array.from(
|
|
new Set((params.timezones || []).filter(Boolean))
|
|
);
|
|
this.timezone = params.timezone || "UTC";
|
|
this.calendar =
|
|
typeof params.calendar === "undefined" ? true : params.calendar;
|
|
this.displayedTimezone = params.displayedTimezone;
|
|
this.format = params.format || (this.time ? DATETIME_FORMAT : DATE_FORMAT);
|
|
this.countdown = params.countdown;
|
|
this.duration = params.duration;
|
|
this.localTimezone = localTimezone;
|
|
}
|
|
|
|
build() {
|
|
const [year, month, day] = this.date.split("-").map((x) => parseInt(x, 10));
|
|
const [hour, minute, second] = (this.time || "")
|
|
.split(":")
|
|
.map((x) => (x ? parseInt(x, 10) : undefined));
|
|
|
|
let displayedTimezone;
|
|
if (this.time) {
|
|
displayedTimezone = this.displayedTimezone || this.localTimezone;
|
|
} else {
|
|
displayedTimezone =
|
|
this.displayedTimezone || this.timezone || this.localTimezone;
|
|
}
|
|
|
|
let localDate = new DateWithZoneHelper({
|
|
year,
|
|
month: month ? month - 1 : null,
|
|
day,
|
|
hour,
|
|
minute,
|
|
second,
|
|
timezone: this.timezone,
|
|
localTimezone: this.localTimezone,
|
|
});
|
|
|
|
if (this.recurring && moment().isAfter(localDate.datetime)) {
|
|
const type = this.recurring.split(".")[1];
|
|
|
|
const repetitionsForType = localDate.unitRepetitionsBetweenDates(
|
|
this.recurring,
|
|
moment.tz(this.localTimezone)
|
|
);
|
|
|
|
localDate = localDate.add(repetitionsForType, type);
|
|
}
|
|
|
|
const previews = this._generatePreviews(localDate, displayedTimezone);
|
|
const hasTime = hour !== undefined;
|
|
return {
|
|
pastEvent:
|
|
!this.recurring &&
|
|
moment.tz(this.localTimezone).isAfter(localDate.datetime),
|
|
formatted: this._applyFormatting(localDate, displayedTimezone, hasTime),
|
|
previews,
|
|
textPreview: this._generateTextPreviews(previews),
|
|
};
|
|
}
|
|
|
|
_generateTextPreviews(previews) {
|
|
return previews
|
|
.map((preview) => {
|
|
const formattedZone = this._zoneWithoutPrefix(preview.timezone);
|
|
return `${formattedZone} ${preview.formatted}`;
|
|
})
|
|
.join(", ");
|
|
}
|
|
|
|
_generatePreviews(localDate, displayedTimezone) {
|
|
const previewedTimezones = [];
|
|
|
|
const timezones = this.timezones.filter(
|
|
(timezone) => !this._isEqualZones(timezone, this.localTimezone)
|
|
);
|
|
|
|
previewedTimezones.push({
|
|
timezone: this._zoneWithoutPrefix(this.localTimezone),
|
|
current: true,
|
|
formatted: this._createDateTimeRange(
|
|
DateWithZoneHelper.fromDatetime(
|
|
localDate.datetime,
|
|
localDate.timezone,
|
|
this.localTimezone
|
|
),
|
|
this.time,
|
|
this.duration
|
|
),
|
|
});
|
|
|
|
if (
|
|
this.timezone &&
|
|
displayedTimezone === this.localTimezone &&
|
|
this.timezone !== displayedTimezone &&
|
|
!this._isEqualZones(displayedTimezone, this.timezone) &&
|
|
!this.timezones.any((t) => this._isEqualZones(t, this.timezone))
|
|
) {
|
|
timezones.unshift(this.timezone);
|
|
}
|
|
|
|
timezones.forEach((timezone) => {
|
|
if (this._isEqualZones(timezone, displayedTimezone)) {
|
|
return;
|
|
}
|
|
|
|
if (this._isEqualZones(timezone, this.localTimezone)) {
|
|
timezone = this.localTimezone;
|
|
}
|
|
|
|
previewedTimezones.push({
|
|
timezone: this._zoneWithoutPrefix(timezone),
|
|
formatted: this._createDateTimeRange(
|
|
DateWithZoneHelper.fromDatetime(
|
|
localDate.datetime,
|
|
localDate.timezone,
|
|
timezone
|
|
),
|
|
this.time,
|
|
this.duration
|
|
),
|
|
});
|
|
});
|
|
|
|
return previewedTimezones.uniqBy("timezone");
|
|
}
|
|
|
|
_isEqualZones(timezoneA, timezoneB) {
|
|
if ((timezoneA || timezoneB) && (!timezoneA || !timezoneB)) {
|
|
return false;
|
|
}
|
|
|
|
if (timezoneA.includes(timezoneB) || timezoneB.includes(timezoneA)) {
|
|
return true;
|
|
}
|
|
|
|
return (
|
|
moment.tz(timezoneA).utcOffset() === moment.tz(timezoneB).utcOffset()
|
|
);
|
|
}
|
|
|
|
_createDateTimeRange(startRange, time, duration) {
|
|
const [startDate, endDate] = this._calculateDatesForRange(
|
|
startRange,
|
|
time,
|
|
duration
|
|
);
|
|
let formatElements = [
|
|
startDate.format(`${DAY_OF_THE_WEEK_FORMAT}, ${DATE_FORMAT}`),
|
|
this._optionalTimeIcon(startDate, endDate),
|
|
startDate.format(TIME_FORMAT),
|
|
];
|
|
if (endDate) {
|
|
formatElements = formatElements.concat([
|
|
RANGE_SEPARATOR,
|
|
endDate.format(this._endDateFormat(startDate, endDate)),
|
|
]);
|
|
}
|
|
return formatElements.filter(Boolean).join(" ");
|
|
}
|
|
|
|
_shortFormat(startDate, endDate) {
|
|
return (
|
|
endDate.datetime.diff(startDate.datetime, "days") <
|
|
SHORT_FORMAT_DAYS_PERIOD
|
|
);
|
|
}
|
|
|
|
_optionalTimeIcon(startDate, endDate) {
|
|
if (!endDate || this._shortFormat(startDate, endDate)) {
|
|
return `<br />${renderIcon("string", TIME_ICON)}`;
|
|
}
|
|
}
|
|
|
|
_endDateFormat(startDate, endDate) {
|
|
return this._shortFormat(startDate, endDate)
|
|
? TIME_FORMAT
|
|
: FULL_DATETIME_FORMAT;
|
|
}
|
|
|
|
_calculateDatesForRange(date, time, duration) {
|
|
// if a time has been given we do not attempt to automatically create a range
|
|
// instead we show only one date with a format showing the time
|
|
if (time && !duration) {
|
|
return [date];
|
|
}
|
|
const dates = [
|
|
date,
|
|
duration ? date.add(duration, "minutes") : date.add(24, "hours"),
|
|
];
|
|
return duration < 0 ? dates.reverse() : dates;
|
|
}
|
|
|
|
_applyFormatting(localDate, displayedTimezone, hasTime) {
|
|
if (this.countdown) {
|
|
const diffTime = moment.tz(this.localTimezone).diff(localDate.datetime);
|
|
|
|
if (diffTime < 0) {
|
|
return moment.duration(diffTime).humanize();
|
|
} else {
|
|
return i18n("discourse_local_dates.relative_dates.countdown.passed");
|
|
}
|
|
}
|
|
|
|
const sameTimezone = this._isEqualZones(
|
|
displayedTimezone,
|
|
this.localTimezone
|
|
);
|
|
|
|
if (this.calendar) {
|
|
const inCalendarRange = moment
|
|
.tz(this.localTimezone)
|
|
.isBetween(
|
|
localDate.subtract(2, "day").datetime,
|
|
localDate.add(1, "day").datetime.endOf("day")
|
|
);
|
|
|
|
if (this.sameLocalDayAsFrom) {
|
|
return this._timeOnlyFormat(localDate, displayedTimezone);
|
|
}
|
|
|
|
if (inCalendarRange && sameTimezone) {
|
|
const date = localDate.datetimeWithZone(this.localTimezone);
|
|
|
|
if (hasTime && date.hours() === 0 && date.minutes() === 0) {
|
|
return date.format("dddd");
|
|
}
|
|
|
|
return date.calendar(
|
|
moment.tz(localDate.timezone),
|
|
this._calendarFormats(this.time ? this.time : null)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!sameTimezone) {
|
|
return this._formatWithZone(localDate, displayedTimezone, this.format);
|
|
}
|
|
|
|
return localDate.format(this.format);
|
|
}
|
|
|
|
_calendarFormats(time) {
|
|
return {
|
|
sameDay: this._translateCalendarKey(time, "today"),
|
|
nextDay: this._translateCalendarKey(time, "tomorrow"),
|
|
lastDay: this._translateCalendarKey(time, "yesterday"),
|
|
sameElse: "L",
|
|
};
|
|
}
|
|
|
|
_translateCalendarKey(time, key) {
|
|
const translated = i18n(`discourse_local_dates.relative_dates.${key}`, {
|
|
time: "LT",
|
|
});
|
|
|
|
if (time) {
|
|
return translated
|
|
.split("LT")
|
|
.map((w) => `[${w}]`)
|
|
.join("LT");
|
|
} else {
|
|
return `[${translated.replace(" LT", "")}]`;
|
|
}
|
|
}
|
|
|
|
_formatTimezone(timezone) {
|
|
return timezone.replace("_", " ").replace("Etc/", "").split("/");
|
|
}
|
|
|
|
_zoneWithoutPrefix(timezone) {
|
|
const [part1, part2] = this._formatTimezone(timezone);
|
|
return part2 || part1;
|
|
}
|
|
|
|
_formatWithZone(localDate, displayedTimezone, format) {
|
|
let formatted = localDate
|
|
.datetimeWithZone(displayedTimezone)
|
|
.format(format);
|
|
return `${formatted} (${this._zoneWithoutPrefix(displayedTimezone)})`;
|
|
}
|
|
|
|
_timeOnlyFormat(localTime, displayedTimezone) {
|
|
return this._formatWithZone(localTime, displayedTimezone, "LT");
|
|
}
|
|
}
|