diff --git a/app/components/contribution-photo.gjs b/app/components/contribution-photo.gjs
index c2a9ac6..80b488b 100644
--- a/app/components/contribution-photo.gjs
+++ b/app/components/contribution-photo.gjs
@@ -2,26 +2,7 @@ import Component from '@glimmer/component';
import { on } from '@ember/modifier';
import { fn } from '@ember/helper';
import or from 'ember-truth-helpers/helpers/or';
-
-function formatRelativeDate(timestamp) {
- if (!timestamp) return '';
- const date = new Date(timestamp * 1000);
- const now = new Date();
- const diffMs = now - date;
- const diffMin = Math.floor(diffMs / 60000);
- const diffHr = Math.floor(diffMin / 60);
- const diffDay = Math.floor(diffHr / 24);
-
- if (diffMin < 1) return 'just now';
- if (diffMin < 60) return `${diffMin} min ago`;
- if (diffHr < 24) return `${diffHr} hr ago`;
- if (diffDay < 7) return `${diffDay} day${diffDay === 1 ? '' : 's'} ago`;
- return date.toLocaleDateString(undefined, {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- });
-}
+import formatRelativeDate from '../helpers/format-relative-date';
export default class ContributionPhoto extends Component {
get item() {
@@ -44,10 +25,6 @@ export default class ContributionPhoto extends Component {
return this.item?.placeNameLoading;
}
- get relativeDate() {
- return formatRelativeDate(this.item?.createdAt);
- }
-
get tags() {
// Collect unique tags across all photos in the contribution group
const all = (this.item?.photos || []).flatMap((p) => p.tags || []);
@@ -86,7 +63,7 @@ export default class ContributionPhoto extends Component {
{{/if}}
- {{this.relativeDate}}
+ {{formatRelativeDate @item.createdAt}}
{{#if (gt this.item.photos.length 1)}}
ยท
{{this.item.photos.length}}
diff --git a/app/helpers/format-relative-date.js b/app/helpers/format-relative-date.js
new file mode 100644
index 0000000..6b435b9
--- /dev/null
+++ b/app/helpers/format-relative-date.js
@@ -0,0 +1,6 @@
+import { helper } from '@ember/component/helper';
+import { formatRelativeDate as format } from '../utils/format-text';
+
+export default helper(function formatRelativeDate([timestamp]) {
+ return format(timestamp);
+});
diff --git a/app/utils/format-text.js b/app/utils/format-text.js
index a08f349..f6d7564 100644
--- a/app/utils/format-text.js
+++ b/app/utils/format-text.js
@@ -12,3 +12,23 @@ export function capitalize(text) {
if (typeof text !== 'string' || !text) return '';
return text.charAt(0).toUpperCase() + text.slice(1);
}
+
+export function formatRelativeDate(timestamp) {
+ if (!timestamp) return '';
+ const date = new Date(timestamp * 1000);
+ const now = new Date();
+ const diffMs = now - date;
+ const diffMin = Math.floor(diffMs / 60000);
+ const diffHr = Math.floor(diffMin / 60);
+ const diffDay = Math.floor(diffHr / 24);
+
+ if (diffMin < 1) return 'just now';
+ if (diffMin < 60) return `${diffMin} min ago`;
+ if (diffHr < 24) return `${diffHr} hr ago`;
+ if (diffDay < 7) return `${diffDay} day${diffDay === 1 ? '' : 's'} ago`;
+ return date.toLocaleDateString(undefined, {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ });
+}
diff --git a/tests/unit/utils/format-text-test.js b/tests/unit/utils/format-text-test.js
new file mode 100644
index 0000000..be47463
--- /dev/null
+++ b/tests/unit/utils/format-text-test.js
@@ -0,0 +1,66 @@
+import { module, test } from 'qunit';
+import {
+ formatRelativeDate,
+ humanizeOsmTag,
+ capitalize,
+} from 'marco/utils/format-text';
+
+module('Unit | Utility | format-text', function () {
+ test('humanizeOsmTag replaces underscores and dashes with spaces and title-cases', function (assert) {
+ assert.strictEqual(humanizeOsmTag('atm'), 'Atm');
+ assert.strictEqual(humanizeOsmTag('fast_food'), 'Fast Food');
+ assert.strictEqual(humanizeOsmTag('phone-repair'), 'Phone Repair');
+ assert.strictEqual(humanizeOsmTag(''), '');
+ assert.strictEqual(humanizeOsmTag(null), '');
+ });
+
+ test('capitalize uppercases the first letter', function (assert) {
+ assert.strictEqual(capitalize('hello'), 'Hello');
+ assert.strictEqual(capitalize(''), '');
+ assert.strictEqual(capitalize(null), '');
+ });
+
+ test('formatRelativeDate returns empty string for falsy timestamp', function (assert) {
+ assert.strictEqual(formatRelativeDate(null), '');
+ assert.strictEqual(formatRelativeDate(0), '');
+ assert.strictEqual(formatRelativeDate(undefined), '');
+ });
+
+ test('formatRelativeDate returns "just now" for timestamps within the last minute', function (assert) {
+ const now = Math.floor(Date.now() / 1000);
+ assert.strictEqual(formatRelativeDate(now), 'just now');
+ assert.strictEqual(formatRelativeDate(now - 10), 'just now');
+ });
+
+ test('formatRelativeDate returns "min ago" for timestamps within the last hour', function (assert) {
+ const now = Math.floor(Date.now() / 1000);
+ assert.strictEqual(formatRelativeDate(now - 60), '1 min ago');
+ assert.strictEqual(formatRelativeDate(now - 300), '5 min ago');
+ assert.strictEqual(formatRelativeDate(now - 3599), '59 min ago');
+ });
+
+ test('formatRelativeDate returns "hr ago" for timestamps within the last day', function (assert) {
+ const now = Math.floor(Date.now() / 1000);
+ assert.strictEqual(formatRelativeDate(now - 3600), '1 hr ago');
+ assert.strictEqual(formatRelativeDate(now - 7200), '2 hr ago');
+ assert.strictEqual(formatRelativeDate(now - 86399), '23 hr ago');
+ });
+
+ test('formatRelativeDate returns "day(s) ago" for timestamps within the last week', function (assert) {
+ const now = Math.floor(Date.now() / 1000);
+ assert.strictEqual(formatRelativeDate(now - 86400), '1 day ago');
+ assert.strictEqual(formatRelativeDate(now - 86400 * 2), '2 days ago');
+ assert.strictEqual(formatRelativeDate(now - 86400 * 6), '6 days ago');
+ });
+
+ test('formatRelativeDate returns an absolute date for timestamps older than a week', function (assert) {
+ const now = Math.floor(Date.now() / 1000);
+ const oldTimestamp = now - 86400 * 30; // 30 days ago
+ const result = formatRelativeDate(oldTimestamp);
+ // The exact format depends on the locale, but it should contain a year
+ assert.ok(
+ result.includes(String(new Date().getFullYear())),
+ 'Contains the year'
+ );
+ });
+});