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 }