diff --git a/.browserslistrc b/.browserslistrc
new file mode 100644
index 00000000..df2d4716
--- /dev/null
+++ b/.browserslistrc
@@ -0,0 +1,7 @@
+>0.2%
+not op_mini all
+Safari > 15
+Firefox >= 115
+Firefox ESR
+Android > 4
+not dead
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 25e499c4..f4c5cf43 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -45,6 +45,7 @@ test:
stage: test
tags:
- amd64
+ - himem
variables:
APT_CACHE_DIR: apt-cache
script:
@@ -58,6 +59,7 @@ build:
stage: build
tags:
- amd64
+ - himem
script:
- yarn
- npm run build
diff --git a/changelog.d/browsers-support.change b/changelog.d/browsers-support.change
new file mode 100644
index 00000000..a62e5024
--- /dev/null
+++ b/changelog.d/browsers-support.change
@@ -0,0 +1,9 @@
+Updated our build system to support browsers:
+ Safari >= 15
+ Firefox >= 115
+ Android > 4
+ no Opera Mini support
+ no IE support
+ no "dead" (unmaintained) browsers support
+
+This does not guarantee that browsers will or will not work.
diff --git a/changelog.d/date-absolute.add b/changelog.d/date-absolute.add
new file mode 100644
index 00000000..d9365f46
--- /dev/null
+++ b/changelog.d/date-absolute.add
@@ -0,0 +1 @@
+Support displaying time in absolute format
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 4ece6cf4..82d5da89 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -217,6 +217,29 @@
{{ $t('settings.no_rich_text_description') }}
+
+
+ {{ $t('settings.absolute_time_format') }}
+
+
+
+ -
+
+ {{ $t('settings.absolute_time_format_min_age') }}
+
+
+
{{ $t('settings.attachments') }}
- {{ relativeTimeString }}
+ {{ relativeOrAbsoluteTimeString }}
@@ -16,16 +16,28 @@ export default {
props: ['time', 'autoUpdate', 'longFormat', 'nowThreshold', 'templateKey'],
data () {
return {
+ relativeTimeMs: 0,
relativeTime: { key: 'time.now', num: 0 },
interval: null
}
},
computed: {
- localeDateString () {
- const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
+ shouldUseAbsoluteTimeFormat () {
+ if (!this.$store.getters.mergedConfig.useAbsoluteTimeFormat) {
+ return false
+ }
+ return DateUtils.durationStrToMs(this.$store.getters.mergedConfig.absoluteTimeFormatMinAge) <= this.relativeTimeMs
+ },
+ browserLocale () {
+ return localeService.internalToBrowserLocale(this.$i18n.locale)
+ },
+ timeAsDate () {
return typeof this.time === 'string'
- ? new Date(Date.parse(this.time)).toLocaleString(browserLocale)
- : this.time.toLocaleString(browserLocale)
+ ? new Date(Date.parse(this.time))
+ : this.time
+ },
+ localeDateString () {
+ return this.timeAsDate.toLocaleString(this.browserLocale)
},
relativeTimeString () {
const timeString = this.$i18n.tc(this.relativeTime.key, this.relativeTime.num, [this.relativeTime.num])
@@ -35,6 +47,40 @@ export default {
}
return timeString
+ },
+ absoluteTimeString () {
+ if (this.longFormat) {
+ return this.localeDateString
+ }
+ const now = new Date()
+ const formatter = (() => {
+ if (DateUtils.isSameDay(this.timeAsDate, now)) {
+ return new Intl.DateTimeFormat(this.browserLocale, {
+ minute: 'numeric',
+ hour: 'numeric'
+ })
+ } else if (DateUtils.isSameMonth(this.timeAsDate, now)) {
+ return new Intl.DateTimeFormat(this.browserLocale, {
+ hour: 'numeric',
+ day: 'numeric'
+ })
+ } else if (DateUtils.isSameYear(this.timeAsDate, now)) {
+ return new Intl.DateTimeFormat(this.browserLocale, {
+ month: 'short',
+ day: 'numeric'
+ })
+ } else {
+ return new Intl.DateTimeFormat(this.browserLocale, {
+ year: 'numeric',
+ month: 'short'
+ })
+ }
+ })()
+
+ return formatter.format(this.timeAsDate)
+ },
+ relativeOrAbsoluteTimeString () {
+ return this.shouldUseAbsoluteTimeFormat ? this.absoluteTimeString : this.relativeTimeString
}
},
watch: {
@@ -54,6 +100,7 @@ export default {
methods: {
refreshRelativeTimeObject () {
const nowThreshold = typeof this.nowThreshold === 'number' ? this.nowThreshold : 1
+ this.relativeTimeMs = DateUtils.relativeTimeMs(this.time)
this.relativeTime = this.longFormat
? DateUtils.relativeTime(this.time, nowThreshold)
: DateUtils.relativeTimeShort(this.time, nowThreshold)
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 3f7ea282..0ed481c0 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -506,6 +506,8 @@
"autocomplete_select_first": "Automatically select the first candidate when autocomplete results are available",
"emoji_reactions_on_timeline": "Show emoji reactions on timeline",
"emoji_reactions_scale": "Reactions scale factor",
+ "absolute_time_format": "Use absolute time format",
+ "absolute_time_format_min_age": "Only use for time older than this amount of time",
"export_theme": "Save preset",
"filtering": "Filtering",
"wordfilter": "Wordfilter",
diff --git a/src/modules/config.js b/src/modules/config.js
index cf84234a..835dcce4 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -180,7 +180,9 @@ export const defaultState = {
autocompleteSelect: undefined, // instance default
closingDrawerMarksAsSeen: undefined, // instance default
unseenAtTop: undefined, // instance default
- ignoreInactionableSeen: undefined // instance default
+ ignoreInactionableSeen: undefined, // instance default
+ useAbsoluteTimeFormat: undefined, // instance defualt
+ absoluteTimeFormatMinAge: undefined // instance default
}
// caching the instance default properties
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 99b8b5d5..994f60a5 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -119,6 +119,8 @@ const defaultState = {
closingDrawerMarksAsSeen: true,
unseenAtTop: false,
ignoreInactionableSeen: false,
+ useAbsoluteTimeFormat: false,
+ absoluteTimeFormatMinAge: '0d',
// Nasty stuff
customEmoji: [],
diff --git a/src/services/date_utils/date_utils.js b/src/services/date_utils/date_utils.js
index ed8e1417..69398c0c 100644
--- a/src/services/date_utils/date_utils.js
+++ b/src/services/date_utils/date_utils.js
@@ -6,10 +6,13 @@ export const WEEK = 7 * DAY
export const MONTH = 30 * DAY
export const YEAR = 365.25 * DAY
-export const relativeTime = (date, nowThreshold = 1) => {
+export const relativeTimeMs = (date) => {
if (typeof date === 'string') date = Date.parse(date)
+ return Math.abs(Date.now() - date)
+}
+export const relativeTime = (date, nowThreshold = 1) => {
const round = Date.now() > date ? Math.floor : Math.ceil
- const d = Math.abs(Date.now() - date)
+ const d = relativeTimeMs(date)
const r = { num: round(d / YEAR), key: 'time.unit.years' }
if (d < nowThreshold * SECOND) {
r.num = 0
@@ -57,3 +60,39 @@ export const secondsToUnit = (unit, amount) => {
case 'days': return (1000 * amount) / DAY
}
}
+
+export const isSameYear = (a, b) => {
+ return a.getFullYear() === b.getFullYear()
+}
+
+export const isSameMonth = (a, b) => {
+ return a.getFullYear() === b.getFullYear() &&
+ a.getMonth() === b.getMonth()
+}
+
+export const isSameDay = (a, b) => {
+ return a.getFullYear() === b.getFullYear() &&
+ a.getMonth() === b.getMonth() &&
+ a.getDate() === b.getDate()
+}
+
+export const durationStrToMs = (str) => {
+ if (typeof str !== 'string') {
+ return 0
+ }
+
+ const unit = str.replace(/[0-9,.]+/, '')
+ const value = str.replace(/[^0-9,.]+/, '')
+ switch (unit) {
+ case 'd':
+ return value * DAY
+ case 'h':
+ return value * HOUR
+ case 'm':
+ return value * MINUTE
+ case 's':
+ return value * SECOND
+ default:
+ return 0
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index c42fe8fa..d6474250 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3144,30 +3144,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001370:
- version "1.0.30001376"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001376.tgz#af2450833e5a06873fbb030a9556ca9461a2736d"
- integrity sha512-I27WhtOQ3X3v3it9gNs/oTpoE5KpwmqKR5oKPA8M0G7uMXh9Ty81Q904HpKUrM30ei7zfcL5jE7AXefgbOfMig==
-
-caniuse-lite@^1.0.30001359:
- version "1.0.30001366"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001366.tgz#c73352c83830a9eaf2dea0ff71fb4b9a4bbaa89c"
- integrity sha512-yy7XLWCubDobokgzudpkKux8e0UOOnLHE6mlNJBzT3lZJz6s5atSEzjoL+fsCPkI0G8MP5uVdDx1ur/fXEWkZA==
-
-caniuse-lite@^1.0.30001400:
- version "1.0.30001418"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz#5f459215192a024c99e3e3a53aac310fc7cf24e6"
- integrity sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg==
-
-caniuse-lite@^1.0.30001587:
- version "1.0.30001591"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz#16745e50263edc9f395895a7cd468b9f3767cf33"
- integrity sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==
-
-caniuse-lite@^1.0.30001599:
- version "1.0.30001599"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce"
- integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001359, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599:
+ version "1.0.30001662"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz"
+ integrity sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==
chai-nightwatch@0.5.3:
version "0.5.3"