Henry Jameson 2 місяців тому
батько
коміт
bd514ab6d0

+ 7 - 0
.browserslistrc

@@ -0,0 +1,7 @@
+>0.2%
+not op_mini all
+Safari > 15
+Firefox >= 115
+Firefox ESR
+Android > 4
+not dead

+ 2 - 0
.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

+ 9 - 0
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.

+ 1 - 0
changelog.d/date-absolute.add

@@ -0,0 +1 @@
+Support displaying time in absolute format

+ 23 - 0
src/components/settings_modal/tabs/general_tab.vue

@@ -217,6 +217,29 @@
             {{ $t('settings.no_rich_text_description') }}
           </BooleanSetting>
         </li>
+        <li>
+          <BooleanSetting
+            path="useAbsoluteTimeFormat"
+            expert="1"
+          >
+            {{ $t('settings.absolute_time_format') }}
+          </BooleanSetting>
+        </li>
+        <ul
+          class="setting-list suboptions"
+          v-if="mergedConfig.useAbsoluteTimeFormat"
+        >
+          <li>
+            <UnitSetting
+              path="absoluteTimeFormatMinAge"
+              unit-set="time"
+              :units="['s', 'm', 'h', 'd']"
+              :min="0"
+            >
+              {{ $t('settings.absolute_time_format_min_age') }}
+            </UnitSetting>
+          </li>
+        </ul>
         <h3>{{ $t('settings.attachments') }}</h3>
         <li>
           <BooleanSetting

+ 52 - 5
src/components/timeago/timeago.vue

@@ -3,7 +3,7 @@
     :datetime="time"
     :title="localeDateString"
   >
-    {{ relativeTimeString }}
+    {{ relativeOrAbsoluteTimeString }}
   </time>
 </template>
 
@@ -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)

+ 2 - 0
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",

+ 3 - 1
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

+ 2 - 0
src/modules/instance.js

@@ -119,6 +119,8 @@ const defaultState = {
   closingDrawerMarksAsSeen: true,
   unseenAtTop: false,
   ignoreInactionableSeen: false,
+  useAbsoluteTimeFormat: false,
+  absoluteTimeFormatMinAge: '0d',
 
   // Nasty stuff
   customEmoji: [],

+ 41 - 2
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
+  }
+}

+ 4 - 24
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"