Merge branch 'upgrade-timeago' into 'master'
Replace jQuery.timeago with timeago.js ## What does this MR do? Replaces jQuery.timeago with [timeago.js](https://github.com/hustcc/timeago.js) ## Are there points in the code the reviewer needs to double check? * Check to make sure its working everywhere 😄 * Check to make sure the timeago wording matches what we have now (I think I've got this down but an extra pair of 👀 would help too) ## Why was this MR needed? * The jQuery.timeago version we have is outdated * timeago.js is smaller (7.19 KB => 4.52 KB) * timeago.js has no jQuery dependency * removes all inline javascript ⚔️ for timeago ## Screenshots (if relevant) None ## Does this MR meet the acceptance criteria? - [x] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added - Tests - [x] All builds are passing - [x] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html) - [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides) - [x] Branch has no merge conflicts with `master` (if you do - rebase it please) - [x] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) ## What are the relevant issue numbers? Closes #21793 See merge request !6274
This commit is contained in:
commit
9eb9d05b45
19 changed files with 320 additions and 289 deletions
|
@ -13,12 +13,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
Activities.prototype.updateTooltips = function() {
|
Activities.prototype.updateTooltips = function() {
|
||||||
return gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
|
gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
|
||||||
};
|
};
|
||||||
|
|
||||||
Activities.prototype.reloadActivities = function() {
|
Activities.prototype.reloadActivities = function() {
|
||||||
$(".content_list").html('');
|
$(".content_list").html('');
|
||||||
return Pager.init(20, true);
|
Pager.init(20, true, false, this.updateTooltips);
|
||||||
};
|
};
|
||||||
|
|
||||||
Activities.prototype.toggleFilter = function(sender) {
|
Activities.prototype.toggleFilter = function(sender) {
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
/*= require jquery-ui/sortable */
|
/*= require jquery-ui/sortable */
|
||||||
/*= require jquery_ujs */
|
/*= require jquery_ujs */
|
||||||
/*= require jquery.endless-scroll */
|
/*= require jquery.endless-scroll */
|
||||||
/*= require jquery.timeago */
|
|
||||||
/*= require jquery.highlight */
|
/*= require jquery.highlight */
|
||||||
/*= require jquery.waitforimages */
|
/*= require jquery.waitforimages */
|
||||||
/*= require jquery.atwho */
|
/*= require jquery.atwho */
|
||||||
|
@ -238,8 +237,5 @@
|
||||||
|
|
||||||
// bind sidebar events
|
// bind sidebar events
|
||||||
new gl.Sidebar();
|
new gl.Sidebar();
|
||||||
|
|
||||||
// Custom time ago
|
|
||||||
gl.utils.shortTimeAgo($('.js-short-timeago'));
|
|
||||||
});
|
});
|
||||||
}).call(this);
|
}).call(this);
|
||||||
|
|
|
@ -169,7 +169,7 @@
|
||||||
$date = $('.js-artifacts-remove');
|
$date = $('.js-artifacts-remove');
|
||||||
if ($date.length) {
|
if ($date.length) {
|
||||||
date = $date.text();
|
date = $date.text();
|
||||||
return $date.text($.timefor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '));
|
return $date.text(gl.utils.timefor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,8 @@
|
||||||
success: function(html) {
|
success: function(html) {
|
||||||
loading.hide();
|
loading.hide();
|
||||||
$target.html(html);
|
$target.html(html);
|
||||||
return $('.js-timeago', $target).timeago();
|
var className = '.' + $target[0].className.replace(' ', '.');
|
||||||
|
gl.utils.localTimeAgo($('.js-timeago', className));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -119,31 +119,12 @@
|
||||||
parser.href = url;
|
parser.href = url;
|
||||||
return parser;
|
return parser;
|
||||||
};
|
};
|
||||||
|
|
||||||
gl.utils.cleanupBeforeFetch = function() {
|
gl.utils.cleanupBeforeFetch = function() {
|
||||||
// Unbind scroll events
|
// Unbind scroll events
|
||||||
$(document).off('scroll');
|
$(document).off('scroll');
|
||||||
// Close any open tooltips
|
// Close any open tooltips
|
||||||
$('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy');
|
$('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy');
|
||||||
};
|
};
|
||||||
|
|
||||||
return jQuery.timefor = function(time, suffix, expiredLabel) {
|
|
||||||
var suffixFromNow, timefor;
|
|
||||||
if (!time) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
suffix || (suffix = 'remaining');
|
|
||||||
expiredLabel || (expiredLabel = 'Past due');
|
|
||||||
jQuery.timeago.settings.allowFuture = true;
|
|
||||||
suffixFromNow = jQuery.timeago.settings.strings.suffixFromNow;
|
|
||||||
jQuery.timeago.settings.strings.suffixFromNow = suffix;
|
|
||||||
timefor = $.timeago(time);
|
|
||||||
if (timefor.indexOf('ago') > -1) {
|
|
||||||
timefor = expiredLabel;
|
|
||||||
}
|
|
||||||
jQuery.timeago.settings.strings.suffixFromNow = suffixFromNow;
|
|
||||||
return timefor;
|
|
||||||
};
|
|
||||||
})(window);
|
})(window);
|
||||||
|
|
||||||
}).call(this);
|
}).call(this);
|
||||||
|
|
|
@ -22,51 +22,64 @@
|
||||||
if (setTimeago == null) {
|
if (setTimeago == null) {
|
||||||
setTimeago = true;
|
setTimeago = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$timeagoEls.each(function() {
|
$timeagoEls.each(function() {
|
||||||
var $el;
|
var $el = $(this);
|
||||||
$el = $(this);
|
$el.attr('title', gl.utils.formatDate($el.attr('datetime')));
|
||||||
return $el.attr('title', gl.utils.formatDate($el.attr('datetime')));
|
|
||||||
|
if (setTimeago) {
|
||||||
|
// Recreate with custom template
|
||||||
|
$el.tooltip({
|
||||||
|
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
gl.utils.renderTimeago($el);
|
||||||
});
|
});
|
||||||
if (setTimeago) {
|
|
||||||
$timeagoEls.timeago();
|
|
||||||
$timeagoEls.tooltip('destroy');
|
|
||||||
// Recreate with custom template
|
|
||||||
return $timeagoEls.tooltip({
|
|
||||||
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
w.gl.utils.shortTimeAgo = function($el) {
|
w.gl.utils.getTimeago = function() {
|
||||||
var shortLocale, tmpLocale;
|
var locale = function(number, index) {
|
||||||
shortLocale = {
|
return [
|
||||||
prefixAgo: null,
|
['less than a minute ago', 'a while'],
|
||||||
prefixFromNow: null,
|
['less than a minute ago', 'in %s seconds'],
|
||||||
suffixAgo: 'ago',
|
['about a minute ago', 'in 1 minute'],
|
||||||
suffixFromNow: 'from now',
|
['%s minutes ago', 'in %s minutes'],
|
||||||
seconds: '1 min',
|
['about an hour ago', 'in 1 hour'],
|
||||||
minute: '1 min',
|
['about %s hours ago', 'in %s hours'],
|
||||||
minutes: '%d mins',
|
['a day ago', 'in 1 day'],
|
||||||
hour: '1 hr',
|
['%s days ago', 'in %s days'],
|
||||||
hours: '%d hrs',
|
['a week ago', 'in 1 week'],
|
||||||
day: '1 day',
|
['%s weeks ago', 'in %s weeks'],
|
||||||
days: '%d days',
|
['a month ago', 'in 1 month'],
|
||||||
month: '1 month',
|
['%s months ago', 'in %s months'],
|
||||||
months: '%d months',
|
['a year ago', 'in 1 year'],
|
||||||
year: '1 year',
|
['%s years ago', 'in %s years']
|
||||||
years: '%d years',
|
][index];
|
||||||
wordSeparator: ' ',
|
|
||||||
numbers: []
|
|
||||||
};
|
};
|
||||||
tmpLocale = $.timeago.settings.strings;
|
|
||||||
$el.each(function(el) {
|
timeago.register('gl_en', locale);
|
||||||
var $el1;
|
return timeago();
|
||||||
$el1 = $(this);
|
};
|
||||||
return $el1.attr('title', gl.utils.formatDate($el.attr('datetime')));
|
|
||||||
});
|
w.gl.utils.timeFor = function(time, suffix, expiredLabel) {
|
||||||
$.timeago.settings.strings = shortLocale;
|
var timefor;
|
||||||
$el.timeago();
|
if (!time) {
|
||||||
$.timeago.settings.strings = tmpLocale;
|
return '';
|
||||||
|
}
|
||||||
|
suffix || (suffix = 'remaining');
|
||||||
|
expiredLabel || (expiredLabel = 'Past due');
|
||||||
|
timefor = gl.utils.getTimeago().format(time).replace('in', '');
|
||||||
|
if (timefor.indexOf('ago') > -1) {
|
||||||
|
timefor = expiredLabel;
|
||||||
|
} else {
|
||||||
|
timefor = timefor.trim() + ' ' + suffix;
|
||||||
|
}
|
||||||
|
return timefor;
|
||||||
|
};
|
||||||
|
|
||||||
|
w.gl.utils.renderTimeago = function($element) {
|
||||||
|
var timeagoInstance = gl.utils.getTimeago();
|
||||||
|
timeagoInstance.render($element, 'gl_en');
|
||||||
};
|
};
|
||||||
|
|
||||||
w.gl.utils.getDayDifference = function(a, b) {
|
w.gl.utils.getDayDifference = function(a, b) {
|
||||||
|
@ -75,7 +88,7 @@
|
||||||
var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
|
var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
|
||||||
|
|
||||||
return Math.floor((date2 - date1) / millisecondsPerDay);
|
return Math.floor((date2 - date1) / millisecondsPerDay);
|
||||||
}
|
};
|
||||||
|
|
||||||
})(window);
|
})(window);
|
||||||
|
|
||||||
|
|
237
app/assets/javascripts/lib/utils/timeago.js
Normal file
237
app/assets/javascripts/lib/utils/timeago.js
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2016 hustcc
|
||||||
|
* License: MIT
|
||||||
|
* Version: v2.0.2
|
||||||
|
* https://github.com/hustcc/timeago.js
|
||||||
|
* This is a forked from (https://gitlab.com/ClemMakesApps/timeago.js)
|
||||||
|
**/
|
||||||
|
/* eslint-disable */
|
||||||
|
/* jshint expr: true */
|
||||||
|
!function (root, factory) {
|
||||||
|
if (typeof module === 'object' && module.exports)
|
||||||
|
module.exports = factory(root);
|
||||||
|
else
|
||||||
|
root.timeago = factory(root);
|
||||||
|
}(typeof window !== 'undefined' ? window : this,
|
||||||
|
function () {
|
||||||
|
var cnt = 0, // the timer counter, for timer key
|
||||||
|
indexMapEn = 'second_minute_hour_day_week_month_year'.split('_'),
|
||||||
|
|
||||||
|
// build-in locales: en & zh_CN
|
||||||
|
locales = {
|
||||||
|
'en': function(number, index) {
|
||||||
|
if (index === 0) return ['just now', 'right now'];
|
||||||
|
var unit = indexMapEn[parseInt(index / 2)];
|
||||||
|
if (number > 1) unit += 's';
|
||||||
|
return [number + ' ' + unit + ' ago', 'in ' + number + ' ' + unit];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// second, minute, hour, day, week, month, year(365 days)
|
||||||
|
SEC_ARRAY = [60, 60, 24, 7, 365/7/12, 12],
|
||||||
|
SEC_ARRAY_LEN = 6,
|
||||||
|
ATTR_DATETIME = 'datetime';
|
||||||
|
|
||||||
|
// format Date / string / timestamp to Date instance.
|
||||||
|
function toDate(input) {
|
||||||
|
if (input instanceof Date) return input;
|
||||||
|
if (!isNaN(input)) return new Date(toInt(input));
|
||||||
|
if (/^\d+$/.test(input)) return new Date(toInt(input, 10));
|
||||||
|
input = (input || '').trim().replace(/\.\d+/, '') // remove milliseconds
|
||||||
|
.replace(/-/, '/').replace(/-/, '/')
|
||||||
|
.replace(/T/, ' ').replace(/Z/, ' UTC')
|
||||||
|
.replace(/([\+\-]\d\d)\:?(\d\d)/, ' $1$2'); // -04:00 -> -0400
|
||||||
|
return new Date(input);
|
||||||
|
}
|
||||||
|
// change f into int, remove Decimal. just for code compression
|
||||||
|
function toInt(f) {
|
||||||
|
return parseInt(f);
|
||||||
|
}
|
||||||
|
// format the diff second to *** time ago, with setting locale
|
||||||
|
function formatDiff(diff, locale, defaultLocale) {
|
||||||
|
// if locale is not exist, use defaultLocale.
|
||||||
|
// if defaultLocale is not exist, use build-in `en`.
|
||||||
|
// be sure of no error when locale is not exist.
|
||||||
|
locale = locales[locale] ? locale : (locales[defaultLocale] ? defaultLocale : 'en');
|
||||||
|
// if (! locales[locale]) locale = defaultLocale;
|
||||||
|
var i = 0;
|
||||||
|
agoin = diff < 0 ? 1 : 0; // timein or timeago
|
||||||
|
diff = Math.abs(diff);
|
||||||
|
|
||||||
|
for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
|
||||||
|
diff /= SEC_ARRAY[i];
|
||||||
|
}
|
||||||
|
diff = toInt(diff);
|
||||||
|
i *= 2;
|
||||||
|
|
||||||
|
if (diff > (i === 0 ? 9 : 1)) i += 1;
|
||||||
|
return locales[locale](diff, i)[agoin].replace('%s', diff);
|
||||||
|
}
|
||||||
|
// calculate the diff second between date to be formated an now date.
|
||||||
|
function diffSec(date, nowDate) {
|
||||||
|
nowDate = nowDate ? toDate(nowDate) : new Date();
|
||||||
|
return (nowDate - toDate(date)) / 1000;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* nextInterval: calculate the next interval time.
|
||||||
|
* - diff: the diff sec between now and date to be formated.
|
||||||
|
*
|
||||||
|
* What's the meaning?
|
||||||
|
* diff = 61 then return 59
|
||||||
|
* diff = 3601 (an hour + 1 second), then return 3599
|
||||||
|
* make the interval with high performace.
|
||||||
|
**/
|
||||||
|
function nextInterval(diff) {
|
||||||
|
var rst = 1, i = 0, d = Math.abs(diff);
|
||||||
|
for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
|
||||||
|
diff /= SEC_ARRAY[i];
|
||||||
|
rst *= SEC_ARRAY[i];
|
||||||
|
}
|
||||||
|
// return leftSec(d, rst);
|
||||||
|
d = d % rst;
|
||||||
|
d = d ? rst - d : rst;
|
||||||
|
return Math.ceil(d);
|
||||||
|
}
|
||||||
|
// get the datetime attribute, jQuery and DOM
|
||||||
|
function getDateAttr(node) {
|
||||||
|
if (node.getAttribute) return node.getAttribute(ATTR_DATETIME);
|
||||||
|
if(node.attr) return node.attr(ATTR_DATETIME);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* timeago: the function to get `timeago` instance.
|
||||||
|
* - nowDate: the relative date, default is new Date().
|
||||||
|
* - defaultLocale: the default locale, default is en. if your set it, then the `locale` parameter of format is not needed of you.
|
||||||
|
*
|
||||||
|
* How to use it?
|
||||||
|
* var timeagoLib = require('timeago.js');
|
||||||
|
* var timeago = timeagoLib(); // all use default.
|
||||||
|
* var timeago = timeagoLib('2016-09-10'); // the relative date is 2016-09-10, so the 2016-09-11 will be 1 day ago.
|
||||||
|
* var timeago = timeagoLib(null, 'zh_CN'); // set default locale is `zh_CN`.
|
||||||
|
* var timeago = timeagoLib('2016-09-10', 'zh_CN'); // the relative date is 2016-09-10, and locale is zh_CN, so the 2016-09-11 will be 1天前.
|
||||||
|
**/
|
||||||
|
function Timeago(nowDate, defaultLocale) {
|
||||||
|
var timers = {}; // real-time render timers
|
||||||
|
// if do not set the defaultLocale, set it with `en`
|
||||||
|
if (! defaultLocale) defaultLocale = 'en'; // use default build-in locale
|
||||||
|
// what the timer will do
|
||||||
|
function doRender(node, date, locale, cnt) {
|
||||||
|
var diff = diffSec(date, nowDate);
|
||||||
|
node.innerHTML = formatDiff(diff, locale, defaultLocale);
|
||||||
|
// waiting %s seconds, do the next render
|
||||||
|
timers['k' + cnt] = setTimeout(function() {
|
||||||
|
doRender(node, date, locale, cnt);
|
||||||
|
}, nextInterval(diff) * 1000);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* nextInterval: calculate the next interval time.
|
||||||
|
* - diff: the diff sec between now and date to be formated.
|
||||||
|
*
|
||||||
|
* What's the meaning?
|
||||||
|
* diff = 61 then return 59
|
||||||
|
* diff = 3601 (an hour + 1 second), then return 3599
|
||||||
|
* make the interval with high performace.
|
||||||
|
**/
|
||||||
|
// this.nextInterval = function(diff) { // for dev test
|
||||||
|
// var rst = 1, i = 0, d = Math.abs(diff);
|
||||||
|
// for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
|
||||||
|
// diff /= SEC_ARRAY[i];
|
||||||
|
// rst *= SEC_ARRAY[i];
|
||||||
|
// }
|
||||||
|
// // return leftSec(d, rst);
|
||||||
|
// d = d % rst;
|
||||||
|
// d = d ? rst - d : rst;
|
||||||
|
// return Math.ceil(d);
|
||||||
|
// }; // for dev test
|
||||||
|
/**
|
||||||
|
* format: format the date to *** time ago, with setting or default locale
|
||||||
|
* - date: the date / string / timestamp to be formated
|
||||||
|
* - locale: the formated string's locale name, e.g. en / zh_CN
|
||||||
|
*
|
||||||
|
* How to use it?
|
||||||
|
* var timeago = require('timeago.js')();
|
||||||
|
* timeago.format(new Date(), 'pl'); // Date instance
|
||||||
|
* timeago.format('2016-09-10', 'fr'); // formated date string
|
||||||
|
* timeago.format(1473473400269); // timestamp with ms
|
||||||
|
**/
|
||||||
|
this.format = function(date, locale) {
|
||||||
|
return formatDiff(diffSec(date, nowDate), locale, defaultLocale);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* render: render the DOM real-time.
|
||||||
|
* - nodes: which nodes will be rendered.
|
||||||
|
* - locale: the locale name used to format date.
|
||||||
|
*
|
||||||
|
* How to use it?
|
||||||
|
* var timeago = new require('timeago.js')();
|
||||||
|
* // 1. javascript selector
|
||||||
|
* timeago.render(document.querySelectorAll('.need_to_be_rendered'));
|
||||||
|
* // 2. use jQuery selector
|
||||||
|
* timeago.render($('.need_to_be_rendered'), 'pl');
|
||||||
|
*
|
||||||
|
* Notice: please be sure the dom has attribute `datetime`.
|
||||||
|
**/
|
||||||
|
this.render = function(nodes, locale) {
|
||||||
|
if (nodes.length === undefined) nodes = [nodes];
|
||||||
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
doRender(nodes[i], getDateAttr(nodes[i]), locale, ++ cnt); // render item
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* cancel: cancel all the timers which are doing real-time render.
|
||||||
|
*
|
||||||
|
* How to use it?
|
||||||
|
* var timeago = new require('timeago.js')();
|
||||||
|
* timeago.render(document.querySelectorAll('.need_to_be_rendered'));
|
||||||
|
* timeago.cancel(); // will stop all the timer, stop render in real time.
|
||||||
|
**/
|
||||||
|
this.cancel = function() {
|
||||||
|
for (var key in timers) {
|
||||||
|
clearTimeout(timers[key]);
|
||||||
|
}
|
||||||
|
timers = {};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* setLocale: set the default locale name.
|
||||||
|
*
|
||||||
|
* How to use it?
|
||||||
|
* var timeago = require('timeago.js');
|
||||||
|
* timeago = new timeago();
|
||||||
|
* timeago.setLocale('fr');
|
||||||
|
**/
|
||||||
|
this.setLocale = function(locale) {
|
||||||
|
defaultLocale = locale;
|
||||||
|
};
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* timeago: the function to get `timeago` instance.
|
||||||
|
* - nowDate: the relative date, default is new Date().
|
||||||
|
* - defaultLocale: the default locale, default is en. if your set it, then the `locale` parameter of format is not needed of you.
|
||||||
|
*
|
||||||
|
* How to use it?
|
||||||
|
* var timeagoLib = require('timeago.js');
|
||||||
|
* var timeago = timeagoLib(); // all use default.
|
||||||
|
* var timeago = timeagoLib('2016-09-10'); // the relative date is 2016-09-10, so the 2016-09-11 will be 1 day ago.
|
||||||
|
* var timeago = timeagoLib(null, 'zh_CN'); // set default locale is `zh_CN`.
|
||||||
|
* var timeago = timeagoLib('2016-09-10', 'zh_CN'); // the relative date is 2016-09-10, and locale is zh_CN, so the 2016-09-11 will be 1天前.
|
||||||
|
**/
|
||||||
|
function timeagoFactory(nowDate, defaultLocale) {
|
||||||
|
return new Timeago(nowDate, defaultLocale);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* register: register a new language locale
|
||||||
|
* - locale: locale name, e.g. en / zh_CN, notice the standard.
|
||||||
|
* - localeFunc: the locale process function
|
||||||
|
*
|
||||||
|
* How to use it?
|
||||||
|
* var timeagoLib = require('timeago.js');
|
||||||
|
*
|
||||||
|
* timeagoLib.register('the locale name', the_locale_func);
|
||||||
|
* // or
|
||||||
|
* timeagoLib.register('pl', require('timeago.js/locales/pl'));
|
||||||
|
**/
|
||||||
|
timeagoFactory.register = function(locale, localeFunc) {
|
||||||
|
locales[locale] = localeFunc;
|
||||||
|
};
|
||||||
|
|
||||||
|
return timeagoFactory;
|
||||||
|
});
|
|
@ -218,7 +218,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (environment.deployed_at && environment.deployed_at_formatted) {
|
if (environment.deployed_at && environment.deployed_at_formatted) {
|
||||||
environment.deployed_at = $.timeago(environment.deployed_at) + '.';
|
environment.deployed_at = gl.utils.getTimeago(environment.deployed_at) + '.';
|
||||||
} else {
|
} else {
|
||||||
$('.js-environment-timeago', $template).remove();
|
$('.js-environment-timeago', $template).remove();
|
||||||
environment.name += '.';
|
environment.name += '.';
|
||||||
|
|
|
@ -162,7 +162,7 @@
|
||||||
if (data.milestone != null) {
|
if (data.milestone != null) {
|
||||||
data.milestone.namespace = _this.currentProject.namespace;
|
data.milestone.namespace = _this.currentProject.namespace;
|
||||||
data.milestone.path = _this.currentProject.path;
|
data.milestone.path = _this.currentProject.path;
|
||||||
data.milestone.remaining = $.timefor(data.milestone.due_date);
|
data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
|
||||||
$value.html(milestoneLinkTemplate(data.milestone));
|
$value.html(milestoneLinkTemplate(data.milestone));
|
||||||
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
|
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -151,7 +151,6 @@ module ApplicationHelper
|
||||||
# time - Time object
|
# time - Time object
|
||||||
# placement - Tooltip placement String (default: "top")
|
# placement - Tooltip placement String (default: "top")
|
||||||
# html_class - Custom class for `time` element (default: "time_ago")
|
# html_class - Custom class for `time` element (default: "time_ago")
|
||||||
# skip_js - When true, exclude the `script` tag (default: false)
|
|
||||||
#
|
#
|
||||||
# By default also includes a `script` element with Javascript necessary to
|
# By default also includes a `script` element with Javascript necessary to
|
||||||
# initialize the `timeago` jQuery extension. If this method is called many
|
# initialize the `timeago` jQuery extension. If this method is called many
|
||||||
|
@ -163,22 +162,19 @@ module ApplicationHelper
|
||||||
# `html_class` argument is provided.
|
# `html_class` argument is provided.
|
||||||
#
|
#
|
||||||
# Returns an HTML-safe String
|
# Returns an HTML-safe String
|
||||||
def time_ago_with_tooltip(time, placement: 'top', html_class: '', skip_js: false, short_format: false)
|
def time_ago_with_tooltip(time, placement: 'top', html_class: '', short_format: false)
|
||||||
css_classes = short_format ? 'js-short-timeago' : 'js-timeago'
|
css_classes = short_format ? 'js-short-timeago' : 'js-timeago'
|
||||||
css_classes << " #{html_class}" unless html_class.blank?
|
css_classes << " #{html_class}" unless html_class.blank?
|
||||||
css_classes << ' js-timeago-pending' unless skip_js
|
|
||||||
|
|
||||||
element = content_tag :time, time.to_s,
|
element = content_tag :time, time.to_s,
|
||||||
class: css_classes,
|
class: css_classes,
|
||||||
datetime: time.to_time.getutc.iso8601,
|
|
||||||
title: time.to_time.in_time_zone.to_s(:medium),
|
title: time.to_time.in_time_zone.to_s(:medium),
|
||||||
data: { toggle: 'tooltip', placement: placement, container: 'body' }
|
datetime: time.to_time.getutc.iso8601,
|
||||||
|
data: {
|
||||||
unless skip_js
|
toggle: 'tooltip',
|
||||||
element << javascript_tag(
|
placement: placement,
|
||||||
"$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()"
|
container: 'body'
|
||||||
)
|
}
|
||||||
end
|
|
||||||
|
|
||||||
element
|
element
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- if event.visible_to_user?(current_user)
|
- if event.visible_to_user?(current_user)
|
||||||
.event-item{ class: event_row_class(event) }
|
.event-item{ class: event_row_class(event) }
|
||||||
.event-item-timestamp
|
.event-item-timestamp
|
||||||
#{time_ago_with_tooltip(event.created_at, skip_js: true)}
|
#{time_ago_with_tooltip(event.created_at)}
|
||||||
|
|
||||||
= cache [event, current_application_settings, "v2.2"] do
|
= cache [event, current_application_settings, "v2.2"] do
|
||||||
= author_avatar(event, size: 40)
|
= author_avatar(event, size: 40)
|
||||||
|
|
|
@ -8,5 +8,5 @@
|
||||||
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
|
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
|
||||||
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
|
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
|
||||||
·
|
·
|
||||||
#{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by
|
#{time_ago_with_tooltip(commit.committed_date)} by
|
||||||
= commit_author_link(commit, avatar: true, size: 24)
|
= commit_author_link(commit, avatar: true, size: 24)
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
.light
|
.light
|
||||||
= commit_author_link(commit, avatar: false)
|
= commit_author_link(commit, avatar: false)
|
||||||
authored
|
authored
|
||||||
#{time_ago_with_tooltip(commit.committed_date, skip_js: true)}
|
#{time_ago_with_tooltip(commit.committed_date)}
|
||||||
%td.line-numbers
|
%td.line-numbers
|
||||||
- line_count = blame_group[:lines].count
|
- line_count = blame_group[:lines].count
|
||||||
- (current_line...(current_line + line_count)).each do |i|
|
- (current_line...(current_line + line_count)).each do |i|
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
- if pipeline.finished_at
|
- if pipeline.finished_at
|
||||||
%p.finished-at
|
%p.finished-at
|
||||||
= icon("calendar")
|
= icon("calendar")
|
||||||
#{time_ago_with_tooltip(pipeline.finished_at, short_format: false, skip_js: true)}
|
#{time_ago_with_tooltip(pipeline.finished_at, short_format: false)}
|
||||||
|
|
||||||
%td.pipeline-actions.hidden-xs
|
%td.pipeline-actions.hidden-xs
|
||||||
.controls.pull-right
|
.controls.pull-right
|
||||||
|
|
|
@ -16,3 +16,6 @@
|
||||||
var url = "#{escape_javascript(@more_log_url)}";
|
var url = "#{escape_javascript(@more_log_url)}";
|
||||||
ajaxGet(url);
|
ajaxGet(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:plain
|
||||||
|
gl.utils.localTimeAgo($('.js-timeago', 'table.table_#{@hex_path} tbody'));
|
4
changelogs/unreleased/upgrade-timeago.yml
Normal file
4
changelogs/unreleased/upgrade-timeago.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: Replace jQuery.timeago with timeago.js
|
||||||
|
merge_request: 6274
|
||||||
|
author: ClemMakesApps
|
|
@ -218,42 +218,24 @@ describe ApplicationHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes a default js-timeago class' do
|
it 'includes a default js-timeago class' do
|
||||||
expect(element.attr('class')).to eq 'js-timeago js-timeago-pending'
|
expect(element.attr('class')).to eq 'js-timeago'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'accepts a custom html_class' do
|
it 'accepts a custom html_class' do
|
||||||
expect(element(html_class: 'custom_class').attr('class')).
|
expect(element(html_class: 'custom_class').attr('class')).
|
||||||
to eq 'js-timeago custom_class js-timeago-pending'
|
to eq 'js-timeago custom_class'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'accepts a custom tooltip placement' do
|
it 'accepts a custom tooltip placement' do
|
||||||
expect(element(placement: 'bottom').attr('data-placement')).to eq 'bottom'
|
expect(element(placement: 'bottom').attr('data-placement')).to eq 'bottom'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 're-initializes timeago Javascript' do
|
|
||||||
el = element.next_element
|
|
||||||
|
|
||||||
expect(el.name).to eq 'script'
|
|
||||||
expect(el.text).to include "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()"
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'allows the script tag to be excluded' do
|
|
||||||
expect(element(skip_js: true)).not_to include 'script'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'converts to Time' do
|
it 'converts to Time' do
|
||||||
expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error
|
expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'add class for the short format and includes inline script' do
|
it 'add class for the short format' do
|
||||||
timeago_element = element(short_format: 'short')
|
timeago_element = element(short_format: 'short')
|
||||||
expect(timeago_element.attr('class')).to eq 'js-short-timeago js-timeago-pending'
|
|
||||||
script_element = timeago_element.next_element
|
|
||||||
expect(script_element.name).to eq 'script'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'add class for the short format and does not include inline script' do
|
|
||||||
timeago_element = element(short_format: 'short', skip_js: true)
|
|
||||||
expect(timeago_element.attr('class')).to eq 'js-short-timeago'
|
expect(timeago_element.attr('class')).to eq 'js-short-timeago'
|
||||||
expect(timeago_element.next_element).to eq nil
|
expect(timeago_element.next_element).to eq nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/*= require merge_request_widget */
|
/*= require merge_request_widget */
|
||||||
/*= require jquery.timeago.js */
|
/*= require lib/utils/timeago.js */
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
describe('MergeRequestWidget', function() {
|
describe('MergeRequestWidget', function() {
|
||||||
|
|
182
vendor/assets/javascripts/jquery.timeago.js
vendored
182
vendor/assets/javascripts/jquery.timeago.js
vendored
|
@ -1,182 +0,0 @@
|
||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* Timeago is a jQuery plugin that makes it easy to support automatically
|
|
||||||
* updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
|
|
||||||
*
|
|
||||||
* @name timeago
|
|
||||||
* @version 1.1.0
|
|
||||||
* @requires jQuery v1.2.3+
|
|
||||||
* @author Ryan McGeary
|
|
||||||
* @license MIT License - http://www.opensource.org/licenses/mit-license.php
|
|
||||||
*
|
|
||||||
* For usage and examples, visit:
|
|
||||||
* http://timeago.yarp.com/
|
|
||||||
*
|
|
||||||
* Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function (factory) {
|
|
||||||
if (typeof define === 'function' && define.amd) {
|
|
||||||
// AMD. Register as an anonymous module.
|
|
||||||
define(['jquery'], factory);
|
|
||||||
} else {
|
|
||||||
// Browser globals
|
|
||||||
factory(jQuery);
|
|
||||||
}
|
|
||||||
}(function ($) {
|
|
||||||
$.timeago = function(timestamp) {
|
|
||||||
if (timestamp instanceof Date) {
|
|
||||||
return inWords(timestamp);
|
|
||||||
} else if (typeof timestamp === "string") {
|
|
||||||
return inWords($.timeago.parse(timestamp));
|
|
||||||
} else if (typeof timestamp === "number") {
|
|
||||||
return inWords(new Date(timestamp));
|
|
||||||
} else {
|
|
||||||
return inWords($.timeago.datetime(timestamp));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var $t = $.timeago;
|
|
||||||
|
|
||||||
$.extend($.timeago, {
|
|
||||||
settings: {
|
|
||||||
refreshMillis: 60000,
|
|
||||||
allowFuture: false,
|
|
||||||
strings: {
|
|
||||||
prefixAgo: null,
|
|
||||||
prefixFromNow: null,
|
|
||||||
suffixAgo: "ago",
|
|
||||||
suffixFromNow: "from now",
|
|
||||||
seconds: "less than a minute",
|
|
||||||
minute: "about a minute",
|
|
||||||
minutes: "%d minutes",
|
|
||||||
hour: "about an hour",
|
|
||||||
hours: "about %d hours",
|
|
||||||
day: "a day",
|
|
||||||
days: "%d days",
|
|
||||||
month: "about a month",
|
|
||||||
months: "%d months",
|
|
||||||
year: "about a year",
|
|
||||||
years: "%d years",
|
|
||||||
wordSeparator: " ",
|
|
||||||
numbers: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
inWords: function(distanceMillis) {
|
|
||||||
var $l = this.settings.strings;
|
|
||||||
var prefix = $l.prefixAgo;
|
|
||||||
var suffix = $l.suffixAgo;
|
|
||||||
if (this.settings.allowFuture) {
|
|
||||||
if (distanceMillis < 0) {
|
|
||||||
prefix = $l.prefixFromNow;
|
|
||||||
suffix = $l.suffixFromNow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var seconds = Math.abs(distanceMillis) / 1000;
|
|
||||||
var minutes = seconds / 60;
|
|
||||||
var hours = minutes / 60;
|
|
||||||
var days = hours / 24;
|
|
||||||
var years = days / 365;
|
|
||||||
|
|
||||||
function substitute(stringOrFunction, number) {
|
|
||||||
var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
|
|
||||||
var value = ($l.numbers && $l.numbers[number]) || number;
|
|
||||||
return string.replace(/%d/i, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
|
|
||||||
seconds < 90 && substitute($l.minute, 1) ||
|
|
||||||
minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
|
|
||||||
minutes < 90 && substitute($l.hour, 1) ||
|
|
||||||
hours < 24 && substitute($l.hours, Math.round(hours)) ||
|
|
||||||
hours < 42 && substitute($l.day, 1) ||
|
|
||||||
days < 30 && substitute($l.days, Math.round(days)) ||
|
|
||||||
days < 45 && substitute($l.month, 1) ||
|
|
||||||
days < 365 && substitute($l.months, Math.round(days / 30)) ||
|
|
||||||
years < 1.5 && substitute($l.year, 1) ||
|
|
||||||
substitute($l.years, Math.round(years));
|
|
||||||
|
|
||||||
var separator = $l.wordSeparator || "";
|
|
||||||
if ($l.wordSeparator === undefined) { separator = " "; }
|
|
||||||
return $.trim([prefix, words, suffix].join(separator));
|
|
||||||
},
|
|
||||||
parse: function(iso8601) {
|
|
||||||
var s = $.trim(iso8601);
|
|
||||||
s = s.replace(/\.\d+/,""); // remove milliseconds
|
|
||||||
s = s.replace(/-/,"/").replace(/-/,"/");
|
|
||||||
s = s.replace(/T/," ").replace(/Z/," UTC");
|
|
||||||
s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
|
|
||||||
return new Date(s);
|
|
||||||
},
|
|
||||||
datetime: function(elem) {
|
|
||||||
var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
|
|
||||||
return $t.parse(iso8601);
|
|
||||||
},
|
|
||||||
isTime: function(elem) {
|
|
||||||
// jQuery's `is()` doesn't play well with HTML5 in IE
|
|
||||||
return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// functions that can be called via $(el).timeago('action')
|
|
||||||
// init is default when no action is given
|
|
||||||
// functions are called with context of a single element
|
|
||||||
var functions = {
|
|
||||||
init: function(){
|
|
||||||
var refresh_el = $.proxy(refresh, this);
|
|
||||||
refresh_el();
|
|
||||||
var $s = $t.settings;
|
|
||||||
if ($s.refreshMillis > 0) {
|
|
||||||
setInterval(refresh_el, $s.refreshMillis);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update: function(time){
|
|
||||||
$(this).data('timeago', { datetime: $t.parse(time) });
|
|
||||||
refresh.apply(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.timeago = function(action, options) {
|
|
||||||
var fn = action ? functions[action] : functions.init;
|
|
||||||
if(!fn){
|
|
||||||
throw new Error("Unknown function name '"+ action +"' for timeago");
|
|
||||||
}
|
|
||||||
// each over objects here and call the requested function
|
|
||||||
this.each(function(){
|
|
||||||
fn.call(this, options);
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
function refresh() {
|
|
||||||
var data = prepareData(this);
|
|
||||||
if (!isNaN(data.datetime)) {
|
|
||||||
$(this).text(inWords(data.datetime));
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareData(element) {
|
|
||||||
element = $(element);
|
|
||||||
if (!element.data("timeago")) {
|
|
||||||
element.data("timeago", { datetime: $t.datetime(element) });
|
|
||||||
var text = $.trim(element.text());
|
|
||||||
if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
|
|
||||||
element.attr("title", text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return element.data("timeago");
|
|
||||||
}
|
|
||||||
|
|
||||||
function inWords(date) {
|
|
||||||
return $t.inWords(distance(date));
|
|
||||||
}
|
|
||||||
|
|
||||||
function distance(date) {
|
|
||||||
return (new Date().getTime() - date.getTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix for IE6 suckage
|
|
||||||
document.createElement("abbr");
|
|
||||||
document.createElement("time");
|
|
||||||
}));
|
|
Loading…
Reference in a new issue