Refactor calendar methods into util class (#3088)

Refactored some methods in calendar module:
- move methods into own file 
- dont call shorten method from titelTransform because why? just call
them after each other.
- added tests for util methods
- cleaned up other tests

---------

Co-authored-by: veeck <michael@veeck.de>
This commit is contained in:
Veeck
2023-04-09 12:49:50 +02:00
committed by GitHub
parent dee3cd3da7
commit 77f9c86774
11 changed files with 787 additions and 757 deletions

View File

@@ -1,4 +1,4 @@
/* global cloneObject */
/* global CalendarUtils, cloneObject */
/* MagicMirror²
* Module: Calendar
@@ -79,7 +79,7 @@ Module.register("calendar", {
// Define required scripts.
getScripts: function () {
return ["moment.js"];
return ["calendarutils.js", "moment.js"];
},
// Define required translations.
@@ -108,7 +108,7 @@ Module.register("calendar", {
}
// Set locale.
moment.updateLocale(config.language, this.getLocaleSpecification(config.timeFormat));
moment.updateLocale(config.language, CalendarUtils.getLocaleSpecification(config.timeFormat));
// clear data holder before start
this.calendarData = {};
@@ -337,7 +337,8 @@ Module.register("calendar", {
}
}
titleWrapper.innerHTML = this.titleTransform(event.title, this.config.titleReplace, this.config.wrapEvents, this.config.maxTitleLength, this.config.maxTitleLines) + repeatingCountTitle;
const transformedTitle = CalendarUtils.titleTransform(event.title, this.config.titleReplace);
titleWrapper.innerHTML = CalendarUtils.shorten(transformedTitle, this.config.maxTitleLength, this.config.wrapEvents, this.config.maxTitleLines) + repeatingCountTitle;
const titleClass = this.titleClassForUrl(event.url);
@@ -362,7 +363,7 @@ Module.register("calendar", {
// Add endDate to dataheaders if showEnd is enabled
if (this.config.showEnd) {
timeWrapper.innerHTML += ` - ${this.capFirst(moment(event.endDate, "x").format("LT"))}`;
timeWrapper.innerHTML += ` - ${CalendarUtils.capFirst(moment(event.endDate, "x").format("LT"))}`;
}
eventWrapper.appendChild(timeWrapper);
@@ -378,20 +379,20 @@ Module.register("calendar", {
if (this.config.timeFormat === "absolute") {
// Use dateFormat
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
// Add end time if showEnd
if (this.config.showEnd) {
timeWrapper.innerHTML += "-";
timeWrapper.innerHTML += this.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat));
timeWrapper.innerHTML += CalendarUtils.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat));
}
// For full day events we use the fullDayEventDateFormat
if (event.fullDayEvent) {
//subtract one second so that fullDayEvents end at 23:59:59, and not at 0:00:00 one the next day
event.endDate -= ONE_SECOND;
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat));
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat));
} else if (this.config.getRelative > 0 && event.startDate < now) {
// Ongoing and getRelative is set
timeWrapper.innerHTML = this.capFirst(
timeWrapper.innerHTML = CalendarUtils.capFirst(
this.translate("RUNNING", {
fallback: `${this.translate("RUNNING")} {timeUntilEnd}`,
timeUntilEnd: moment(event.endDate, "x").fromNow(true)
@@ -399,19 +400,19 @@ Module.register("calendar", {
);
} else if (this.config.urgency > 0 && event.startDate - now < this.config.urgency * ONE_DAY) {
// Within urgency days
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").fromNow());
}
if (event.fullDayEvent && this.config.nextDaysRelative) {
// Full days events within the next two days
if (event.today) {
timeWrapper.innerHTML = this.capFirst(this.translate("TODAY"));
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("TODAY"));
} else if (event.yesterday) {
timeWrapper.innerHTML = this.capFirst(this.translate("YESTERDAY"));
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("YESTERDAY"));
} else if (event.startDate - now < ONE_DAY && event.startDate - now > 0) {
timeWrapper.innerHTML = this.capFirst(this.translate("TOMORROW"));
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("TOMORROW"));
} else if (event.startDate - now < 2 * ONE_DAY && event.startDate - now > 0) {
if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") {
timeWrapper.innerHTML = this.capFirst(this.translate("DAYAFTERTOMORROW"));
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("DAYAFTERTOMORROW"));
}
}
}
@@ -420,9 +421,9 @@ Module.register("calendar", {
if (event.startDate >= now || (event.fullDayEvent && event.today)) {
// Use relative time
if (!this.config.hideTime && !event.fullDayEvent) {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }));
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }));
} else {
timeWrapper.innerHTML = this.capFirst(
timeWrapper.innerHTML = CalendarUtils.capFirst(
moment(event.startDate, "x").calendar(null, {
sameDay: this.config.showTimeToday ? "LT" : `[${this.translate("TODAY")}]`,
nextDay: `[${this.translate("TOMORROW")}]`,
@@ -434,27 +435,27 @@ Module.register("calendar", {
if (event.fullDayEvent) {
// Full days events within the next two days
if (event.today) {
timeWrapper.innerHTML = this.capFirst(this.translate("TODAY"));
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("TODAY"));
} else if (event.dayBeforeYesterday) {
if (this.translate("DAYBEFOREYESTERDAY") !== "DAYBEFOREYESTERDAY") {
timeWrapper.innerHTML = this.capFirst(this.translate("DAYBEFOREYESTERDAY"));
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("DAYBEFOREYESTERDAY"));
}
} else if (event.yesterday) {
timeWrapper.innerHTML = this.capFirst(this.translate("YESTERDAY"));
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("YESTERDAY"));
} else if (event.startDate - now < ONE_DAY && event.startDate - now > 0) {
timeWrapper.innerHTML = this.capFirst(this.translate("TOMORROW"));
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("TOMORROW"));
} else if (event.startDate - now < 2 * ONE_DAY && event.startDate - now > 0) {
if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") {
timeWrapper.innerHTML = this.capFirst(this.translate("DAYAFTERTOMORROW"));
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("DAYAFTERTOMORROW"));
}
}
} else if (event.startDate - now < this.config.getRelative * ONE_HOUR) {
// If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").fromNow());
}
} else {
// Ongoing event
timeWrapper.innerHTML = this.capFirst(
timeWrapper.innerHTML = CalendarUtils.capFirst(
this.translate("RUNNING", {
fallback: `${this.translate("RUNNING")} {timeUntilEnd}`,
timeUntilEnd: moment(event.endDate, "x").fromNow(true)
@@ -503,7 +504,9 @@ Module.register("calendar", {
const descCell = document.createElement("td");
descCell.className = "location";
descCell.colSpan = "2";
descCell.innerHTML = this.titleTransform(event.location, this.config.locationTitleReplace, this.config.wrapLocationEvents, this.config.maxLocationTitleLength, this.config.maxEventTitleLines);
const transformedTitle = CalendarUtils.titleTransform(event.location, this.config.locationTitleReplace);
descCell.innerHTML = CalendarUtils.shorten(transformedTitle, this.config.maxLocationTitleLength, this.config.wrapLocationEvents, this.config.maxEventTitleLines);
locationRow.appendChild(descCell);
wrapper.appendChild(locationRow);
@@ -519,28 +522,6 @@ Module.register("calendar", {
return wrapper;
},
/**
* This function accepts a number (either 12 or 24) and returns a moment.js LocaleSpecification with the
* corresponding timeformat to be used in the calendar display. If no number is given (or otherwise invalid input)
* it will a localeSpecification object with the system locale time format.
*
* @param {number} timeFormat Specifies either 12 or 24 hour time format
* @returns {moment.LocaleSpecification} formatted time
*/
getLocaleSpecification: function (timeFormat) {
switch (timeFormat) {
case 12: {
return { longDateFormat: { LT: "h:mm A" } };
}
case 24: {
return { longDateFormat: { LT: "HH:mm" } };
}
default: {
return { longDateFormat: { LT: moment.localeData().longDateFormat("LT") } };
}
}
},
/**
* Checks if this config contains the calendar url.
*
@@ -870,98 +851,6 @@ Module.register("calendar", {
return !!this.getCalendarProperty(url, property, undefined);
},
/**
* Shortens a string if it's longer than maxLength and add a ellipsis to the end
*
* @param {string} string Text string to shorten
* @param {number} maxLength The max length of the string
* @param {boolean} wrapEvents Wrap the text after the line has reached maxLength
* @param {number} maxTitleLines The max number of vertical lines before cutting event title
* @returns {string} The shortened string
*/
shorten: function (string, maxLength, wrapEvents, maxTitleLines) {
if (typeof string !== "string") {
return "";
}
if (wrapEvents === true) {
const words = string.split(" ");
let temp = "";
let currentLine = "";
let line = 0;
for (let i = 0; i < words.length; i++) {
const word = words[i];
if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) {
// max - 1 to account for a space
currentLine += `${word} `;
} else {
line++;
if (line > maxTitleLines - 1) {
if (i < words.length) {
currentLine += "…";
}
break;
}
if (currentLine.length > 0) {
temp += `${currentLine}<br>${word} `;
} else {
temp += `${word}<br>`;
}
currentLine = "";
}
}
return (temp + currentLine).trim();
} else {
if (maxLength && typeof maxLength === "number" && string.length > maxLength) {
return `${string.trim().slice(0, maxLength)}`;
} else {
return string.trim();
}
}
},
/**
* Capitalize the first letter of a string
*
* @param {string} string The string to capitalize
* @returns {string} The capitalized string
*/
capFirst: function (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
},
/**
* Transforms the title of an event for usage.
* Replaces parts of the text as defined in config.titleReplace.
* Shortens title based on config.maxTitleLength and config.wrapEvents
*
* @param {string} title The title to transform.
* @param {object} titleReplace Pairs of strings to be replaced in the title
* @param {boolean} wrapEvents Wrap the text after the line has reached maxLength
* @param {number} maxTitleLength The max length of the string
* @param {number} maxTitleLines The max number of vertical lines before cutting event title
* @returns {string} The transformed title.
*/
titleTransform: function (title, titleReplace, wrapEvents, maxTitleLength, maxTitleLines) {
for (let needle in titleReplace) {
const replacement = titleReplace[needle];
const regParts = needle.match(/^\/(.+)\/([gim]*)$/);
if (regParts) {
// the parsed pattern is a regexp.
needle = new RegExp(regParts[1], regParts[2]);
}
title = title.replace(needle, replacement);
}
title = this.shorten(title, maxTitleLength, wrapEvents, maxTitleLines);
return title;
},
/**
* Broadcasts the events to all other modules for reuse.
* The all events available in one array, sorted on startdate.