diff --git a/docs/API-Reference/utils/LocalizationUtils.md b/docs/API-Reference/utils/LocalizationUtils.md
index 2b9cb0035d..efe3950c23 100644
--- a/docs/API-Reference/utils/LocalizationUtils.md
+++ b/docs/API-Reference/utils/LocalizationUtils.md
@@ -34,7 +34,7 @@ Formats a given date object into a locale-aware date and time string.
-## dateTimeFromNow([date], [lang]) ⇒ string
+## dateTimeFromNow([date], [lang], [fromDate]) ⇒ string
Returns a relative time string (e.g., "2 days ago", "in 3 hours") based on the difference between the given date and now.
**Kind**: global function
@@ -44,6 +44,7 @@ Returns a relative time string (e.g., "2 days ago", "in 3 hours") based on the d
| --- | --- | --- |
| [date] | Date | The date to compare with the current date and time. If not given, defaults to now. |
| [lang] | string | Optional language code to use for formatting (e.g., 'en', 'fr'). If not provided, defaults to the application locale or 'en'. |
+| [fromDate] | string | Optional date to use instead of now to compute the relative dateTime from. |
diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js
index afd026f374..525659da69 100644
--- a/src/document/DocumentCommandHandlers.js
+++ b/src/document/DocumentCommandHandlers.js
@@ -1957,6 +1957,9 @@ define(function (require, exports, module) {
if (entry) {
brackets.app.openPathInFileBrowser(entry.fullPath)
.catch(err=>console.error("Error showing '" + entry.fullPath + "' in OS folder:", err));
+ } else {
+ brackets.app.openPathInFileBrowser(ProjectManager.getProjectRoot().fullPath)
+ .catch(err=>console.error("Error showing '" + ProjectManager.getProjectRoot().fullPath + "' in OS folder:", err));
}
}
@@ -1964,6 +1967,8 @@ define(function (require, exports, module) {
const entry = ProjectManager.getSelectedItem();
if (entry && entry.fullPath) {
NodeUtils.openNativeTerminal(entry.fullPath);
+ } else {
+ NodeUtils.openNativeTerminal(ProjectManager.getProjectRoot().fullPath);
}
}
@@ -1971,13 +1976,17 @@ define(function (require, exports, module) {
const entry = ProjectManager.getSelectedItem();
if (entry && entry.fullPath) {
NodeUtils.openNativeTerminal(entry.fullPath, true);
+ } else {
+ NodeUtils.openNativeTerminal(ProjectManager.getProjectRoot().fullPath, true);
}
}
function openDefaultApp() {
const entry = ProjectManager.getSelectedItem();
if (entry && entry.fullPath) {
- NodeUtils.openInDefaultApp(entry.fullPath, true);
+ NodeUtils.openInDefaultApp(entry.fullPath);
+ } else {
+ NodeUtils.openInDefaultApp(ProjectManager.getProjectRoot().fullPath);
}
}
diff --git a/src/styles/brackets_core_ui_variables.less b/src/styles/brackets_core_ui_variables.less
index dd07a3c947..9266d8e41d 100644
--- a/src/styles/brackets_core_ui_variables.less
+++ b/src/styles/brackets_core_ui_variables.less
@@ -108,7 +108,7 @@
@bc-panel-bg-hover: rgba(255, 255, 255, 0.6);
@bc-panel-bg-hover-alt: rgba(0, 0, 0, 0.04);
@bc-panel-bg-selected: #d0d5d5;
-@bc-panel-bg-selected-table: #b8c9d1;
+@bc-panel-bg-selected-table: #cddae0;
@bc-panel-bg-text-highlight: #fff;
@bc-panel-border: rgba(0, 0, 0, 0.09);
@bc-panel-separator: #c3c6c5;
diff --git a/src/utils/LocalizationUtils.js b/src/utils/LocalizationUtils.js
index 19118b204a..7abe394f24 100644
--- a/src/utils/LocalizationUtils.js
+++ b/src/utils/LocalizationUtils.js
@@ -77,28 +77,31 @@ define(function (require, exports, module) {
* @param {Date} [date] - The date to compare with the current date and time. If not given, defaults to now.
* @param {string} [lang] - Optional language code to use for formatting (e.g., 'en', 'fr').
* If not provided, defaults to the application locale or 'en'.
+ * @param {Date} [fromDate] - Optional date to use instead of now to compute the relative dateTime from.
* @returns {string} - A human-readable relative time string (e.g., "2 days ago", "in 3 hours").
*/
- function dateTimeFromNow(date, lang) {
+ function dateTimeFromNow(date, lang, fromDate) {
date = date || new Date();
- const now = new Date();
- const diffInSeconds = Math.floor((date - now) / 1000);
+ fromDate = fromDate || new Date();
+ const diffInSeconds = Math.floor((date - fromDate) / 1000);
const rtf = new Intl.RelativeTimeFormat([lang || brackets.getLocale() || "en", "en"],
{ numeric: 'auto' });
-
- if (Math.abs(diffInSeconds) < 60) {
+ if (Math.abs(diffInSeconds) < 3) {
+ // we consider diffs less than 3 seconds to be always 'now', for better UX and for unit tests stability.
+ return rtf.format(0, 'second');
+ } else if (Math.abs(diffInSeconds) < 60) {
return rtf.format(diffInSeconds, 'second');
} else if (Math.abs(diffInSeconds) < 3600) {
- return rtf.format(Math.floor(diffInSeconds / 60), 'minute');
+ return rtf.format(Math.trunc(diffInSeconds / 60), 'minute');
} else if (Math.abs(diffInSeconds) < 86400) {
- return rtf.format(Math.floor(diffInSeconds / 3600), 'hour');
+ return rtf.format(Math.trunc(diffInSeconds / 3600), 'hour');
} else if (Math.abs(diffInSeconds) < 2592000) {
- return rtf.format(Math.floor(diffInSeconds / 86400), 'day');
+ return rtf.format(Math.trunc(diffInSeconds / 86400), 'day');
} else if (Math.abs(diffInSeconds) < 31536000) {
- return rtf.format(Math.floor(diffInSeconds / 2592000), 'month');
+ return rtf.format(Math.trunc(diffInSeconds / 2592000), 'month');
} else {
- return rtf.format(Math.floor(diffInSeconds / 31536000), 'year');
+ return rtf.format(Math.trunc(diffInSeconds / 31536000), 'year');
}
}
@@ -115,7 +118,7 @@ define(function (require, exports, module) {
function dateTimeFromNowFriendly(date, lang) {
const now = new Date();
const diffInMilliseconds = date - now;
- const diffInDays = Math.floor(diffInMilliseconds / (1000 * 60 * 60 * 24));
+ const diffInDays = Math.trunc(diffInMilliseconds / (1000 * 60 * 60 * 24));
// If within the last 30 days or the future, use relative time
if (Math.abs(diffInDays) <= 30) {
diff --git a/test/spec/LocalizationUtils-test.js b/test/spec/LocalizationUtils-test.js
index 34e2bf898a..e91099c9fb 100644
--- a/test/spec/LocalizationUtils-test.js
+++ b/test/spec/LocalizationUtils-test.js
@@ -132,64 +132,118 @@ define(function (require, exports, module) {
});
describe("dateTimeFromNow", function () {
- it("should return 'now' for current time", function () {
- const now = new Date();
+ // we take a reference date for tests stability. due to use of math.trunk, using date.now will
+ // sometimes lead to valid time jumps in tests. so we just take a reference time fixed to not account for
+ // any time taken in compute for tests.
+ const refDate = Date.now();
+ function referenceDate({
+ seconds = 0,
+ minutes = 0,
+ hours = 0,
+ days = 0,
+ months = 0,
+ years = 0
+ } = {}) {
+ const date = new Date(refDate); // Original reference date
+
+ // Add time components
+ date.setSeconds(date.getSeconds() + seconds);
+ date.setMinutes(date.getMinutes() + minutes);
+ date.setHours(date.getHours() + hours);
+ date.setDate(date.getDate() + days);
+ date.setMonth(date.getMonth() + months);
+ date.setFullYear(date.getFullYear() + years);
+
+ return date;
+ }
+
+ it("should return 'now' for current time without specifying fromTime", function () {
+ let now = new Date();
let result = LocalizationUtils.dateTimeFromNow(now, "en");
expect(result).toBe("now");
result = LocalizationUtils.dateTimeFromNow(now, "de");
expect(result).toBe("jetzt");
});
+ it("should return 'now' for current time within 3 seconds of time", function () {
+ let now = referenceDate();
+ let result = LocalizationUtils.dateTimeFromNow(now, "en", referenceDate());
+ expect(result).toBe("now");
+ now = referenceDate({seconds: 1});
+ result = LocalizationUtils.dateTimeFromNow(now, "en", referenceDate());
+ expect(result).toBe("now");
+ now = referenceDate({seconds: 2});
+ result = LocalizationUtils.dateTimeFromNow(now, "en", referenceDate());
+ expect(result).toBe("now");
+ result = LocalizationUtils.dateTimeFromNow(now, "de", referenceDate());
+ expect(result).toBe("jetzt");
+ now = referenceDate({seconds: 1});
+ result = LocalizationUtils.dateTimeFromNow(now, "de", referenceDate());
+ expect(result).toBe("jetzt");
+ now = referenceDate({seconds: 2});
+ result = LocalizationUtils.dateTimeFromNow(now, "de", referenceDate());
+ expect(result).toBe("jetzt");
+ });
+
it("should handle future dates within seconds", function () {
- const futureDate = new Date(Date.now() + 30 * 1000); // 30 seconds in the future
- const result = LocalizationUtils.dateTimeFromNow(futureDate, "en");
+ const futureDate = referenceDate({seconds: 30}); // 30 seconds in the future
+ const result = LocalizationUtils.dateTimeFromNow(futureDate, "en", referenceDate());
expect(result).toBe("in 30 seconds");
});
it("should handle past dates within minutes", function () {
- const pastDate = new Date(Date.now() - 90 * 1000); // 90 seconds in the past
- const result = LocalizationUtils.dateTimeFromNow(pastDate, "en");
+ const pastDate = referenceDate({seconds: -90}); // 90 seconds in the past
+ const result = LocalizationUtils.dateTimeFromNow(pastDate, "en", referenceDate());
+ expect(result).toBe("1 minute ago");
+ });
+
+ it("should handle past and future dates similarly", function () {
+ const pastDate = referenceDate({seconds: -130}); // 2 minutes and 10 secs
+ let result = LocalizationUtils.dateTimeFromNow(pastDate, "en", referenceDate());
expect(result).toBe("2 minutes ago");
+ const futureDate = referenceDate({seconds: 130}); // 2 minutes and 10 secs
+ result = LocalizationUtils.dateTimeFromNow(futureDate, "en", referenceDate());
+ expect(result).toBe("in 2 minutes");
});
it("should handle future dates within hours", function () {
- const futureDate = new Date(Date.now() + 2 * 60 * 60 * 1000); // 2 hours in the future
- const result = LocalizationUtils.dateTimeFromNow(futureDate, "en");
+ const futureDate = referenceDate({hours: 2}); // 2 hours in the future
+ const result = LocalizationUtils.dateTimeFromNow(futureDate, "en", referenceDate());
expect(result).toBe("in 2 hours");
});
it("should handle past dates within days", function () {
- const pastDate = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000); // 3 days ago
- const result = LocalizationUtils.dateTimeFromNow(pastDate, "en");
+ const pastDate = referenceDate({ days: -3 }); // 3 days ago
+ const result = LocalizationUtils.dateTimeFromNow(pastDate, "en", referenceDate());
expect(result).toBe("3 days ago");
});
it("should handle future dates within months", function () {
- const futureDate = new Date(Date.now() + 45 * 24 * 60 * 60 * 1000); // 45 days in the future
- const result = LocalizationUtils.dateTimeFromNow(futureDate, "en");
+ const futureDate = referenceDate({ days: 45 }); // 45 days in the future
+ const result = LocalizationUtils.dateTimeFromNow(futureDate, "en", referenceDate());
expect(result).toBe("next month");
});
it("should handle past dates within years", function () {
- const pastDate = new Date(Date.now() - 2 * 365 * 24 * 60 * 60 * 1000); // 2 years ago
- const result = LocalizationUtils.dateTimeFromNow(pastDate, "en");
+ const pastDate = referenceDate({ years: -2 }); // 2 years ago
+ const result = LocalizationUtils.dateTimeFromNow(pastDate, "en", referenceDate());
expect(result).toBe("2 years ago");
});
it("should return relative time in French locale", function () {
- const pastDate = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000); // 3 days ago
- const result = LocalizationUtils.dateTimeFromNow(pastDate, "fr");
+ const pastDate = referenceDate({ days: -3 }); // 3 days ago
+ const result = LocalizationUtils.dateTimeFromNow(pastDate, "fr", referenceDate());
expect(result).toBe("il y a 3 jours");
});
it("should fallback to default locale if an invalid locale is specified", function () {
- const futureDate = new Date(Date.now() + 2 * 60 * 60 * 1000); // 2 hours in the future
- const result = LocalizationUtils.dateTimeFromNow(futureDate, "invalid-locale");
+ const futureDate = referenceDate({ hours: 2 }); // 2 hours in the future
+ const result = LocalizationUtils.dateTimeFromNow(futureDate, "invalid-locale", referenceDate());
expect(result).toBe("in 2 hours");
});
it("should handle default date input (now) gracefully", function () {
- const result = LocalizationUtils.dateTimeFromNow(undefined, "en");
+ const result = LocalizationUtils.dateTimeFromNow();
expect(result).toBe("now");
});
});