1/* 2 * Copyright 2023 Anyware Services 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17AmetysFront.Utils = { 18 19 /** 20 * OBSOLETE Use toRelativeDate 21 * Get a readable date, with relative time (eg: '4 days ago') for duration less that 7 days by default. 22 * @param {String} date The raw date 23 * @param {Number} [relativeLimit=7] limit in days for relative date 24 * @param {Number} [withoutRelativeSuffix=false] true to get the relative time without the suffix (eg: '4 days'). UNUSED 25 * @param {Boolean} [withTime=false] true to format date with hours 26 * @return {String} the readable date 27 */ 28 toMomentDate: function (date, relativeLimit, withoutRelativeSuffix, withTime) 29 { 30 return AmetysFront.Utils.toRelativeDate(date, { 31 relativeLimit: relativeLimit, 32 withTime: withTime === false ? false : true 33 }) 34 }, 35 36 /** 37 * Get a readable date, with relative time (eg: '4 days ago') for duration less that 7 days by default. 38 * @param {String} date The raw date 39 * @param {Object} config The transformation config 40 * @param {Number} [config.relativeLimit=7] limit in days for relative date 41 * @param {String} [config.weekday=undefined] Can be "long", "short", "narrow" or undefined. 42 * @param {String} [config.month="long"] Can be "numeric", "2-digit", "long", "short" or "narrow". 43 * @param {Boolean} [config.withTime=false] To display time 44 * @param {Boolean} [config.withSeconds=false] Only when withTime is true, to display seconds 45 * @param {Boolean} [config.locale=AmetysFront.getAppParameter("locale")] The locale code such as: en, fr... 46 * @return {String} the readable relative date 47 */ 48 toRelativeDate: function (date, config) 49 { 50 // The number of days of diff 51 function _daysDiff(d1, d2) 52 { 53 d1 = new Date(d1); 54 d1.setHours(0, 0, 0, 0); // at midnight 55 56 d2 = new Date(d2); 57 d2.setHours(0, 0, 0, 0); // at midnight 58 59 return (d1 - d2) / (24 * 60 * 60 * 1000); 60 } 61 62 config = config || {}; 63
64 let locale = config.locale || AmetysFront.getAppParameter("locale"); 65 66 let relativeLimit = config.relativeLimit || 7; 67 68 let withTime = config.withTime === true; 69 let month = config.month || "long"; 70 71 let now = new Date(); 72 let relativeDate = new Date(date); 73 74 let daysDiff = _daysDiff(now, relativeDate); 75 let timeDiff = (now - relativeDate) / 1000; 76 77 if (timeDiff < -59) // issue in user/server time-sync can lead to a close past event to appear in the near future 78 { 79 // Future dates are not supported 80 return AmetysFront.Utils.toReadableDate(date, { 81 "weekday": config.weekday, 82 "month": month, 83 "withTime": withTime, 84 "withSeconds": config.withSeconds, 85 "locale": locale 86 }); 87 } 88 89 let dayText = ""; 90 if (Math.min(relativeLimit, 7) <= daysDiff) 91 { 92 if (now.getFullYear() === relativeDate.getFullYear()) 93 { 94 // novembre 12 95 dayText = relativeDate.toLocaleString(locale, { 96 "weekday": config.weekday, 97 "day": "numeric", 98 "month": month 99 }); 100 } 101 else 102 { 103 // november 12, 2023 104 dayText = relativeDate.toLocaleString(locale, { 105 "weekday": config.weekday, 106 "day": "numeric", 107 "month": month, 108 "year": "numeric" 109 }); 110 } 111 } 112 else 113 { 114 // Less than a week, and less than the configured limit 115 switch(daysDiff) 116 { 117 case 0: // today 118 dayText = new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(0, "day"); 119 break; 120 case 1: // yesterday 121 dayText = new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(-1, "day"); 122 break; 123 default: // friday 124 dayText = relativeDate.toLocaleString(locale, { 125 "weekday": "long" // not using config.weekday that is for long dates 126 }); 127 } 128 } 129 130 if (withTime && daysDiff == 0) 131 { 132 if (timeDiff < 60) // a few seconds ago 133 { 134 return "il y a quelques secondes"; 135 } 136 else if (timeDiff < 3600) // x minutes ago 137 { 138 return new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(- Math.floor(timeDiff/60), "minutes"); 139 } 140 else if (timeDiff < 3*3600) // x hours ago 141 { 142 return new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(- Math.floor(timeDiff/60/60), "hours"); 143 } 144 else // xx:yy 145 { 146 return relativeDate.toLocaleTimeString(locale, { 147 hour: 'numeric', 148 minute:'2-digit' 149 }); 150 } 151 } 152 else 153 { 154 return dayText; 155 } 156 }, 157 158 /** 159 * Get ellapsed time between given date and nox, such as '4 days ago' 160 * @param {String} date The raw date 161 * @param {Object} config The transformation config 162 * @return {String} the readable relative date 163 */ 164 fromNow: function(date, config) 165 { 166 config = config || {}; 167
168 let locale = config.locale || AmetysFront.getAppParameter("locale"); 169 const now = new Date(); 170 const then = new Date(date); 171 const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" }); 172 173 const seconds = Math.round((now - then) / 1000); 174 const minutes = Math.round(seconds / 60); 175 const hours = Math.round(minutes / 60); 176 const days = Math.round(hours / 24); 177 const months = Math.round(days / 30.44); 178 const years = Math.round(days / 365.25); 179 180 if (seconds < 45) 181 { 182 return rtf.format(-minutes, "second"); 183 } 184 else if (minutes < 45) 185 { 186 return rtf.format(-minutes, "minute"); 187 } 188 else if (hours < 22) 189 { 190 return rtf.format(-hours, "hour"); 191 } 192 else if (days < 26) 193 { 194 return rtf.format(-days, "day"); 195 } 196 else if (months < 11) 197 { 198 return rtf.format(-months, "month"); 199 } 200 else 201 { 202 return rtf.format(-years, "year"); 203 } 204 }, 205 206 /** 207 * Convert an iso date to a readable date such as "Monday, nov. 6, 2024" 208 * @param {String} date The date in ISO format 209 * @param {Object} [config] Options to change the output format 210 * @param {String} [config.weekday=undefined] Can be "long", "short", "narrow" or undefined. 211 * @param {String} [config.month="short"] Can be "numeric", "2-digit", "long", "short" or "narrow". 212 * @param {Boolean} [config.withTime=false] To display time 213 * @param {Boolean} [config.withSeconds=false] Only when withTime is true, to display seconds 214 * @param {Boolean} [config.locale=AmetysFront.getAppParameter("locale")] The locale code such as: en, fr... 215 */ 216 toReadableDate(date, config) 217 { 218 config = config || {}; 219
220 let locale = config.locale || AmetysFront.getAppParameter("locale"); 221 222 let dateConfig = { 223 "weekday": config.weekday, 224 "day": "numeric", 225 "month": config.month || "short", 226 "year": "numeric" 227 }; 228 229 if (config.withTime) 230 { 231 dateConfig.hour = "numeric"; 232 dateConfig.minute = "2-digit"; 233 if (config.withSeconds) 234 { 235 dateConfig.second = "2-digit"; 236 } 237 } 238 239 return new Date(date).toLocaleString(locale, dateConfig); 240 }, 241 242 /** 243 * Format file size in readable size with units 244 * @param {Number} bytes the file length in bytes 245 * @param {Object} [config] Options to change the output format 246 * @param {String} [config.fixedValue=2] Number of digits after the decimal point. 247 */ 248 getReadableSize: function(bytes, config) 249 { 250 config = config || {}; 251 252 let fixedValue = config.fixedValue != undefined ? config.fixedValue : 1; 253 254 let limit = 1024; 255 let i = 0; 256 257 let units = [' ' + "Ko", 258 ' ' + "Mo", 259 ' ' + "Go", 260 ' ' + "Go"]; 261 262 let size = bytes; 263 264 if (Math.abs(bytes) <= limit) { 265 return bytes + ' ' + "octet"; 266 } 267 else { 268 while (size > limit) { 269 size = size / limit; 270 i++; 271 } 272 } 273 274 return Math.max(size, 0.1).toFixed(fixedValue) + units[i-1]; 275 }, 276 277 /** 278 * Remove special languages characters to allow easy comparison 279 * @param {String} s The string to deemphasize 280 * @return {String} The modified string 281 */ 282 deemphasize: function(s) 283 { 284 if (!s) return s; 285 286 s = s.replace(new RegExp(/[ÃÃÃÃÃà ]/g),"A"); 287 s = s.replace(new RegExp(/[à áâãäå]/g),"a"); 288 s = s.replace(new RegExp(/Ã/g),"AE"); 289 s = s.replace(new RegExp(/æ/g),"ae"); 290 s = s.replace(new RegExp(/Ã/g),"C"); 291 s = s.replace(new RegExp(/ç/g),"c"); 292 s = s.replace(new RegExp(/[ÃÃÃÃ]/g),"E"); 293 s = s.replace(new RegExp(/[èéêë]/g),"e"); 294 s = s.replace(new RegExp(/[ÃÃÃÃ]/g),"I"); 295 s = s.replace(new RegExp(/[ìÃîï]/g),"i"); 296 s = s.replace(new RegExp(/Ã/g),"N"); 297 s = s.replace(new RegExp(/ñ/g),"n"); 298 s = s.replace(new RegExp(/[ÃÃÃÃÃ]/g),"O"); 299 s = s.replace(new RegExp(/[òóôõö]/g),"o"); 300 s = s.replace(new RegExp(/Å/g),"OE"); 301 s = s.replace(new RegExp(/Å/g),"oe"); 302 s = s.replace(new RegExp(/[ÃÃÃÃ]/g),"U"); 303 s = s.replace(new RegExp(/[ùúûü]/g),"u"); 304 s = s.replace(new RegExp(/[ß]/g),"y"); 305 s = s.replace(new RegExp(/[ýÿ]/g),"y"); 306 307 return s; 308 }, 309 310 confirm: function(title, message, okFn) 311 { 312 $j('<div class="ui-confirm-box"></div>').html( message ).dialog({ 313 open: function(event, ui) { 314 $j(this).closest("[role=dialog]").find(".ui-dialog-titlebar-close").hide(); // Remove the closing 'X' from the dialog 315 $j(this).closest("[role=dialog]").attr("aria-modal", true); // aria attributes 316 }, 317 title: title, 318 resizable: true, 319 minWidth: 500, 320 maxWidth: 600, 321 modal: true, 322 zIndex: 10000, 323 buttons: { 324 "Oui": function() { 325 $j( this ).dialog( 'destroy' ); 326 okFn(); 327 }, 328 "Non": function() { 329 $j( this ).dialog( 'destroy' ); 330 } 331 } 332 }); 333 }, 334 335 info: function(title, message) 336 { 337 if ( !title ) 338 title = "Information"; 339 340 if ( !message ) 341 message = 'No Message to Display.'; 342 343 $j('<div class="ui-state-info ui-corner-all"></div>').html( message ).dialog({ 344 open: function(event, ui) { 345 $j(this).closest("[role=dialog]").find(".ui-dialog-titlebar-close").hide(); // Remove the closing 'X' from the dialog 346 $j(this).closest("[role=dialog]").attr("aria-modal", true); // aria attributes 347 }, 348 title: title, 349 resizable: true, 350 minWidth: 500, 351 maxWidth: 600, 352 modal: true, 353 zIndex: 10000, 354 buttons: { 355 "Ok": function() { 356 $j( this ).dialog( 'destroy' ); 357 } 358 } 359 }); 360 }, 361 362 error: function(message, title) 363 { 364 if ( !title ) 365 title = "Attention!"; 366 367 if ( !message ) 368 message = 'No Message to Display.'; 369 370 $j('<div class="ui-state-error ui-corner-all"></div>').html( message ).dialog({ 371 open: function(event, ui) { 372 $j(this).closest("[role=dialog]").find(".ui-dialog-titlebar-close").hide(); // Remove the closing 'X' from the dialog 373 $j(this).closest("[role=dialog]").attr("aria-modal", true); // aria attributes 374 }, 375 classes: { 376 "ui-dialog": "ametys-ui-dialog-error" 377 }, 378 title: title, 379 resizable: true, 380 minWidth: 500, 381 maxWidth: 600, 382 modal: true, 383 zIndex: 10000, 384 buttons: { 385 "Ok": function() { 386 $j( this ).dialog( 'destroy' ); 387 } 388 } 389 }); 390 } 391}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.