mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 18:03:38 +08:00
FEATURE: refactoring and better handling of special cases (#6666)
This commit is contained in:
parent
3453707784
commit
b0d08b5a8c
|
@ -1,141 +0,0 @@
|
|||
(function($) {
|
||||
$.fn.applyLocalDates = function(repeat) {
|
||||
function _formatTimezone(timezone) {
|
||||
return timezone.replace("_", " ").split("/");
|
||||
}
|
||||
|
||||
function processElement($element, options) {
|
||||
repeat = repeat || true;
|
||||
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
|
||||
var relativeTime;
|
||||
|
||||
var dateAndTime = options.date;
|
||||
if (options.time) {
|
||||
dateAndTime = dateAndTime + " " + options.time;
|
||||
}
|
||||
|
||||
if (options.timezone) {
|
||||
relativeTime = moment.tz(dateAndTime, options.timezone).utc();
|
||||
} else {
|
||||
relativeTime = moment.utc(dateAndTime);
|
||||
}
|
||||
|
||||
if (relativeTime < moment().utc()) {
|
||||
if (options.recurring) {
|
||||
var parts = options.recurring.split(".");
|
||||
var count = parseInt(parts[0], 10);
|
||||
var type = parts[1];
|
||||
var diff = moment().diff(relativeTime, type);
|
||||
var add = Math.ceil(diff + count);
|
||||
|
||||
relativeTime = relativeTime.add(add, type);
|
||||
} else {
|
||||
$element.addClass("past");
|
||||
}
|
||||
}
|
||||
|
||||
var previews = options.timezones.split("|").map(function(timezone) {
|
||||
var dateTime = relativeTime.tz(timezone).format(options.format);
|
||||
|
||||
var timezoneParts = _formatTimezone(timezone);
|
||||
|
||||
if (dateTime.match(/TZ/)) {
|
||||
return dateTime.replace("TZ", timezoneParts.join(": "));
|
||||
} else {
|
||||
var output = timezoneParts[0];
|
||||
if (timezoneParts[1]) {
|
||||
output += " (" + timezoneParts[1] + ")";
|
||||
}
|
||||
output += " " + dateTime;
|
||||
return output;
|
||||
}
|
||||
});
|
||||
|
||||
var relativeTime = relativeTime.tz(options.displayedZone);
|
||||
|
||||
var d = function(key) {
|
||||
var translated = I18n.t("discourse_local_dates.relative_dates." + key, {
|
||||
time: "LT"
|
||||
});
|
||||
|
||||
if (options.time) {
|
||||
return translated
|
||||
.split("LT")
|
||||
.map(function(w) {
|
||||
return "[" + w + "]";
|
||||
})
|
||||
.join("LT");
|
||||
} else {
|
||||
return "[" + translated.replace(" LT", "") + "]";
|
||||
}
|
||||
};
|
||||
|
||||
var relativeFormat = {
|
||||
sameDay: d("today"),
|
||||
nextDay: d("tomorrow"),
|
||||
lastDay: d("yesterday"),
|
||||
sameElse: "L"
|
||||
};
|
||||
|
||||
if (
|
||||
options.calendar &&
|
||||
relativeTime.isBetween(
|
||||
moment().subtract(1, "day"),
|
||||
moment().add(2, "day")
|
||||
)
|
||||
) {
|
||||
relativeTime = relativeTime.calendar(null, relativeFormat);
|
||||
} else {
|
||||
relativeTime = relativeTime.format(options.format);
|
||||
}
|
||||
|
||||
var html = "<span>";
|
||||
html += "<i class='fa fa-globe d-icon d-icon-globe'></i>";
|
||||
html += "<span class='relative-time'></span>";
|
||||
html += "</span>";
|
||||
|
||||
var joinedPreviews = previews.join("\n");
|
||||
|
||||
var displayedTime = relativeTime.replace(
|
||||
"TZ",
|
||||
_formatTimezone(options.displayedZone).join(": ")
|
||||
);
|
||||
|
||||
$element
|
||||
.html(html)
|
||||
.attr("title", joinedPreviews)
|
||||
.attr("data-tooltip", joinedPreviews)
|
||||
.addClass("cooked-date")
|
||||
.find(".relative-time")
|
||||
.text(displayedTime);
|
||||
|
||||
if (repeat) {
|
||||
this.timeout = setTimeout(function() {
|
||||
processElement($element, options);
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
return this.each(function() {
|
||||
var $this = $(this);
|
||||
|
||||
var options = {};
|
||||
options.time = $this.attr("data-time");
|
||||
options.format =
|
||||
$this.attr("data-format") || (options.time ? "LLL" : "LL");
|
||||
options.date = $this.attr("data-date");
|
||||
options.recurring = $this.attr("data-recurring");
|
||||
options.timezones = $this.attr("data-timezones") || "Etc/UTC";
|
||||
options.timezone = $this.attr("data-timezone");
|
||||
options.calendar = ($this.attr("data-calendar") || "on") === "on";
|
||||
options.displayedZone =
|
||||
$this.attr("data-displayed-zone") || moment.tz.guess();
|
||||
|
||||
processElement($this, options);
|
||||
});
|
||||
};
|
||||
})(jQuery);
|
|
@ -0,0 +1,283 @@
|
|||
(function($) {
|
||||
$.fn.applyLocalDates = function(repeat) {
|
||||
const processElement = ($element, options = {}) => {
|
||||
if (this.timeout) clearTimeout(this.timeout);
|
||||
|
||||
repeat = repeat || true;
|
||||
const utc = moment().utc();
|
||||
const dateTime = options.time
|
||||
? `${options.date} ${options.time}`
|
||||
: options.date;
|
||||
let utcDateTime;
|
||||
|
||||
let displayedTimezone;
|
||||
if (options.time) {
|
||||
displayedTimezone = options.displayedTimezone || moment.tz.guess();
|
||||
} else {
|
||||
displayedTimezone =
|
||||
options.displayedTimezone || options.timezone || moment.tz.guess();
|
||||
}
|
||||
|
||||
// if timezone given we convert date and time from given zone to Etc/UTC
|
||||
if (options.timezone) {
|
||||
utcDateTime = _applyZoneToDateTime(dateTime, options.timezone);
|
||||
} else {
|
||||
utcDateTime = moment.utc(dateTime);
|
||||
}
|
||||
|
||||
if (utcDateTime < utc) {
|
||||
// if event is in the past we want to bump it no next occurrence when
|
||||
// recurring is set
|
||||
if (options.recurring) {
|
||||
utcDateTime = _applyRecurrence(utcDateTime, options.recurring);
|
||||
} else {
|
||||
$element.addClass("past");
|
||||
}
|
||||
}
|
||||
|
||||
// once we have the correct UTC date we want
|
||||
// we adjust it to watching user timezone
|
||||
const adjustedDateTime = utcDateTime.tz(displayedTimezone);
|
||||
|
||||
const previews = _generatePreviews(
|
||||
adjustedDateTime.clone(),
|
||||
displayedTimezone,
|
||||
options
|
||||
);
|
||||
const textPreview = _generateTextPreview(previews);
|
||||
const htmlPreview = _generateHtmlPreview(previews);
|
||||
|
||||
const formatedDateTime = _applyFormatting(
|
||||
adjustedDateTime,
|
||||
displayedTimezone,
|
||||
options
|
||||
);
|
||||
|
||||
const $dateTemplate = `
|
||||
<span>
|
||||
<i class="fa fa-globe d-icon d-icon-globe"></i>
|
||||
<span class="relative-time"></span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
$element
|
||||
.html($dateTemplate)
|
||||
.attr("title", textPreview)
|
||||
.attr("data-html-tooltip", `<div class="previews">${htmlPreview}</div>`)
|
||||
.addClass("cooked-date")
|
||||
.find(".relative-time")
|
||||
.text(formatedDateTime);
|
||||
|
||||
if (repeat) {
|
||||
this.timeout = setTimeout(
|
||||
() => processElement($element, options),
|
||||
10000
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const _formatTimezone = timezone =>
|
||||
timezone
|
||||
.replace("_", " ")
|
||||
.replace("Etc/", "")
|
||||
.split("/");
|
||||
|
||||
const _applyZoneToDateTime = (dateTime, timezone) => {
|
||||
return moment.tz(dateTime, timezone).utc();
|
||||
};
|
||||
|
||||
const _calendarFormats = time => {
|
||||
const _translate = key => {
|
||||
const translated = I18n.t(
|
||||
`discourse_local_dates.relative_dates.${key}`,
|
||||
{
|
||||
time: "LT"
|
||||
}
|
||||
);
|
||||
|
||||
if (time) {
|
||||
return translated
|
||||
.split("LT")
|
||||
.map(w => `[${w}]`)
|
||||
.join("LT");
|
||||
} else {
|
||||
return `[${translated.replace(" LT", "")}]`;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
sameDay: _translate("today"),
|
||||
nextDay: _translate("tomorrow"),
|
||||
lastDay: _translate("yesterday"),
|
||||
sameElse: "L"
|
||||
};
|
||||
};
|
||||
|
||||
const _applyFormatting = (dateTime, displayedTimezone, options) => {
|
||||
const sameTimezone = displayedTimezone === moment.tz.guess();
|
||||
const inCalendarRange = dateTime.isBetween(
|
||||
moment().subtract(2, "days"),
|
||||
moment().add(2, "days")
|
||||
);
|
||||
|
||||
if (options.calendar && inCalendarRange) {
|
||||
if (sameTimezone) {
|
||||
if (options.time) {
|
||||
dateTime = dateTime.calendar(null, _calendarFormats(options.time));
|
||||
} else {
|
||||
dateTime = dateTime.calendar(null, _calendarFormats(null));
|
||||
}
|
||||
} else {
|
||||
dateTime = dateTime.format(options.format);
|
||||
dateTime = dateTime.replace("TZ", "");
|
||||
dateTime = `${dateTime} (${_formatTimezone(displayedTimezone).join(
|
||||
": "
|
||||
)})`;
|
||||
}
|
||||
} else {
|
||||
if (options.time) {
|
||||
dateTime = dateTime.format(options.format);
|
||||
|
||||
if (options.displayedTimezone && !sameTimezone) {
|
||||
dateTime = dateTime.replace("TZ", "");
|
||||
dateTime = `${dateTime} (${_formatTimezone(displayedTimezone).join(
|
||||
": "
|
||||
)})`;
|
||||
} else {
|
||||
dateTime = dateTime.replace(
|
||||
"TZ",
|
||||
_formatTimezone(displayedTimezone).join(": ")
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dateTime = dateTime.format(options.format);
|
||||
|
||||
if (!sameTimezone) {
|
||||
dateTime = dateTime.replace("TZ", "");
|
||||
dateTime = `${dateTime} (${_formatTimezone(displayedTimezone).join(
|
||||
": "
|
||||
)})`;
|
||||
} else {
|
||||
dateTime = dateTime.replace(
|
||||
"TZ",
|
||||
_formatTimezone(displayedTimezone).join(": ")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dateTime;
|
||||
};
|
||||
|
||||
const _applyRecurrence = (dateTime, recurring) => {
|
||||
const parts = recurring.split(".");
|
||||
const count = parseInt(parts[0], 10);
|
||||
const type = parts[1];
|
||||
const diff = moment().diff(dateTime, type);
|
||||
const add = Math.ceil(diff + count);
|
||||
|
||||
return dateTime.add(add, type);
|
||||
};
|
||||
|
||||
const createDateTimeRange = (dateTime, timezone) => {
|
||||
const startRange = dateTime.tz(timezone).format("LLL");
|
||||
const separator = "→";
|
||||
const endRange = dateTime
|
||||
.add(24, "hours")
|
||||
.tz(timezone)
|
||||
.format("LLL");
|
||||
|
||||
return `${startRange} ${separator} ${endRange}`;
|
||||
};
|
||||
|
||||
const _generatePreviews = (dateTime, displayedTimezone, options) => {
|
||||
const previewedTimezones = [];
|
||||
const watchingUserTimezone = moment.tz.guess();
|
||||
|
||||
if (displayedTimezone !== watchingUserTimezone) {
|
||||
previewedTimezones.push({
|
||||
timezone: watchingUserTimezone,
|
||||
current: true,
|
||||
dateTime: options.time
|
||||
? dateTime.tz(watchingUserTimezone).format("LLL")
|
||||
: createDateTimeRange(dateTime, watchingUserTimezone)
|
||||
});
|
||||
}
|
||||
|
||||
options.timezones
|
||||
.filter(x => x !== watchingUserTimezone)
|
||||
.forEach(timezone => {
|
||||
previewedTimezones.push({
|
||||
timezone,
|
||||
dateTime: options.time
|
||||
? dateTime.tz(timezone).format("LLL")
|
||||
: createDateTimeRange(dateTime, timezone)
|
||||
});
|
||||
});
|
||||
|
||||
return previewedTimezones;
|
||||
};
|
||||
|
||||
const _generateTextPreview = previews => {
|
||||
return previews
|
||||
.map(preview => {
|
||||
const timezoneParts = _formatTimezone(preview.timezone);
|
||||
|
||||
if (preview.dateTime.match(/TZ/)) {
|
||||
return preview.dateTime.replace(/TZ/, timezoneParts.join(": "));
|
||||
} else {
|
||||
let output = timezoneParts[0];
|
||||
if (timezoneParts[1]) output += ` (${timezoneParts[1]})`;
|
||||
return (output += ` ${preview.dateTime}`);
|
||||
}
|
||||
})
|
||||
.join(", ");
|
||||
};
|
||||
|
||||
const _generateHtmlPreview = previews => {
|
||||
const $htmlTooltip = $("<div></div>");
|
||||
|
||||
const $previewTemplate = $(`
|
||||
<div class='preview'>
|
||||
<span class='timezone'></span>
|
||||
<span class='date-time'></span>
|
||||
</div>
|
||||
`);
|
||||
|
||||
previews.forEach(preview => {
|
||||
const $template = $previewTemplate.clone();
|
||||
|
||||
if (preview.current) $template.addClass("current");
|
||||
|
||||
$template
|
||||
.find(".timezone")
|
||||
.text(_formatTimezone(preview.timezone).join(": "));
|
||||
$template.find(".date-time").text(preview.dateTime);
|
||||
$htmlTooltip.append($template);
|
||||
});
|
||||
|
||||
return $htmlTooltip.html();
|
||||
};
|
||||
|
||||
return this.each(function() {
|
||||
const $element = $(this);
|
||||
|
||||
const options = {};
|
||||
options.time = $element.attr("data-time");
|
||||
options.date = $element.attr("data-date");
|
||||
options.recurring = $element.attr("data-recurring");
|
||||
options.timezones = (
|
||||
$element.attr("data-timezones") ||
|
||||
Discourse.SiteSettings.discourse_local_dates_default_timezones ||
|
||||
"Etc/UTC"
|
||||
).split("|");
|
||||
options.timezone = $element.attr("data-timezone");
|
||||
options.calendar = ($element.attr("data-calendar") || "on") === "on";
|
||||
options.displayedTimezone = $element.attr("data-displayed-timezone");
|
||||
options.format =
|
||||
$element.attr("data-format") || (options.time ? "LLL" : "LL");
|
||||
|
||||
processElement($element, options);
|
||||
});
|
||||
};
|
||||
})(jQuery);
|
|
@ -14,18 +14,13 @@ export default Ember.Component.extend({
|
|||
formats: null,
|
||||
recurring: null,
|
||||
advancedMode: false,
|
||||
isValid: true,
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
|
||||
this.set("date", moment().format(this.dateFormat));
|
||||
this.set("format", `LLL`);
|
||||
this.set(
|
||||
"timezones",
|
||||
(this.siteSettings.discourse_local_dates_default_timezones || "")
|
||||
.split("|")
|
||||
.filter(f => f)
|
||||
);
|
||||
this.set("timezones", []);
|
||||
this.set(
|
||||
"formats",
|
||||
(this.siteSettings.discourse_local_dates_default_formats || "")
|
||||
|
@ -34,10 +29,9 @@ export default Ember.Component.extend({
|
|||
);
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
|
||||
this._setConfig();
|
||||
@observes("date", "time", "toDate", "toTime")
|
||||
_resetFormValidity() {
|
||||
this.set("isValid", true);
|
||||
},
|
||||
|
||||
@computed
|
||||
|
@ -74,44 +68,30 @@ export default Ember.Component.extend({
|
|||
return moment.tz.names();
|
||||
},
|
||||
|
||||
@observes(
|
||||
"date",
|
||||
"time",
|
||||
"toDate",
|
||||
"toTime",
|
||||
"recurring",
|
||||
"format",
|
||||
"timezones"
|
||||
)
|
||||
_setConfig() {
|
||||
const toTime = this.get("toTime");
|
||||
getConfig(range) {
|
||||
const endOfRange = range && range === "end";
|
||||
const time = endOfRange ? this.get("toTime") : this.get("time");
|
||||
let date = endOfRange ? this.get("toDate") : this.get("date");
|
||||
|
||||
if (toTime && !this.get("toDate")) {
|
||||
this.set("toDate", moment().format(this.dateFormat));
|
||||
if (endOfRange && time && !date) {
|
||||
date = moment().format(this.dateFormat);
|
||||
}
|
||||
|
||||
const date = this.get("date");
|
||||
const toDate = this.get("toDate");
|
||||
const time = this.get("time");
|
||||
const recurring = this.get("recurring");
|
||||
const format = this.get("format");
|
||||
const timezones = this.get("timezones");
|
||||
const timeInferred = time ? false : true;
|
||||
const toTimeInferred = toTime ? false : true;
|
||||
const timezone = this.get("currentUserTimezone");
|
||||
|
||||
let dateTime;
|
||||
if (!timeInferred) {
|
||||
dateTime = moment.tz(`${date} ${time}`, timezone).utc();
|
||||
dateTime = moment.tz(`${date} ${time}`, timezone);
|
||||
} else {
|
||||
dateTime = moment.tz(date, timezone).utc();
|
||||
if (endOfRange) {
|
||||
dateTime = moment.tz(date, timezone).endOf("day");
|
||||
} else {
|
||||
dateTime = moment.tz(date, timezone);
|
||||
}
|
||||
|
||||
let toDateTime;
|
||||
if (!toTimeInferred) {
|
||||
toDateTime = moment.tz(`${toDate} ${toTime}`, timezone);
|
||||
} else {
|
||||
toDateTime = moment.tz(toDate, timezone).endOf("day");
|
||||
}
|
||||
|
||||
let config = {
|
||||
|
@ -127,82 +107,64 @@ export default Ember.Component.extend({
|
|||
config.time = dateTime.format(this.timeFormat);
|
||||
}
|
||||
|
||||
if (!toTimeInferred) {
|
||||
config.toTime = toDateTime.format(this.timeFormat);
|
||||
if (timeInferred) {
|
||||
config.displayedTimezone = this.get("currentUserTimezone");
|
||||
}
|
||||
|
||||
if (toDate) {
|
||||
config.toDate = toDateTime.format(this.dateFormat);
|
||||
}
|
||||
|
||||
if (
|
||||
timeInferred &&
|
||||
toTimeInferred &&
|
||||
this.get("formats").includes(format)
|
||||
) {
|
||||
if (timeInferred && this.get("formats").includes(format)) {
|
||||
config.format = "LL";
|
||||
}
|
||||
|
||||
if (toDate) {
|
||||
config.toDateTime = toDateTime;
|
||||
}
|
||||
|
||||
if (
|
||||
!timeInferred &&
|
||||
!toTimeInferred &&
|
||||
date === moment().format(this.dateFormat) &&
|
||||
date === toDate &&
|
||||
this.get("formats").includes(format)
|
||||
) {
|
||||
config.format = "LT";
|
||||
}
|
||||
|
||||
this.set("config", config);
|
||||
return config;
|
||||
},
|
||||
|
||||
getTextConfig(config) {
|
||||
_generateDateMarkup(config) {
|
||||
let text = `[date=${config.date}`;
|
||||
if (config.recurring) text += `recurring=${config.recurring} `;
|
||||
|
||||
if (config.time) {
|
||||
text += ` time=${config.time} `;
|
||||
}
|
||||
|
||||
if (config.format && config.format.length) {
|
||||
text += ` format="${config.format}" `;
|
||||
text += `timezones="${config.timezones.join("|")}"`;
|
||||
text += `]`;
|
||||
|
||||
if (config.toDate) {
|
||||
text += ` → `;
|
||||
text += `[date=${config.toDate} `;
|
||||
|
||||
if (config.toTime) {
|
||||
text += `time=${config.toTime} `;
|
||||
}
|
||||
|
||||
text += `format="${config.format}" `;
|
||||
text += `timezones="${config.timezones.join("|")}"`;
|
||||
text += `]`;
|
||||
if (config.timezone) {
|
||||
text += ` timezone="${config.timezone}"`;
|
||||
}
|
||||
|
||||
if (config.timezones && config.timezones.length) {
|
||||
text += ` timezones="${config.timezones.join("|")}"`;
|
||||
}
|
||||
|
||||
text += `]`;
|
||||
|
||||
return text;
|
||||
},
|
||||
|
||||
@computed("config.dateTime", "config.toDateTime")
|
||||
validDate(dateTime, toDateTime) {
|
||||
if (!dateTime) return false;
|
||||
valid(isRange) {
|
||||
const fromConfig = this.getConfig(isRange ? "start" : null);
|
||||
|
||||
if (toDateTime) {
|
||||
if (!toDateTime.isValid()) {
|
||||
if (!fromConfig.dateTime || !fromConfig.dateTime.isValid()) {
|
||||
this.set("isValid", false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (toDateTime.diff(dateTime) < 0) {
|
||||
if (isRange) {
|
||||
const toConfig = this.getConfig("end");
|
||||
|
||||
if (
|
||||
!toConfig.dateTime ||
|
||||
!toConfig.dateTime.isValid() ||
|
||||
toConfig.dateTime.diff(fromConfig.dateTime) < 0
|
||||
) {
|
||||
this.set("isValid", false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return dateTime.isValid();
|
||||
this.set("isValid", true);
|
||||
return true;
|
||||
},
|
||||
|
||||
@computed("advancedMode")
|
||||
|
@ -218,10 +180,23 @@ export default Ember.Component.extend({
|
|||
},
|
||||
|
||||
save() {
|
||||
const isRange =
|
||||
this.get("date") && (this.get("toDate") || this.get("toTime"));
|
||||
|
||||
if (this.valid(isRange)) {
|
||||
this._closeModal();
|
||||
|
||||
const textConfig = this.getTextConfig(this.get("config"));
|
||||
this.get("toolbarEvent").addText(textConfig);
|
||||
let text = this._generateDateMarkup(
|
||||
this.getConfig(isRange ? "start" : null)
|
||||
);
|
||||
|
||||
if (isRange) {
|
||||
text += ` → `;
|
||||
text += this._generateDateMarkup(this.getConfig("end"));
|
||||
}
|
||||
|
||||
this.get("toolbarEvent").addText(text);
|
||||
}
|
||||
},
|
||||
|
||||
fillFormat(format) {
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<span class="preview">{{currentUserTimezone}}</span>
|
||||
</div>
|
||||
|
||||
{{#unless validDate}}
|
||||
{{#unless isValid}}
|
||||
<span class="validation-error">{{i18n "discourse_local_dates.create.form.invalid_date"}}</span>
|
||||
{{/unless}}
|
||||
|
||||
|
@ -111,7 +111,7 @@
|
|||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer discourse-local-dates-create-modal-footer">
|
||||
{{#if validDate}}
|
||||
{{#if isValid}}
|
||||
{{d-button class="btn btn-default"
|
||||
action="save"
|
||||
label="discourse_local_dates.create.form.insert"}}
|
||||
|
|
|
@ -9,7 +9,7 @@ function addLocalDate(buffer, matches, state) {
|
|||
timezone: null,
|
||||
format: null,
|
||||
timezones: null,
|
||||
displayedZone: null
|
||||
displayedTimezone: null
|
||||
};
|
||||
|
||||
let parsed = parseBBCodeTag(
|
||||
|
@ -25,13 +25,20 @@ function addLocalDate(buffer, matches, state) {
|
|||
config.timezone = parsed.attrs.timezone;
|
||||
config.recurring = parsed.attrs.recurring;
|
||||
config.timezones = parsed.attrs.timezones;
|
||||
config.displayedZone = parsed.attrs.displayedZone;
|
||||
config.displayedTimezone = parsed.attrs.displayedTimezone;
|
||||
|
||||
token = new state.Token("span_open", "span", 1);
|
||||
token.attrs = [
|
||||
["class", "discourse-local-date"],
|
||||
["data-date", state.md.utils.escapeHtml(config.date)]
|
||||
];
|
||||
token.attrs = [["data-date", state.md.utils.escapeHtml(config.date)]];
|
||||
|
||||
if (!config.date.match(/\d{4}-\d{2}-\d{2}/)) {
|
||||
closeBuffer(buffer, state, moment.invalid().format());
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.time && !config.time.match(/\d{2}:\d{2}(?::\d{2})?/)) {
|
||||
closeBuffer(buffer, state, moment.invalid().format());
|
||||
return;
|
||||
}
|
||||
|
||||
let dateTime = config.date;
|
||||
if (config.time) {
|
||||
|
@ -39,6 +46,13 @@ function addLocalDate(buffer, matches, state) {
|
|||
dateTime = `${dateTime} ${config.time}`;
|
||||
}
|
||||
|
||||
if (!moment(dateTime).isValid()) {
|
||||
closeBuffer(buffer, state, moment.invalid().format());
|
||||
return;
|
||||
}
|
||||
|
||||
token.attrs.push(["class", "discourse-local-date"]);
|
||||
|
||||
if (config.format) {
|
||||
token.attrs.push(["data-format", state.md.utils.escapeHtml(config.format)]);
|
||||
}
|
||||
|
@ -50,21 +64,28 @@ function addLocalDate(buffer, matches, state) {
|
|||
]);
|
||||
}
|
||||
|
||||
if (config.displayedZone) {
|
||||
if (
|
||||
config.displayedTimezone &&
|
||||
moment.tz.names().includes(config.displayedTimezone)
|
||||
) {
|
||||
token.attrs.push([
|
||||
"data-displayed-zone",
|
||||
state.md.utils.escapeHtml(config.displayedZone)
|
||||
"data-displayed-timezone",
|
||||
state.md.utils.escapeHtml(config.displayedTimezone)
|
||||
]);
|
||||
}
|
||||
|
||||
if (config.timezones) {
|
||||
const timezones = config.timezones.split("|").filter(timezone => {
|
||||
return moment.tz.names().includes(timezone);
|
||||
});
|
||||
|
||||
token.attrs.push([
|
||||
"data-timezones",
|
||||
state.md.utils.escapeHtml(config.timezones)
|
||||
state.md.utils.escapeHtml(timezones.join("|"))
|
||||
]);
|
||||
}
|
||||
|
||||
if (config.timezone) {
|
||||
if (config.timezone && moment.tz.names().includes(config.timezone)) {
|
||||
token.attrs.push([
|
||||
"data-timezone",
|
||||
state.md.utils.escapeHtml(config.timezone)
|
||||
|
@ -95,8 +116,14 @@ function addLocalDate(buffer, matches, state) {
|
|||
}
|
||||
token.attrs.push(["data-email-preview", emailPreview]);
|
||||
|
||||
closeBuffer(buffer, state, dateTime.utc().format(config.format));
|
||||
}
|
||||
|
||||
function closeBuffer(buffer, state, text) {
|
||||
let token;
|
||||
|
||||
token = new state.Token("text", "", 0);
|
||||
token.content = dateTime.utc().format(config.format);
|
||||
token.content = text;
|
||||
buffer.push(token);
|
||||
|
||||
token = new state.Token("span_close", "span", -1);
|
||||
|
|
|
@ -18,6 +18,29 @@
|
|||
}
|
||||
}
|
||||
|
||||
.discourse-local-date + #discourse-tooltip {
|
||||
.tooltip-content {
|
||||
max-width: 360px;
|
||||
padding: 0.5em;
|
||||
|
||||
.previews {
|
||||
.preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 5px;
|
||||
|
||||
.timezone {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&.current {
|
||||
background: $tertiary-low;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discourse-local-dates-create-modal-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# author: Joffrey Jaffeux
|
||||
hide_plugin if self.respond_to?(:hide_plugin)
|
||||
|
||||
register_asset "javascripts/discourse-local-dates.js"
|
||||
register_asset "javascripts/discourse-local-dates.js.no-module.es6"
|
||||
register_asset "stylesheets/common/discourse-local-dates.scss"
|
||||
register_asset "moment.js", :vendored_core_pretty_text
|
||||
register_asset "moment-timezone.js", :vendored_core_pretty_text
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
require 'rails_helper'
|
||||
|
||||
def generate_html(text, opts = {})
|
||||
output = "<p><span class=\"discourse-local-date\""
|
||||
output = "<p><span"
|
||||
output += " data-date=\"#{opts[:date]}\"" if opts[:date]
|
||||
output += " data-time=\"#{opts[:time]}\"" if opts[:time]
|
||||
output += " class=\"discourse-local-date\""
|
||||
output += " data-format=\"#{opts[:format]}\"" if opts[:format]
|
||||
output += " data-email-preview=\"#{opts[:email_preview]}\"" if opts[:email_preview]
|
||||
output += ">"
|
||||
|
|
|
@ -17,15 +17,15 @@ test("composer bbcode", async assert => {
|
|||
|
||||
await fillIn(
|
||||
".d-editor-input",
|
||||
'[date=2017-10-23 time=01:30:00 displayedZone="America/Chicago" format="LLLL" calendar="off" recurring="1.weeks" timezone="Asia/Calcutta" timezones="Europe/Paris|America/Los_Angeles"]'
|
||||
'[date=2017-10-23 time=01:30:00 displayedTimezone="America/Chicago" format="LLLL" calendar="off" recurring="1.weeks" timezone="Asia/Calcutta" timezones="Europe/Paris|America/Los_Angeles"]'
|
||||
);
|
||||
|
||||
assert.equal(getAttr("date"), "2017-10-23", "it has the correct date");
|
||||
assert.equal(getAttr("time"), "01:30:00", "it has the correct time");
|
||||
assert.equal(
|
||||
getAttr("displayed-zone"),
|
||||
getAttr("displayed-timezone"),
|
||||
"America/Chicago",
|
||||
"it has the correct displayed zone"
|
||||
"it has the correct displayed timezone"
|
||||
);
|
||||
assert.equal(getAttr("format"), "LLLL", "it has the correct format");
|
||||
assert.equal(
|
||||
|
|
|
@ -16,6 +16,7 @@ acceptance("Local Dates", {
|
|||
|
||||
const DEFAULT_DATE = "2018-06-20";
|
||||
const DEFAULT_ZONE = "Europe/Paris";
|
||||
const DEFAULT_ZONE_FORMATED = DEFAULT_ZONE.split("/").join(": ");
|
||||
|
||||
function advance(count, unit = "days") {
|
||||
return moment(DEFAULT_DATE)
|
||||
|
@ -54,56 +55,72 @@ function generateHTML(options = {}) {
|
|||
output += ` data-date="${options.date || DEFAULT_DATE}"`;
|
||||
if (options.format) output += ` data-format="${options.format}"`;
|
||||
if (options.time) output += ` data-time="${options.time}"`;
|
||||
output += ` data-timezone="${options.timezone || DEFAULT_ZONE}"`;
|
||||
if (options.calendar) output += ` data-calendar="${options.calendar}"`;
|
||||
if (options.recurring) output += ` data-recurring="${options.recurring}"`;
|
||||
if (options.displayedZone)
|
||||
output += ` data-displayed-zone="${options.displayedZone}"`;
|
||||
if (options.displayedTimezone)
|
||||
output += ` data-displayed-timezone="${options.displayedTimezone}"`;
|
||||
|
||||
return (output += "></span>");
|
||||
}
|
||||
|
||||
test("default format - time specified", assert => {
|
||||
const html = generateHTML({ date: advance(3), time: "00:00" });
|
||||
const html = generateHTML({ date: advance(3), time: "02:00" });
|
||||
const transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text(),
|
||||
transformed.text().trim(),
|
||||
"June 23, 2018 2:00 AM",
|
||||
"it uses moment LLL format"
|
||||
);
|
||||
});
|
||||
|
||||
test("default format - no time specified", assert => {
|
||||
const html = generateHTML({ date: advance(3) });
|
||||
const transformed = $(html).applyLocalDates();
|
||||
let html = generateHTML({ date: advance(3) });
|
||||
let transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text(),
|
||||
transformed.text().trim(),
|
||||
"June 23, 2018",
|
||||
"it uses moment LL format as default if not time is specified"
|
||||
);
|
||||
|
||||
freezeDateAndZone(advance(1), "Pacific/Auckland", () => {
|
||||
html = generateHTML({ date: advance(3) });
|
||||
transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text().trim(),
|
||||
`June 23, 2018 (${DEFAULT_ZONE_FORMATED})`,
|
||||
"it appends creator timezone if watching user timezone is different"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("today", assert => {
|
||||
const html = generateHTML({ time: "14:00" });
|
||||
const html = generateHTML({ time: "16:00" });
|
||||
const transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(transformed.text(), "Today 4:00 PM", "it display Today");
|
||||
assert.equal(transformed.text().trim(), "Today 4:00 PM", "it display Today");
|
||||
});
|
||||
|
||||
test("today - no time", assert => {
|
||||
const html = generateHTML();
|
||||
const transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(transformed.text(), "Today", "it display Today without time");
|
||||
assert.equal(
|
||||
transformed.text().trim(),
|
||||
"Today",
|
||||
"it display Today without time"
|
||||
);
|
||||
});
|
||||
|
||||
test("yesterday", assert => {
|
||||
const html = generateHTML({ date: rewind(1), time: "14:00" });
|
||||
const html = generateHTML({ date: rewind(1), time: "16:00" });
|
||||
const transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text(),
|
||||
transformed.text().trim(),
|
||||
"Yesterday 4:00 PM",
|
||||
"it displays yesterday"
|
||||
);
|
||||
|
@ -114,17 +131,21 @@ QUnit.skip("yesterday - no time", assert => {
|
|||
const transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text(),
|
||||
transformed.text().trim(),
|
||||
"Yesterday",
|
||||
"it displays yesterday without time"
|
||||
);
|
||||
});
|
||||
|
||||
test("tomorrow", assert => {
|
||||
const html = generateHTML({ date: advance(1), time: "14:00" });
|
||||
const html = generateHTML({ date: advance(1), time: "16:00" });
|
||||
const transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(transformed.text(), "Tomorrow 4:00 PM", "it displays tomorrow");
|
||||
assert.equal(
|
||||
transformed.text().trim(),
|
||||
"Tomorrow 4:00 PM",
|
||||
"it displays tomorrow"
|
||||
);
|
||||
});
|
||||
|
||||
test("tomorrow - no time", assert => {
|
||||
|
@ -132,7 +153,7 @@ test("tomorrow - no time", assert => {
|
|||
const transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text(),
|
||||
transformed.text().trim(),
|
||||
"Tomorrow",
|
||||
"it displays tomorrow without time"
|
||||
);
|
||||
|
@ -142,35 +163,39 @@ test("today - no time with different zones", assert => {
|
|||
const html = generateHTML();
|
||||
let transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(transformed.text(), "Today", "it displays today without time");
|
||||
assert.equal(
|
||||
transformed.text().trim(),
|
||||
"Today",
|
||||
"it displays today without time"
|
||||
);
|
||||
|
||||
freezeDateAndZone(rewind(12, "hours"), "Pacific/Auckland", () => {
|
||||
transformed = $(html).applyLocalDates();
|
||||
assert.equal(
|
||||
transformed.text(),
|
||||
"Tomorrow",
|
||||
"it displays Tomorrow without time"
|
||||
transformed.text().trim(),
|
||||
`June 20, 2018 (${DEFAULT_ZONE_FORMATED})`,
|
||||
"it displays the date without calendar and creator timezone"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("calendar off", assert => {
|
||||
const html = generateHTML({ calendar: "off", time: "14:00" });
|
||||
const html = generateHTML({ calendar: "off", time: "16:00" });
|
||||
const transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text(),
|
||||
transformed.text().trim(),
|
||||
"June 20, 2018 4:00 PM",
|
||||
"it displays the date without Today"
|
||||
);
|
||||
});
|
||||
|
||||
test("recurring", assert => {
|
||||
const html = generateHTML({ recurring: "1.week", time: "14:00" });
|
||||
const html = generateHTML({ recurring: "1.week", time: "16:00" });
|
||||
let transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text(),
|
||||
transformed.text().trim(),
|
||||
"Today 4:00 PM",
|
||||
"it displays the next occurrence"
|
||||
);
|
||||
|
@ -179,28 +204,13 @@ test("recurring", assert => {
|
|||
transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text(),
|
||||
transformed.text().trim(),
|
||||
"June 27, 2018 4:00 PM",
|
||||
"it displays the next occurrence"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("displayedZone", assert => {
|
||||
const html = generateHTML({
|
||||
date: advance(3),
|
||||
displayedZone: "Etc/UTC",
|
||||
time: "14:00"
|
||||
});
|
||||
const transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text(),
|
||||
"June 23, 2018 2:00 PM",
|
||||
"it forces display in the given timezone"
|
||||
);
|
||||
});
|
||||
|
||||
test("format", assert => {
|
||||
const html = generateHTML({
|
||||
date: advance(3),
|
||||
|
@ -209,12 +219,144 @@ test("format", assert => {
|
|||
const transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text(),
|
||||
transformed.text().trim(),
|
||||
"2018 | 06 - 23",
|
||||
"it uses the given format"
|
||||
);
|
||||
});
|
||||
|
||||
test("displayedTimezone", assert => {
|
||||
let html = generateHTML({
|
||||
date: advance(3),
|
||||
displayedTimezone: "America/Chicago",
|
||||
time: "16:00"
|
||||
});
|
||||
let transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text().trim(),
|
||||
"June 23, 2018 9:00 AM (America: Chicago)",
|
||||
"it displays timezone when different from watching user"
|
||||
);
|
||||
|
||||
html = generateHTML({
|
||||
date: advance(3),
|
||||
displayedTimezone: DEFAULT_ZONE,
|
||||
time: "16:00"
|
||||
});
|
||||
|
||||
transformed = $(html).applyLocalDates();
|
||||
assert.equal(
|
||||
transformed.text().trim(),
|
||||
"June 23, 2018 4:00 PM",
|
||||
"it doesn’t display timezone when same from watching user"
|
||||
);
|
||||
|
||||
html = generateHTML({ displayedTimezone: "Etc/UTC" });
|
||||
transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text().trim(),
|
||||
"June 19, 2018 (UTC)",
|
||||
"it displays timezone and drops calendar mode when timezone is different from watching user"
|
||||
);
|
||||
|
||||
html = generateHTML({ displayedTimezone: DEFAULT_ZONE });
|
||||
transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text().trim(),
|
||||
"Today",
|
||||
"it doesn’t display timezone and doesn’t drop calendar mode when timezone is same from watching user"
|
||||
);
|
||||
|
||||
html = generateHTML({
|
||||
timezone: "America/Chicago"
|
||||
});
|
||||
transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text().trim(),
|
||||
"June 20, 2018 (America: Chicago)",
|
||||
"it uses timezone when displayedTimezone is not set"
|
||||
);
|
||||
|
||||
html = generateHTML();
|
||||
transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text().trim(),
|
||||
"Today",
|
||||
"it uses user’s timezone when displayedTimezone and timezone are not set"
|
||||
);
|
||||
|
||||
html = generateHTML({
|
||||
timezone: "America/Chicago",
|
||||
displayedTimezone: "Pacific/Auckland"
|
||||
});
|
||||
transformed = $(html).applyLocalDates();
|
||||
|
||||
assert.equal(
|
||||
transformed.text().trim(),
|
||||
"June 20, 2018 (Pacific: Auckland)",
|
||||
"it uses displayedTimezone over timezone"
|
||||
);
|
||||
});
|
||||
|
||||
test("tooltip", assert => {
|
||||
let html = generateHTML({ timezone: "America/Chicago" });
|
||||
let transformed = $(html).applyLocalDates();
|
||||
|
||||
let htmlToolip = transformed.attr("data-html-tooltip");
|
||||
let currentUserPreview = $(htmlToolip).find(".preview.current");
|
||||
let timezone = currentUserPreview.find(".timezone").text();
|
||||
let dateTime = currentUserPreview.find(".date-time").text();
|
||||
|
||||
assert.equal(
|
||||
timezone,
|
||||
DEFAULT_ZONE_FORMATED,
|
||||
"it creates a range adjusted to watching user timezone"
|
||||
);
|
||||
assert.equal(
|
||||
dateTime,
|
||||
"June 20, 2018 7:00 AM → June 21, 2018 7:00 AM",
|
||||
"it creates a range adjusted to watching user timezone"
|
||||
);
|
||||
|
||||
freezeDateAndZone(DEFAULT_DATE, "Pacific/Auckland", () => {
|
||||
html = generateHTML({ timezone: "Pacific/Auckland" });
|
||||
transformed = $(html).applyLocalDates();
|
||||
htmlToolip = transformed.attr("data-html-tooltip");
|
||||
currentUserPreview = $(htmlToolip).find(".preview.current");
|
||||
|
||||
assert.notOk(
|
||||
exists(currentUserPreview),
|
||||
"it doesn’t create entry if watching user has the same timezone than creator"
|
||||
);
|
||||
});
|
||||
|
||||
html = generateHTML({ timezone: "America/Chicago", time: "14:00:00" });
|
||||
transformed = $(html).applyLocalDates();
|
||||
htmlToolip = transformed.attr("data-html-tooltip");
|
||||
|
||||
const $preview = $(htmlToolip)
|
||||
.find(".preview")
|
||||
.first();
|
||||
dateTime = $preview.find(".date-time").text();
|
||||
timezone = $preview.find(".timezone").text();
|
||||
|
||||
assert.ok(
|
||||
!exists(".preview.current"),
|
||||
"doesn’t create current timezone when displayed timezone equals watching user timezone"
|
||||
);
|
||||
assert.equal(
|
||||
dateTime,
|
||||
"June 20, 2018 7:00 PM",
|
||||
"it doesn’t create range if time has been set"
|
||||
);
|
||||
assert.equal(timezone, "UTC", "Etc/UTC is rewritten to UTC");
|
||||
});
|
||||
|
||||
test("test utils", assert => {
|
||||
assert.equal(
|
||||
moment().format("LLLL"),
|
||||
|
|
Loading…
Reference in New Issue
Block a user