File indexing completed on 2024-06-16 04:29:53

0001 // SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com>
0002 // SPDX-License-Identifier: LGPL-2.1-or-later
0003 
0004 .import "dateutils.js" as DateUtils;
0005 
0006 // This regex detects URLs in the description text, so we can turn them into links
0007 const urlRegexp = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/ig
0008 
0009 function numberToString(number) {
0010     // The code in here was adapted from an article by Johnathan Wood, see:
0011     // http://www.blackbeltcoder.com/Articles/strings/converting-numbers-to-ordinal-strings
0012 
0013     let numSuffixes = [ "th",
0014     "st",
0015     "nd",
0016     "rd",
0017     "th",
0018     "th",
0019     "th",
0020     "th",
0021     "th",
0022     "th"];
0023 
0024     let i = (number % 100);
0025     let j = (i > 10 && i < 20) ? 0 : (number % 10);
0026     return i18n(number + numSuffixes[j]);
0027 }
0028 
0029 function secondsToReminderLabel(seconds) { // Gives prettified time
0030 
0031     function numAndUnit(secs) {
0032         if(secs >= (2 * 24 * 60 * 60))
0033             return i18nc("%1 is 2 or more", "%1 days", Math.round(secs / (24*60*60))); // 2 days +
0034             else if (secs >= (1 * 24 * 60 * 60))
0035                 return i18n("1 day");
0036             else if (secs >= (2 * 60 * 60))
0037                 return i18nc("%1 is 2 or mores", "%1 hours", Math.round(secs / (60*60))); // 2 hours +
0038                 else if (secs >= (1 * 60 * 60))
0039                     return i18n("1 hour");
0040                 else
0041                     return i18n("%1 minutes", Math.round(secs / 60));
0042     }
0043 
0044     if (seconds < 0) {
0045         return i18n("%1 before start of event", numAndUnit(seconds * -1));
0046     } else if (seconds < 0) {
0047         return i18n("%1 after start of event", numAndUnit(seconds));
0048     } else {
0049         return i18n("On event start");
0050     }
0051 }
0052 
0053 function weeklyRecurrenceToString(recurrenceData) {
0054     let returnString = i18np("Every week", "Every %1 weeks", recurrenceData.frequency);
0055 
0056     if (recurrenceData.weekdays.filter(x => x === true).length > 0) {
0057         returnString = i18np("Every week on", "Every %1 weeks on", recurrenceData.frequency);
0058 
0059         for(let i = 0; i < recurrenceData.weekdays.length; i++) {
0060 
0061             if(recurrenceData.weekdays[i]) {
0062                 returnString += ` ${Qt.locale().dayName(i + 1, 0)},`; // C++ Qt weekdays go Mon->Sun, JS goes Sun->Sat, 0 is HACK for locale enum
0063             }
0064         }
0065         // Delete last comma
0066         returnString = returnString.slice(0, -1);
0067     }
0068 
0069     return returnString;
0070 }
0071 
0072 function monthPositionsToString(recurrenceData) {
0073     let returnString = "";
0074 
0075     for(let position of recurrenceData.monthPositions) {
0076         returnString += `${numberToString(position.pos)} ${Qt.locale().dayName(position.day)}, `
0077     }
0078 
0079     return returnString.slice(0, -2);
0080 }
0081 
0082 function yearlyPosRecurrenceToString(recurrenceData) {
0083     let months = "";
0084 
0085     for(let i = 0; i < recurrenceData.yearMonths.length; i++) {
0086         months += `${Qt.locale().monthName(recurrenceData.yearMonths[i])}, `;
0087     }
0088     months = months.slice(0, -2); // Remove space and comma
0089 
0090     return i18np("Every year on the %2 of %3", "Every %1 years on the %2 of %3",
0091                  recurrenceData.frequency, monthPositionsToString(recurrenceData), months);
0092 }
0093 
0094 function yearlyDaysRecurrenceToString(recurrenceData) {
0095     let dayNumsString = "";
0096 
0097     for(let dayNum of recurrenceData.yearDays) {
0098         dayNumsString += `${numberToString(dayNum)}, `;
0099     }
0100     return dayNumsString.slice(0, -2); // Remove space and comma
0101 }
0102 
0103 function recurrenceToString(recurrenceData) {
0104     switch(recurrenceData.type) {
0105         case 0:
0106             return i18n("Never");
0107 
0108         case 1:
0109             return i18np("Every minute", "Every %1 minutes", recurrenceData.frequency);
0110 
0111         case 2:
0112             return i18np("Every hour", "Every %1 hours", recurrenceData.frequency);
0113 
0114         case 3: // Daily
0115             return i18np("Every day", "Every %1 days", recurrenceData.frequency);
0116 
0117         case 4: // Weekly
0118             return weeklyRecurrenceToString(recurrenceData);
0119 
0120         case 5: // Monthly on position (e.g. third Monday)
0121             return i18np("Every month on the %2", "Every %1 months on the %2",
0122                   recurrenceData.frequency, monthPositionsToString(recurrenceData));
0123 
0124         case 6: // Monthly on day (1st of month)
0125             return i18np("Every month on the %2", "Every %1 months on the %2",
0126                          recurrenceData.frequency, numberToString(recurrenceData.startDateTime.getDate()));
0127 
0128         case 7: // Yearly on month (e.g. every April 15th)
0129             return i18np("Every year on the %2 of %3", "Every %1 years on the %2 of %3", recurrenceData.frequency,
0130                          numberToString(recurrenceData.startDateTime.getDate()), Qt.locale().monthName(recurrenceData.startDateTime.getMonth()));
0131 
0132         case 8: // Yearly on day (e.g. 192nd day of the year)
0133             return i18np("Every year on the %2 day of the year", "Every %1 years on the %2 day of the year", recurrenceData.frequency,
0134                          yearlyDaysRecurrenceToString(recurrenceData));
0135         case 9: // Yearly on position
0136             return yearlyPosRecurrenceToString(recurrenceData);
0137 
0138         case 10:
0139             return i18n("Complex recurrence rule");
0140 
0141         default:
0142             return i18n("Unknown");
0143     }
0144 
0145 }
0146 
0147 function recurrenceEndToString(recurrenceData) {
0148     switch(recurrenceData.duration) {
0149         case -1:
0150             return i18n("Never ends");
0151         case 0:
0152             return !isNaN(recurrenceData.endDateTime) ? i18n("Ends on %1", recurrenceData.endDateTime.toLocaleDateString()) : "";
0153         default:
0154             return i18n("Ends after %1 occurrences", recurrenceData.duration);
0155     }
0156 }
0157 
0158 function getDarkness(background) {
0159     // Thanks to Gojir4 from the Qt forum
0160     // https://forum.qt.io/topic/106362/best-way-to-set-text-color-for-maximum-contrast-on-background-color/
0161     var temp = Qt.darker(background, 1);
0162     var a = 1 - ( 0.299 * temp.r + 0.587 * temp.g + 0.114 * temp.b);
0163     return a;
0164 }
0165 
0166 function isDarkColor(background) {
0167     var temp = Qt.darker(background, 1);
0168     return temp.a > 0 && getDarkness(background) >= 0.4;
0169 }
0170 
0171 function getIncidenceDelegateBackgroundColor(backgroundColor, darkMode, eventEnd = null, pastEventsDimLevel = 0.0) {
0172     let bgColor = getDarkness(backgroundColor) > 0.9 ? Qt.lighter(backgroundColor, 1.5) : backgroundColor;
0173     if(darkMode) {
0174         if(getDarkness(backgroundColor) >= 0.5) {
0175             bgColor.a = 0.6;
0176         } else {
0177             bgColor.a = 0.4;
0178         }
0179     } else {
0180         bgColor.a = 0.7;
0181     }
0182 
0183     if(pastEventsDimLevel > 0 && eventEnd) {
0184         const now = new Date();
0185         if (modelData.endTime < now) {
0186             bgColor.a = Math.max(0.0, bgColor.a - pastEventsDimLevel);
0187         }
0188     }
0189     return bgColor;
0190 }
0191 
0192 function getIncidenceLabelColor(background, darkMode) {
0193 
0194     if(getDarkness(background) >= 0.9) {
0195         return "white";
0196     } else if(darkMode) {
0197         if(getDarkness(background) >= 0.5) {
0198             return Qt.lighter(background, 2.1);
0199         } else {
0200             return Qt.lighter(background, 1.5);
0201         }
0202     }
0203     else if(getDarkness(background) >= 0.68) {
0204         return Qt.lighter(background, 2.4);
0205     } else {
0206         return Qt.darker(background, 2.1);
0207     }
0208 
0209 }
0210 
0211 function todoDateTimeLabel(datetime, allDay, completed) {
0212     if(!isNaN(datetime.getTime())) {
0213         let now = new Date();
0214         let dateFormat = datetime.getFullYear() == now.getFullYear() ? "dddd dd MMMM" : "dddd dd MMMM yyyy";
0215         let dateString = datetime.toLocaleDateString(Qt.locale(), dateFormat);
0216         let timeString = allDay === true ?
0217             " " :
0218             i18nc("%1 is the time, spaces included to allow use of 'empty' string when an event is allday and has no time", " at %1 ", datetime.toLocaleTimeString(Qt.locale(), 1));
0219 
0220         if(DateUtils.sameDay(datetime, now)) {
0221             return (datetime < now) && !completed && !allDay ?
0222                 i18nc("No space since the %1 string, which includes the time (or not), includes this space", "Today%1(overdue)", timeString) :
0223                 i18nc("No space since the %1 string, which includes the time (or not), includes this space", "Today%1", timeString);
0224         } else if(DateUtils.sameDay(DateUtils.addDaysToDate(datetime, - 1), now)) { // Tomorrow
0225             return i18nc("No space since the %1 string, which includes the time (or not), includes this space", "Tomorrow%1", timeString);
0226         } else if(DateUtils.sameDay(DateUtils.addDaysToDate(datetime, 1), now)) { // Yesterday
0227             return !completed ?
0228                 i18nc("No space since the %1 string, which includes the time (or not), includes this space", "Yesterday%1(overdue)", timeString) :
0229                 i18nc("No space since the %1 string, which includes the time (or not), includes this space", "Yesterday");
0230         }
0231 
0232         return datetime < now && !completed ? dateString + timeString + i18n("(overdue)") : dateString + timeString;
0233     } else {
0234         return "";
0235     }
0236 }
0237 
0238 function priorityString(priority) {
0239     if(priority === 1) {
0240         return i18nc("%1 is the priority level number", "%1 (Highest priority)", priority);
0241     } else if (priority < 5) {
0242         return i18nc("%1 is the priority level number", "%1 (Mid-high priority)", priority);
0243     } else if (priority === 5) {
0244         return i18nc("%1 is the priority level number", "%1 (Medium priority)", priority);
0245     } else if (priority < 9) {
0246         return i18nc("%1 is the priority level number", "%1 (Mid-low priority)", priority);
0247     } else if (priority === 9) {
0248         return i18nc("%1 is the priority level number", "%1 (Lowest priority)", priority);
0249     } else {
0250         return i18n("No set priority level");
0251     }
0252 }