diff --git a/app/assets/javascripts/lib/utils/datetime/timeago_utility.js b/app/assets/javascripts/lib/utils/datetime/timeago_utility.js index f8ef0b20cada65700ea1d11373bdae2c2cecfe62..9bdc78cf2156de60a343fedf071185c81c932fb7 100644 --- a/app/assets/javascripts/lib/utils/datetime/timeago_utility.js +++ b/app/assets/javascripts/lib/utils/datetime/timeago_utility.js @@ -1,6 +1,7 @@ import * as timeago from 'timeago.js'; import { diffSec } from 'timeago.js/lib/utils/date'; import { newDate } from '~/lib/utils/datetime/date_calculation_utility'; +import { formatDate } from '~/lib/utils/datetime/date_format_utility'; import { DEFAULT_DATE_TIME_FORMAT, DATE_ONLY_FORMAT, @@ -202,8 +203,19 @@ export const localTimeAgo = (elements, updateTooltip = true) => { function addTimeAgoTooltip() { elements.forEach((el) => { - // Recreate with custom template - el.setAttribute('title', localeDateFormat.asDateTimeFull.format(newDate(el.dateTime))); + const customFormat = el.dataset.tooltipFormat; + const dateTime = newDate(el.dateTime); + let title = null; + + if (customFormat && localeDateFormat[customFormat]) { + title = localeDateFormat[customFormat].format(dateTime); + } else if (customFormat) { + title = formatDate(dateTime, customFormat); + } else { + title = localeDateFormat.asDateTimeFull.format(dateTime); + } + + el.setAttribute('title', title); }); } diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fca4359ec04f3d2fc02f87846fd5119a5ae19ec7..fa793e9722a6ea4deb5182b452a3cbaa655d3d03 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -198,22 +198,32 @@ def registry_config # `html_class` argument is provided. # # Returns an HTML-safe String - def time_ago_with_tooltip(time, placement: 'top', html_class: '', short_format: false) + def time_ago_with_tooltip(time, placement: 'top', html_class: '', short_format: false, tooltip_format: :timeago_tooltip) return "" if time.nil? css_classes = [short_format ? 'js-short-timeago' : 'js-timeago'] css_classes << html_class unless html_class.blank? + formatted_time = case tooltip_format + when :timeago_tooltip + l(time.to_time.in_time_zone, format: tooltip_format) + when Symbol + time.to_time.getutc.iso8601 + else + time.to_time.in_time_zone.strftime(tooltip_format) + end + content_tag :time, l(time, format: "%b %d, %Y"), class: css_classes.join(' '), - title: l(time.to_time.in_time_zone, format: :timeago_tooltip), + title: formatted_time, datetime: time.to_time.getutc.iso8601, tabindex: '0', - aria: { label: l(time.to_time.in_time_zone, format: :timeago_tooltip) }, + aria: { label: formatted_time }, data: { toggle: 'tooltip', placement: placement, - container: 'body' + container: 'body', + tooltip_format: (tooltip_format != :timeago_tooltip ? tooltip_format.to_s : nil) } end diff --git a/app/views/projects/commits/_committer.html.haml b/app/views/projects/commits/_committer.html.haml index 86574a18554874e3b8b6da52e57f616644f53744..a900a99c6b506dd5f46d80e573e2e844ae707020 100644 --- a/app/views/projects/commits/_committer.html.haml +++ b/app/views/projects/commits/_committer.html.haml @@ -1,9 +1,9 @@ .committer.gl-text-sm - commit_author_link = commit_author_link(commit, avatar: false, size: 24) - - commit_authored_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom') + - commit_authored_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom', tooltip_format: 'mmm d, yyyy h:MMtt Z') - if commit.different_committer? && commit.committer - commit_committer_link = commit_committer_link(commit) - - commit_committer_timeago = time_ago_with_tooltip(commit.committed_date, placement: 'bottom') + - commit_committer_timeago = time_ago_with_tooltip(commit.committed_date, placement: 'bottom', tooltip_format: 'mmm d, yyyy h:MMtt Z') - commit_committer_avatar = commit_committer_avatar(commit.committer, size: 16, has_tooltip: false) - commit_text = _('%{commit_author_link} authored %{commit_authored_timeago} and %{commit_committer_avatar} %{commit_committer_link} committed %{commit_committer_timeago}') % { commit_author_link: commit_author_link, commit_authored_timeago: commit_authored_timeago, commit_committer_avatar: commit_committer_avatar, commit_committer_link: commit_committer_link, commit_committer_timeago: commit_committer_timeago } - else diff --git a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js index b3b088fce13e36e407d51607466e6bba0e46df49..33159a7570a52500d8e7d7d44e1eb8eb1aba4108 100644 --- a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js +++ b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js @@ -229,5 +229,40 @@ describe('TimeAgo utils', () => { expect(element.innerText).toBe(text); }); }); + + describe('with custom tooltip format', () => { + beforeEach(() => { + window.gon = { time_display_relative: false }; + }); + + it('uses formatDate when data-tooltip-format is a custom string', () => { + const element = document.querySelector('time'); + element.dataset.tooltipFormat = 'mmm d, yyyy h:MMtt Z'; + + localTimeAgo([element]); + jest.runAllTimers(); + + expect(element.getAttribute('title')).toBe('Feb 18, 2020 10:22pm UTC'); + }); + + it('uses localeDateFormat when data-tooltip-format matches a known format key', () => { + const element = document.querySelector('time'); + element.dataset.tooltipFormat = 'asDateTimeFull'; + + localTimeAgo([element]); + jest.runAllTimers(); + + expect(element.getAttribute('title')).toBe('February 18, 2020 at 22:22:32 GMT'); + }); + + it('uses default asDateTimeFull when no data-tooltip-format is set', () => { + const element = document.querySelector('time'); + + localTimeAgo([element]); + jest.runAllTimers(); + + expect(element.getAttribute('title')).toBe('February 18, 2020 at 22:22:32 GMT'); + }); + }); }); }); diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index f4f95f5bfc461b2191612f9fe5b84b5a66de04c9..735fd993ecff16a61e90c4beeed40b57b958ff16 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -180,6 +180,29 @@ def element(**arguments) expect(el).to eq('') expect(el.html_safe).to eq('') end + + context 'with custom tooltip_format' do + it 'uses strftime format when tooltip_format is a string' do + timeago_element = element(tooltip_format: '%B %d, %Y at %I:%M%p') + + expect(timeago_element.attr('title')).to eq('July 02, 2015 at 08:23AM') + expect(timeago_element.attr('data-tooltip-format')).to eq('%B %d, %Y at %I:%M%p') + end + + it 'uses ISO8601 format when tooltip_format is a non-default symbol' do + timeago_element = element(tooltip_format: :custom_format) + + expect(timeago_element.attr('title')).to eq('2015-07-02T08:23:00Z') + expect(timeago_element.attr('data-tooltip-format')).to eq('custom_format') + end + + it 'does not set data-tooltip-format when using default :timeago_tooltip' do + timeago_element = element(tooltip_format: :timeago_tooltip) + + expect(timeago_element.attr('title')).to eq('Jul 2, 2015 8:23am') + expect(timeago_element.attr('data-tooltip-format')).to be_nil + end + end end describe 'edited_time_ago_with_tooltip' do