Droppped momentjs in favor of Intl

Uses a fallback for Intl.RelativeTimeFormat in Date.prototype.fromNow
This commit is contained in:
djmaze 2020-10-15 10:45:18 +02:00
parent 6f585e1f1d
commit 3bfe2c67d1
17 changed files with 478 additions and 97 deletions

View file

@ -97,7 +97,7 @@ Things might work in Edge 18, Firefox 50-62 and Chrome 54-68 due to one polyfill
* Removed jquery-scrollstop
* Removed jquery-mousewheel
* Removed matchmedia-polyfill
* Removed momentjs (localization still used)
* Removed momentjs (use Intl)
* Removed opentip (use CSS)
* Removed non-community (aka Prem/Premium/License) code
@ -106,22 +106,22 @@ RainLoop 1.14 vs SnappyMail
|js/* |RainLoop |Snappy |
|----------- |--------: |--------: |
|admin.js |2.130.942 | 774.093 |
|app.js |4.184.455 |2.426.328 |
|admin.js |2.130.942 | 669.377 |
|app.js |4.184.455 |2.418.170 |
|boot.js | 671.522 | 5.285 |
|libs.js | 647.614 | 247.440 |
|libs.js | 647.614 | 240.181 |
|polyfills.js | 325.834 | 0 |
|TOTAL |7.960.367 |3.453.146 |
|TOTAL |7.960.367 |3.333.013 |
|js/min/* |RainLoop |Snappy |Rain gzip |gzip |brotli |
|--------------- |--------: |--------: |--------: |--------: |--------: |
|admin.min.js | 252.147 | 103.575 | 73.657 | 27.571 | 23.931 |
|app.min.js | 511.202 | 327.895 |140.462 | 84.341 | 68.393 |
|admin.min.js | 252.147 | 92.040 | 73.657 | 23.656 | 20.584 |
|app.min.js | 511.202 | 325.791 |140.462 | 84.060 | 68.147 |
|boot.min.js | 66.007 | 2.918 | 22.567 | 1.500 | 1.275 |
|libs.min.js | 572.545 | 142.244 |176.720 | 50.849 | 45.242 |
|libs.min.js | 572.545 | 135.084 |176.720 | 48.346 | 42.934 |
|polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 |
|TOTAL |1.434.353 | 576.632 |424.718 |164.261 |138.841 |
|TOTAL (no admin) |1.182.206 | 473.057 |351.061 |136.690 |114.910 |
|TOTAL |1.434.353 | 555.833 |424.718 |157.562 |132.940 |
|TOTAL (no admin) |1.182.206 | 463.793 |351.061 |133.906 |112.356 |
For a user its around 61% smaller and faster than traditional RainLoop.

View file

@ -28,4 +28,3 @@ function __get_additional_configuration_name()
return '';
return defined('APP_SITE') && 0 < strlen(APP_SITE) ? APP_SITE.'.ini' : ''; // additional configuration file name
}

83
dev/prototype.js vendored
View file

@ -1,15 +1,12 @@
(w=>{
(doc=>{
Array.isNotEmpty = array => Array.isArray(array) && array.length;
Array.prototype.unique = function() { return this.filter((v, i, a) => a.indexOf(v) === i); };
Array.prototype.validUnique = function(fn) {
return this.filter((v, i, a) => (fn ? fn(v) : v) && a.indexOf(v) === i);
};
// Import momentjs locales function
w.moment = {
defineLocale: (name, config) => locale = config
};
Date.defineRelativeTimeFormat = config => relativeTime = config;
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
let formats = {
@ -26,20 +23,11 @@
D: {weekday: 'short'},
j: {day: 'numeric'},
l: {weekday: 'long'},
// N: {},
// w: {},
// z: {},
// Week
// W: {},
// Month
F: {month: 'long'},
m: {month: '2-digit'},
M: {month: 'short'},
n: {month: 'numeric'},
// t: {},
// Year
// L: {},
// o: {},
Y: {year: 'numeric'},
y: {year: '2-digit'},
// Time
@ -52,38 +40,15 @@
i: {minute: '2-digit'},
s: {second: '2-digit'},
u: {fractionalSecondDigits: 3},
// Timezone
// O: return UTC ? 'Z' : (d.Z > 0 ? '+' : '-') + pad2(Math.abs(d.Z / 60)) + '00';
// P: return UTC ? 'Z' : (d.Z > 0 ? '+' : '-') + pad2(Math.abs(d.Z / 60)) + :' + pad2(Math.abs(d.Z % 60));
// T: return UTC ? 'UTC' : new Date(d.Y, 0, 1).toTimeString().replace(/^.+ \(?([^)]+)\)?$/, '$1');
Z: {timeZone: 'UTC'}
// Full Date/Time
// c: {},
// r: {},
// U: {},
// options.timeZoneName = 'short';
},
locale = {
relativeTime : {
future : 'in %s',
past : '%s ago',
s : 'a few seconds',
ss : '%d seconds',
m : 'a minute',
mm : '%d minutes',
h : 'an hour',
hh : '%d hours',
d : 'a day',
dd : '%d days',
M : 'a month',
MM : '%d months',
y : 'a year',
yy : '%d years'
}
relativeTime = {
// see /rainloop/v/0.0.0/app/localization/relativetimeformat/
},
pad2 = v => 10 > v ? '0' + v : v;
// Simulate PHP's date function
// Format momentjs/PHP date formats to Intl.DateTimeFormat
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
Date.prototype.format = function (str, UTC) {
if ('Y-m-d\\TH:i:s' == str) {
return this.getFullYear() + '-' + pad2(1 + this.getMonth()) + '-' + pad2(this.getDate())
@ -103,29 +68,31 @@
}
formats[str] = options;
}
return new Intl.DateTimeFormat(document.documentElement.lang, options).format(this);
return new Intl.DateTimeFormat(doc.documentElement.lang, options).format(this);
};
// Simulate momentjs fromNow function
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat
Date.prototype.fromNow = function() {
let format = 's',
seconds = (Date.now() - this.getTime()) / 1000,
str = locale.relativeTime[0 < seconds ? 'past' : 'future'],
t = [[60,'m'],[3600,'h'],[86400,'d'],[2628000,'M'],[31536000,'y']],
i = 5;
seconds = Math.abs(seconds);
let unit = 'second',
value = (this.getTime() - Date.now()) / 1000,
t = [[60,'minute'],[3600,'hour'],[86400,'day'],[2628000,'month'],[31536000,'year']],
i = 5,
abs = Math.abs(value);
while (i--) {
if (t[i][0] <= seconds) {
seconds = seconds / t[i][0];
format = t[i][1];
if (t[i][0] <= abs) {
value = Math.round(value / t[i][0]);
unit = t[i][1];
break;
}
}
seconds = Math.round(seconds);
if (1 < seconds) {
format += format;
if (Intl.RelativeTimeFormat) {
let rtf = new Intl.RelativeTimeFormat(doc.documentElement.lang);
return rtf.format(value, unit);
}
return str.replace('%s', locale.relativeTime[format].replace('%d', seconds));
abs = Math.abs(value);
let rtf = relativeTime.long[unit][0 > value ? 'past' : 'future'],
plural = relativeTime.plural(abs);
return (rtf[plural] || rtf).replace('{0}', abs);
};
Element.prototype.closestWithin = function(selector, parent) {
@ -134,7 +101,7 @@
};
Element.fromHTML = string => {
const template = document.createElement('template');
const template = doc.createElement('template');
template.innerHTML = string.trim();
return template.content.firstChild;
};
@ -174,4 +141,4 @@
};
}
})(this);
})(document);

View file

@ -80,7 +80,6 @@
"gulp-through": "0.4.0",
"gulp-util": "3.0.8",
"gulp-zip": "5.0.1",
"moment": "2.24.0",
"node-fs": "0.1.7",
"openpgp": "2.6.2",
"prettier": "1.19.1",

View file

@ -1071,17 +1071,16 @@ class ServiceActions
$sLanguage = strtr($sLanguage, '_', '-');
$sMoment = 'window.moment && window.moment.locale && window.moment.locale(\'en\');';
$options = [$sLanguage, \substr($sLanguage, 0, 2)];
$sTimeFormat = '';
$options = [$sLanguage, \substr($sLanguage, 0, 2), 'en'];
foreach ($options as $lang) {
$sMomentFileName = APP_VERSION_ROOT_PATH.'app/localization/moment/'.$lang.'.js';
if (\is_file($sMomentFileName)) {
$sMoment = \file_get_contents($sMomentFileName);
$sMoment = \preg_replace('/\/\/[^\n]+\n/', '', $sMoment);
$sFileName = APP_VERSION_ROOT_PATH.'app/localization/relativetimeformat/'.$lang.'.json';
if (\is_file($sFileName)) {
$sTimeFormat = \preg_replace('/^\\s+/', '', \file_get_contents($sFileName));
break;
}
}
return 'document.documentElement.lang = "'.$sLanguage.'";window.rainloopI18N='.$sResult.';'.$sMoment;
return "document.documentElement.lang = '{$sLanguage}';\nwindow.rainloopI18N={$sResult};\nDate.defineRelativeTimeFormat({$sTimeFormat});";
}
}

View file

@ -1 +1,3 @@
https://www.transifex.com/rainloop/rainloop-webmail/
https://www.transifex.com/rainloop/rainloop-webmail/
https://tools.ietf.org/html/bcp47
relativetimeformat based on Unicode CLDR

View file

@ -0,0 +1,75 @@
{
long: {
year: {
past: {
one: "vor {0} Jahr",
other: "vor {0} Jahren"
},
future: {
one: "in {0} Jahr",
other: "in {0} Jahren"
}
},
month: {
past: {
one: "vor {0} Monat",
other: "vor {0} Monaten"
},
future: {
one: "in {0} Monat",
other: "in {0} Monaten"
}
},
week: {
past: {
one: "vor {0} Woche",
other: "vor {0} Wochen"
},
future: {
one: "in {0} Woche",
other: "in {0} Wochen"
}
},
day: {
past: {
one: "vor {0} Tag",
other: "vor {0} Tagen"
},
future: {
one: "in {0} Tag",
other: "in {0} Tagen"
}
},
hour: {
past: {
one: "vor {0} Stunde",
other: "vor {0} Stunden"
},
future: {
one: "in {0} Stunde",
other: "in {0} Stunden"
}
},
minute: {
past: {
one: "vor {0} Minute",
other: "vor {0} Minuten"
},
future: {
one: "in {0} Minute",
other: "in {0} Minuten"
}
},
second: {
past: {
one: "vor {0} Sekunde",
other: "vor {0} Sekunden"
},
future: {
one: "in {0} Sekunde",
other: "in {0} Sekunden"
}
}
},
plural: n => 1 == n ? 'one' : 'other'
}

View file

@ -0,0 +1,75 @@
{
long: {
year: {
past: {
one: "{0} year ago",
other: "{0} years ago"
},
future: {
one: "in {0} year",
other: "in {0} years"
}
},
month: {
past: {
one: "{0} month ago",
other: "{0} months ago"
},
future: {
one: "in {0} month",
other: "in {0} months"
}
},
week: {
past: {
one: "{0} week ago",
other: "{0} weeks ago"
},
future: {
one: "in {0} week",
other: "in {0} weeks"
}
},
day: {
past: {
one: "{0} day ago",
other: "{0} days ago"
},
future: {
one: "in {0} day",
other: "in {0} days"
}
},
hour: {
past: {
one: "{0} hour ago",
other: "{0} hours ago"
},
future: {
one: "in {0} hour",
other: "in {0} hours"
}
},
minute: {
past: {
one: "{0} minute ago",
other: "{0} minutes ago"
},
future: {
one: "in {0} minute",
other: "in {0} minutes"
}
},
second: {
past: {
one: "{0} second ago",
other: "{0} seconds ago"
},
future: {
one: "in {0} second",
other: "in {0} seconds"
}
}
},
plural: n => 1 == n ? 'one' : 'other'
}

View file

@ -0,0 +1,76 @@
{
long: {
year: {
past: {
one: "{0} year ago",
other: "{0} years ago"
},
future: {
one: "in {0} year",
other: "in {0} years"
}
},
month: {
past: {
one: "{0} month ago",
other: "{0} months ago"
},
future: {
one: "in {0} month",
other: "in {0} months"
}
},
week: {
past: {
one: "{0} week ago",
other: "{0} weeks ago"
},
future: {
one: "in {0} week",
other: "in {0} weeks"
}
},
day: {
past: {
one: "{0} day ago",
other: "{0} days ago"
},
future: {
one: "in {0} day",
other: "in {0} days"
}
},
hour: {
past: {
one: "{0} hour ago",
other: "{0} hours ago"
},
future: {
one: "in {0} hour",
other: "in {0} hours"
}
},
minute: {
past: {
one: "{0} minute ago",
other: "{0} minutes ago"
},
future: {
one: "in {0} minute",
other: "in {0} minutes"
}
},
second: {
past: {
one: "{0} second ago",
other: "{0} seconds ago"
},
future: {
one: "in {0} second",
other: "in {0} seconds"
}
}
},
// 'zero', 'one', 'two', 'few', 'many', 'other'
plural: n => 1 == n ? 'one' : 'other'
}

View file

@ -0,0 +1,75 @@
{
long: {
year: {
past: {
one: "hace {0} año",
other: "hace {0} años"
},
future: {
one: "dentro de {0} año",
other: "dentro de {0} años"
}
},
month: {
past: {
one: "hace {0} mes",
other: "hace {0} meses"
},
future: {
one: "dentro de {0} mes",
other: "dentro de {0} meses"
}
},
week: {
past: {
one: "hace {0} semana",
other: "hace {0} semanas"
},
future: {
one: "dentro de {0} semana",
other: "dentro de {0} semanas"
}
},
day: {
past: {
one: "hace {0} día",
other: "hace {0} días"
},
future: {
one: "dentro de {0} día",
other: "dentro de {0} días"
}
},
hour: {
past: {
one: "hace {0} hora",
other: "hace {0} horas"
},
future: {
one: "dentro de {0} hora",
other: "dentro de {0} horas"
}
},
minute: {
past: {
one: "hace {0} minuto",
other: "hace {0} minutos"
},
future: {
one: "dentro de {0} minuto",
other: "dentro de {0} minutos"
}
},
second: {
past: {
one: "hace {0} segundo",
other: "hace {0} segundos"
},
future: {
one: "dentro de {0} segundo",
other: "dentro de {0} segundos"
}
}
},
plural: n => 1 == n ? 'one' : 'other'
}

View file

@ -0,0 +1,69 @@
{
long: {
year: {
past: {
one: "il y a {0} an",
other: "il y a {0} ans"
},
future: {
one: "dans {0} an",
other: "dans {0} ans"
}
},
month: {
past: "il y a {0} mois",
future: "dans {0} mois"
},
week: {
past: {
one: "il y a {0} semaine",
other: "il y a {0} semaines"
},
future: {
one: "dans {0} semaine",
other: "dans {0} semaines"
}
},
day: {
past: {
one: "il y a {0} jour",
other: "il y a {0} jours"
},
future: {
one: "dans {0} jour",
other: "dans {0} jours"
}
},
hour: {
past: {
one: "il y a {0} heure",
other: "il y a {0} heures"
},
future: {
one: "dans {0} heure",
other: "dans {0} heures"
}
},
minute: {
past: {
one: "il y a {0} minute",
other: "il y a {0} minutes"
},
future: {
one: "dans {0} minute",
other: "dans {0} minutes"
}
},
second: {
past: {
one: "il y a {0} seconde",
other: "il y a {0} secondes"
},
future: {
one: "dans {0} seconde",
other: "dans {0} secondes"
}
}
},
plural: n => 1 == n ? 'one' : 'other'
}

View file

@ -0,0 +1,63 @@
{
long: {
year: {
past: "{0} jaar geleden",
future: "over {0} jaar"
},
month: {
past: {
one: "{0} maand geleden",
other: "{0} maanden geleden"
},
future: {
one: "over {0} maand",
other: "over {0} maanden"
}
},
week: {
past: {
one: "{0} week geleden",
other: "{0} weken geleden"
},
future: {
one: "over {0} week",
other: "over {0} weken"
}
},
day: {
past: {
one: "{0} dag geleden",
other: "{0} dagen geleden"
},
future: {
one: "over {0} dag",
other: "over {0} dagen"
}
},
hour: {
past: "{0} uur geleden",
future: "over {0} uur"
},
minute: {
past: {
one: "{0} minuut geleden",
other: "{0} minuten geleden"
},
future: {
one: "over {0} minuut",
other: "over {0} minuten"
}
},
second: {
past: {
one: "{0} seconde geleden",
other: "{0} seconden geleden"
},
future: {
one: "over {0} seconde",
other: "over {0} seconden"
}
}
},
plural: n => 1 == n ? 'one' : 'other'
}

View file

@ -30,7 +30,6 @@ config.paths.static = 'rainloop/v/' + config.devVersion + '/static/';
config.paths.staticJS = 'rainloop/v/' + config.devVersion + '/static/js/';
config.paths.staticMinJS = 'rainloop/v/' + config.devVersion + '/static/js/min/';
config.paths.staticCSS = 'rainloop/v/' + config.devVersion + '/static/css/';
config.paths.momentLocales = 'rainloop/v/' + config.devVersion + '/app/localization/moment/';
config.paths.assets = {
src: 'assets/**/*.*'
@ -65,9 +64,6 @@ config.paths.css = {
};
config.paths.js = {
moment: {
locales: ['node_modules/moment/locale/*.js']
},
libs: {
name: 'libs.js',
src: [

View file

@ -6,13 +6,6 @@ const stripbom = require('gulp-stripbom');
const { config } = require('./config');
const { del } = require('./common');
// moment
const momentLocalesClear = () => del('rainloop/v/' + config.devVersion + '/app/localization/moment/*.js');
const momentLocales = () => gulp.src(config.paths.js.moment.locales).pipe(gulp.dest(config.paths.momentLocales));
const moment = gulp.series(momentLocalesClear, momentLocales);
// lightgallery
const lightgalleryFontsClear = () => del('rainloop/v/' + config.devVersion + '/static/css/fonts/lg.*');
@ -61,4 +54,4 @@ const ckeditorSetup = () =>
const ckeditor = gulp.series(ckeditorClear, ckeditorCopy, ckeditorCopyPlugins, ckeditorSetup);
exports.vendors = gulp.parallel(moment, squire, ckeditor, fontastic, lightgallery);
exports.vendors = gulp.parallel(squire, ckeditor, fontastic, lightgallery);

View file

@ -84,7 +84,6 @@ module.exports = function(publicPath, pro, mode) {
*/
},
plugins: [
// new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env': {

View file

@ -4627,11 +4627,6 @@ mkdirp@^0.5.1:
dependencies:
minimist "^1.2.5"
moment@2.24.0:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"