diff --git a/app/javascript/retrospring/controllers/timestamp_controller.ts b/app/javascript/retrospring/controllers/timestamp_controller.ts new file mode 100644 index 00000000..ec4f40ff --- /dev/null +++ b/app/javascript/retrospring/controllers/timestamp_controller.ts @@ -0,0 +1,43 @@ +import {Controller} from "@hotwired/stimulus"; + +const INTERVALS = { + "year": 86_400_000 * 365, + "month": 86_400_000 * 30, + "week": 86_400_000 * 7, + "day": 86_400_000, + "hour": 3_600_000, + "minute": 60_000, + "second": 1_000, +}; + +export default class extends Controller { + private formatter: Intl.RelativeTimeFormat; + + initialize(): void { + const locale = Intl.RelativeTimeFormat.supportedLocalesOf([document.documentElement.lang, 'en'], { localeMatcher: "lookup" }); + this.formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto', style: 'long'}); + } + + connect(): void { + const date = new Date(this.element.dateTime); + + // convert date to timestamp by casting to number + const now = +new Date(); + const then = +date; + const delta = then - now; + + this.element.innerText = this.formatTime(delta); + } + + formatTime(delta: number): string { + for (const unit in INTERVALS) { + const duration = INTERVALS[unit]; + const count = Math.floor(delta / duration); + if (count < -1) { + return this.formatter.format(count, unit as Intl.RelativeTimeFormatUnitSingular); + } + } + + return this.formatter.format(0, 'second'); + } +} diff --git a/app/javascript/retrospring/initializers/stimulus.ts b/app/javascript/retrospring/initializers/stimulus.ts index 6239a1f9..e0573980 100644 --- a/app/javascript/retrospring/initializers/stimulus.ts +++ b/app/javascript/retrospring/initializers/stimulus.ts @@ -9,6 +9,7 @@ import ThemeController from "retrospring/controllers/theme_controller"; import CapabilitiesController from "retrospring/controllers/capabilities_controller"; import CropperController from "retrospring/controllers/cropper_controller"; import InboxSharingController from "retrospring/controllers/inbox_sharing_controller"; +import TimestampController from "retrospring/controllers/timestamp_controller"; import ToastController from "retrospring/controllers/toast_controller"; /** @@ -30,5 +31,6 @@ export default function (): void { window['Stimulus'].register('format-popup', FormatPopupController); window['Stimulus'].register('inbox-sharing', InboxSharingController); window['Stimulus'].register('theme', ThemeController); + window['Stimulus'].register('timestamp', TimestampController); window['Stimulus'].register('toast', ToastController); } diff --git a/app/views/application/_answerbox.html.haml b/app/views/application/_answerbox.html.haml index 20b88963..fed97697 100644 --- a/app/views/application/_answerbox.html.haml +++ b/app/views/application/_answerbox.html.haml @@ -19,14 +19,16 @@ %h6.answerbox__answer-user = raw t(".answered", hide: hidespan(t(".hide"), "d-none d-sm-inline"), user: user_screen_name(a.user)) .answerbox__answer-date - = link_to(raw(t("time.distance_ago", time: time_tooltip(a))), answer_path(a.user.screen_name, a.id)) + = link_to(answer_path(a.user.screen_name, a.id)) do + %time{ datetime: a.created_at.iso8601, data: { controller: "timestamp" } }= a.created_at .col-md-6.d-flex.d-md-block.answerbox__actions = render "answerbox/actions", a: a, display_all: display_all - else .row .col-md-6.text-start.text-muted %i.fa.fa-clock-o - = link_to(raw(t("time.distance_ago", time: time_tooltip(a))), answer_path(a.user.screen_name, a.id), class: "answerbox__permalink") + = link_to(answer_path(a.user.screen_name, a.id), class: "answerbox__permalink") do + %time{ datetime: a.created_at.iso8601, data: { controller: "timestamp" } }= a.created_at - if a.pinned_at.present? %span.answerbox__pinned ยท diff --git a/tsconfig.json b/tsconfig.json index 621e350f..8f168c9b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "lib": ["es6", "dom"], + "lib": ["es6", "es2020", "dom"], "module": "es6", "moduleResolution": "node", "resolveJsonModule": true,