From fa1ac47ef167e55d79a69d69596012fe5ac99d20 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 8 Sep 2016 13:57:24 -0500 Subject: Replace jQuery.timeago with timeago.js --- app/assets/javascripts/activities.js | 4 +- app/assets/javascripts/application.js | 4 - app/assets/javascripts/build.js | 2 +- app/assets/javascripts/compare.js | 3 +- app/assets/javascripts/lib/utils/common_utils.js | 19 -- .../javascripts/lib/utils/datetime_utility.js | 95 +++++---- app/assets/javascripts/lib/utils/timeago.js | 237 +++++++++++++++++++++ app/assets/javascripts/merge_request_widget.js.es6 | 2 +- app/assets/javascripts/milestone_select.js | 2 +- app/helpers/application_helper.rb | 18 +- app/views/events/_event.html.haml | 2 +- app/views/projects/_last_commit.html.haml | 2 +- app/views/projects/blame/show.html.haml | 2 +- .../projects/ci/pipelines/_pipeline.html.haml | 2 +- app/views/projects/refs/logs_tree.js.haml | 3 + changelogs/unreleased/upgrade-timeago.yml | 4 + spec/helpers/application_helper_spec.rb | 24 +-- spec/javascripts/merge_request_widget_spec.js | 2 +- vendor/assets/javascripts/jquery.timeago.js | 182 ---------------- 19 files changed, 320 insertions(+), 289 deletions(-) create mode 100644 app/assets/javascripts/lib/utils/timeago.js create mode 100644 changelogs/unreleased/upgrade-timeago.yml delete mode 100644 vendor/assets/javascripts/jquery.timeago.js diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js index 59ac9b9cef5..919107b8cb9 100644 --- a/app/assets/javascripts/activities.js +++ b/app/assets/javascripts/activities.js @@ -13,12 +13,12 @@ } Activities.prototype.updateTooltips = function() { - return gl.utils.localTimeAgo($('.js-timeago', '.content_list')); + gl.utils.localTimeAgo($('.js-timeago', '.content_list')); }; Activities.prototype.reloadActivities = function() { $(".content_list").html(''); - return Pager.init(20, true); + Pager.init(20, true, false, this.updateTooltips); }; Activities.prototype.toggleFilter = function(sender) { diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 7d942de0184..e6b55c9b6ae 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,7 +13,6 @@ /*= require jquery-ui/sortable */ /*= require jquery_ujs */ /*= require jquery.endless-scroll */ -/*= require jquery.timeago */ /*= require jquery.highlight */ /*= require jquery.waitforimages */ /*= require jquery.atwho */ @@ -238,8 +237,5 @@ // bind sidebar events new gl.Sidebar(); - - // Custom time ago - gl.utils.shortTimeAgo($('.js-short-timeago')); }); }).call(this); diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 12e653f4122..d63125ce44b 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -169,7 +169,7 @@ $date = $('.js-artifacts-remove'); if ($date.length) { 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')), ' ')); } }; diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js index b3f769d4129..61cc91c524b 100644 --- a/app/assets/javascripts/compare.js +++ b/app/assets/javascripts/compare.js @@ -80,7 +80,8 @@ success: function(html) { loading.hide(); $target.html(html); - return $('.js-timeago', $target).timeago(); + var className = '.' + $target[0].className.replace(' ', '.'); + gl.utils.localTimeAgo($('.js-timeago', className)); } }); }; diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 8447421195d..6cb3d95f984 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -119,31 +119,12 @@ parser.href = url; return parser; }; - gl.utils.cleanupBeforeFetch = function() { // Unbind scroll events $(document).off('scroll'); // Close any open tooltips $('.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); }).call(this); diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 59e526ed623..3965109dd65 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -22,51 +22,64 @@ if (setTimeago == null) { setTimeago = true; } + $timeagoEls.each(function() { - var $el; - $el = $(this); - return $el.attr('title', gl.utils.formatDate($el.attr('datetime'))); + var $el = $(this); + $el.attr('title', gl.utils.formatDate($el.attr('datetime'))); + + if (setTimeago) { + // Recreate with custom template + $el.tooltip({ + template: '' + }); + } + gl.utils.renderTimeago($el); }); - if (setTimeago) { - $timeagoEls.timeago(); - $timeagoEls.tooltip('destroy'); - // Recreate with custom template - return $timeagoEls.tooltip({ - template: '' - }); - } }; - w.gl.utils.shortTimeAgo = function($el) { - var shortLocale, tmpLocale; - shortLocale = { - prefixAgo: null, - prefixFromNow: null, - suffixAgo: 'ago', - suffixFromNow: 'from now', - seconds: '1 min', - minute: '1 min', - minutes: '%d mins', - hour: '1 hr', - hours: '%d hrs', - day: '1 day', - days: '%d days', - month: '1 month', - months: '%d months', - year: '1 year', - years: '%d years', - wordSeparator: ' ', - numbers: [] + w.gl.utils.getTimeago = function() { + var locale = function(number, index) { + return [ + ['less than a minute ago', 'a while'], + ['less than a minute ago', 'in %s seconds'], + ['about a minute ago', 'in 1 minute'], + ['%s minutes ago', 'in %s minutes'], + ['about an hour ago', 'in 1 hour'], + ['about %s hours ago', 'in %s hours'], + ['a day ago', 'in 1 day'], + ['%s days ago', 'in %s days'], + ['a week ago', 'in 1 week'], + ['%s weeks ago', 'in %s weeks'], + ['a month ago', 'in 1 month'], + ['%s months ago', 'in %s months'], + ['a year ago', 'in 1 year'], + ['%s years ago', 'in %s years'] + ][index]; }; - tmpLocale = $.timeago.settings.strings; - $el.each(function(el) { - var $el1; - $el1 = $(this); - return $el1.attr('title', gl.utils.formatDate($el.attr('datetime'))); - }); - $.timeago.settings.strings = shortLocale; - $el.timeago(); - $.timeago.settings.strings = tmpLocale; + + timeago.register('gl_en', locale); + return timeago(); + }; + + w.gl.utils.timeFor = function(time, suffix, expiredLabel) { + var timefor; + if (!time) { + 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) { @@ -75,7 +88,7 @@ var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()); return Math.floor((date2 - date1) / millisecondsPerDay); - } + }; })(window); diff --git a/app/assets/javascripts/lib/utils/timeago.js b/app/assets/javascripts/lib/utils/timeago.js new file mode 100644 index 00000000000..42606dd2d46 --- /dev/null +++ b/app/assets/javascripts/lib/utils/timeago.js @@ -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; +}); \ No newline at end of file diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 3a2fe454b68..56c87af3226 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -218,7 +218,7 @@ } 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 { $('.js-environment-timeago', $template).remove(); environment.name += '.'; diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index c909b53dc21..d1cd38ad110 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -162,7 +162,7 @@ if (data.milestone != null) { data.milestone.namespace = _this.currentProject.namespace; 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)); return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone)); } else { diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ebd78bf9888..c816b616631 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -151,7 +151,6 @@ module ApplicationHelper # time - Time object # placement - Tooltip placement String (default: "top") # 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 # initialize the `timeago` jQuery extension. If this method is called many @@ -163,22 +162,19 @@ module ApplicationHelper # `html_class` argument is provided. # # 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 << " #{html_class}" unless html_class.blank? - css_classes << ' js-timeago-pending' unless skip_js element = content_tag :time, time.to_s, class: css_classes, - datetime: time.to_time.getutc.iso8601, title: time.to_time.in_time_zone.to_s(:medium), - data: { toggle: 'tooltip', placement: placement, container: 'body' } - - unless skip_js - element << javascript_tag( - "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()" - ) - end + datetime: time.to_time.getutc.iso8601, + data: { + toggle: 'tooltip', + placement: placement, + container: 'body' + } element end diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 31fdcc5e21b..5c318cd3b8b 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -1,7 +1,7 @@ - if event.visible_to_user?(current_user) .event-item{ class: event_row_class(event) } .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 = author_avatar(event, size: 40) diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index 8e23d51b224..7f530708947 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -8,5 +8,5 @@ = 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" · -#{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) diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index dfb96305f48..cadfe5a3e30 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -32,7 +32,7 @@ .light = commit_author_link(commit, avatar: false) authored - #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} + #{time_ago_with_tooltip(commit.committed_date)} %td.line-numbers - line_count = blame_group[:lines].count - (current_line...(current_line + line_count)).each do |i| diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 1f748d73d06..2a2d24be736 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -59,7 +59,7 @@ - if pipeline.finished_at %p.finished-at = 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 .controls.pull-right diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml index 1141168f037..44fa4b60343 100644 --- a/app/views/projects/refs/logs_tree.js.haml +++ b/app/views/projects/refs/logs_tree.js.haml @@ -16,3 +16,6 @@ var url = "#{escape_javascript(@more_log_url)}"; ajaxGet(url); } + +:plain + gl.utils.localTimeAgo($('.js-timeago', 'table.table_#{@hex_path} tbody')); \ No newline at end of file diff --git a/changelogs/unreleased/upgrade-timeago.yml b/changelogs/unreleased/upgrade-timeago.yml new file mode 100644 index 00000000000..ddb266ba558 --- /dev/null +++ b/changelogs/unreleased/upgrade-timeago.yml @@ -0,0 +1,4 @@ +--- +title: Replace jQuery.timeago with timeago.js +merge_request: 6274 +author: ClemMakesApps diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 73f5470cf35..c706e418d26 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -218,42 +218,24 @@ describe ApplicationHelper do end 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 it 'accepts a custom html_class' do expect(element(html_class: 'custom_class').attr('class')). - to eq 'js-timeago custom_class js-timeago-pending' + to eq 'js-timeago custom_class' end it 'accepts a custom tooltip placement' do expect(element(placement: 'bottom').attr('data-placement')).to eq 'bottom' 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 expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error 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') - 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.next_element).to eq nil end diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index 49dfeab61d8..91f19aca719 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -1,6 +1,6 @@ /* eslint-disable */ /*= require merge_request_widget */ -/*= require jquery.timeago.js */ +/*= require lib/utils/timeago.js */ (function() { describe('MergeRequestWidget', function() { diff --git a/vendor/assets/javascripts/jquery.timeago.js b/vendor/assets/javascripts/jquery.timeago.js deleted file mode 100644 index de76cdd2ea7..00000000000 --- a/vendor/assets/javascripts/jquery.timeago.js +++ /dev/null @@ -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"); -})); -- cgit v1.2.1