mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-12 03:26:30 +00:00
Use JS-native date/time formatting (#4488)
This commit is contained in:
parent
7309ba0a82
commit
9a8d7d8838
8 changed files with 79 additions and 101 deletions
1
web/.gitignore
vendored
1
web/.gitignore
vendored
|
@ -3,4 +3,3 @@ node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
src/assets/dayjsLocales
|
|
||||||
|
|
|
@ -4,5 +4,4 @@ coverage/
|
||||||
LICENSE
|
LICENSE
|
||||||
components.d.ts
|
components.d.ts
|
||||||
src/assets/locales/*.json
|
src/assets/locales/*.json
|
||||||
src/assets/dayjsLocales/
|
|
||||||
!src/assets/locales/en.json
|
!src/assets/locales/en.json
|
||||||
|
|
|
@ -106,7 +106,6 @@ export default antfu(
|
||||||
'tsconfig.json',
|
'tsconfig.json',
|
||||||
'src/assets/locales/**/*',
|
'src/assets/locales/**/*',
|
||||||
'!src/assets/locales/en.json',
|
'!src/assets/locales/en.json',
|
||||||
'src/assets/dayjsLocales/',
|
|
||||||
'components.d.ts',
|
'components.d.ts',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.4.47",
|
||||||
"@vueuse/core": "^12.0.0",
|
"@vueuse/core": "^12.0.0",
|
||||||
"ansi_up": "^6.0.2",
|
"ansi_up": "^6.0.2",
|
||||||
"dayjs": "^1.11.12",
|
|
||||||
"dompurify": "^3.2.0",
|
"dompurify": "^3.2.0",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
|
@ -57,7 +56,6 @@
|
||||||
"eslint-plugin-vue-scoped-css": "^2.8.1",
|
"eslint-plugin-vue-scoped-css": "^2.8.1",
|
||||||
"jsdom": "^25.0.0",
|
"jsdom": "^25.0.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"replace-in-file": "^8.1.0",
|
|
||||||
"tinycolor2": "^1.6.0",
|
"tinycolor2": "^1.6.0",
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.6.3",
|
||||||
"vite": "^5.4.1",
|
"vite": "^5.4.1",
|
||||||
|
|
|
@ -26,9 +26,6 @@ importers:
|
||||||
ansi_up:
|
ansi_up:
|
||||||
specifier: ^6.0.2
|
specifier: ^6.0.2
|
||||||
version: 6.0.2
|
version: 6.0.2
|
||||||
dayjs:
|
|
||||||
specifier: ^1.11.12
|
|
||||||
version: 1.11.13
|
|
||||||
dompurify:
|
dompurify:
|
||||||
specifier: ^3.2.0
|
specifier: ^3.2.0
|
||||||
version: 3.2.1
|
version: 3.2.1
|
||||||
|
@ -123,9 +120,6 @@ importers:
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.3.3
|
specifier: ^3.3.3
|
||||||
version: 3.3.3
|
version: 3.3.3
|
||||||
replace-in-file:
|
|
||||||
specifier: ^8.1.0
|
|
||||||
version: 8.2.0
|
|
||||||
tinycolor2:
|
tinycolor2:
|
||||||
specifier: ^1.6.0
|
specifier: ^1.6.0
|
||||||
version: 1.6.0
|
version: 1.6.0
|
||||||
|
@ -1107,10 +1101,6 @@ packages:
|
||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
chalk@5.3.0:
|
|
||||||
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
|
|
||||||
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
|
||||||
|
|
||||||
char-regex@1.0.2:
|
char-regex@1.0.2:
|
||||||
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
|
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -1214,9 +1204,6 @@ packages:
|
||||||
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
|
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
dayjs@1.11.13:
|
|
||||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
|
||||||
|
|
||||||
de-indent@1.0.2:
|
de-indent@1.0.2:
|
||||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||||
|
|
||||||
|
@ -2295,11 +2282,6 @@ packages:
|
||||||
resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==}
|
resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
replace-in-file@8.2.0:
|
|
||||||
resolution: {integrity: sha512-hMsQtdYHwWviQT5ZbNsgfu0WuCiNlcUSnnD+aHAL081kbU9dPkPocDaHlDvAHKydTWWpx1apfcEcmvIyQk3CpQ==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
require-directory@2.1.1:
|
require-directory@2.1.1:
|
||||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -3798,8 +3780,6 @@ snapshots:
|
||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
supports-color: 7.2.0
|
supports-color: 7.2.0
|
||||||
|
|
||||||
chalk@5.3.0: {}
|
|
||||||
|
|
||||||
char-regex@1.0.2: {}
|
char-regex@1.0.2: {}
|
||||||
|
|
||||||
character-entities@2.0.2: {}
|
character-entities@2.0.2: {}
|
||||||
|
@ -3898,8 +3878,6 @@ snapshots:
|
||||||
whatwg-mimetype: 4.0.0
|
whatwg-mimetype: 4.0.0
|
||||||
whatwg-url: 14.0.0
|
whatwg-url: 14.0.0
|
||||||
|
|
||||||
dayjs@1.11.13: {}
|
|
||||||
|
|
||||||
de-indent@1.0.2: {}
|
de-indent@1.0.2: {}
|
||||||
|
|
||||||
debug@3.2.7:
|
debug@3.2.7:
|
||||||
|
@ -5205,12 +5183,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
jsesc: 0.5.0
|
jsesc: 0.5.0
|
||||||
|
|
||||||
replace-in-file@8.2.0:
|
|
||||||
dependencies:
|
|
||||||
chalk: 5.3.0
|
|
||||||
glob: 10.4.5
|
|
||||||
yargs: 17.7.2
|
|
||||||
|
|
||||||
require-directory@2.1.1: {}
|
require-directory@2.1.1: {}
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
"not_found": "Server could not find requested object"
|
"not_found": "Server could not find requested object"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"template": "MMM D, YYYY, HH:mm z",
|
"not_started": "not started yet",
|
||||||
"not_started": "not started yet"
|
"just_now": "just now"
|
||||||
},
|
},
|
||||||
"repo": {
|
"repo": {
|
||||||
"manual_pipeline": {
|
"manual_pipeline": {
|
||||||
|
|
|
@ -1,44 +1,94 @@
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
|
||||||
import duration from 'dayjs/plugin/duration';
|
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
|
||||||
import timezone from 'dayjs/plugin/timezone';
|
|
||||||
import utc from 'dayjs/plugin/utc';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
dayjs.extend(timezone);
|
let currentLocale = 'en';
|
||||||
dayjs.extend(utc);
|
|
||||||
dayjs.extend(advancedFormat);
|
|
||||||
dayjs.extend(relativeTime);
|
|
||||||
dayjs.extend(duration);
|
|
||||||
|
|
||||||
function toLocaleString(date: Date) {
|
function splitDuration(durationMs: number) {
|
||||||
return dayjs(date).format(useI18n().t('time.template'));
|
const totalSeconds = durationMs / 1000;
|
||||||
|
const totalMinutes = totalSeconds / 60;
|
||||||
|
const totalHours = totalMinutes / 60;
|
||||||
|
|
||||||
|
const seconds = Math.floor(totalSeconds) % 60;
|
||||||
|
const minutes = Math.floor(totalMinutes) % 60;
|
||||||
|
const hours = Math.floor(totalHours) % 24;
|
||||||
|
|
||||||
|
return {
|
||||||
|
seconds,
|
||||||
|
minutes,
|
||||||
|
hours,
|
||||||
|
totalHours,
|
||||||
|
totalMinutes,
|
||||||
|
totalSeconds,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function timeAgo(date: Date | string | number) {
|
function toLocaleString(date: Date) {
|
||||||
return dayjs().to(dayjs(date));
|
return date.toLocaleString(currentLocale, {
|
||||||
|
dateStyle: 'short',
|
||||||
|
timeStyle: 'short',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeAgo(date: number) {
|
||||||
|
const seconds = Math.floor((new Date().getTime() - date) / 1000);
|
||||||
|
|
||||||
|
const formatter = new Intl.RelativeTimeFormat(currentLocale);
|
||||||
|
|
||||||
|
let interval = seconds / 31536000;
|
||||||
|
if (interval > 1) {
|
||||||
|
return formatter.format(-Math.round(interval), 'year');
|
||||||
|
}
|
||||||
|
interval = seconds / 2592000;
|
||||||
|
if (interval > 1) {
|
||||||
|
return formatter.format(-Math.round(interval), 'month');
|
||||||
|
}
|
||||||
|
interval = seconds / 86400;
|
||||||
|
if (interval > 1) {
|
||||||
|
return formatter.format(-Math.round(interval), 'day');
|
||||||
|
}
|
||||||
|
interval = seconds / 3600;
|
||||||
|
if (interval > 1) {
|
||||||
|
return formatter.format(-Math.round(interval), 'hour');
|
||||||
|
}
|
||||||
|
interval = seconds / 60;
|
||||||
|
if (interval > 0.5) {
|
||||||
|
return formatter.format(-Math.round(interval), 'minute');
|
||||||
|
}
|
||||||
|
return useI18n().t('time.just_now');
|
||||||
}
|
}
|
||||||
|
|
||||||
function prettyDuration(durationMs: number) {
|
function prettyDuration(durationMs: number) {
|
||||||
return dayjs.duration(durationMs).humanize();
|
const t = splitDuration(durationMs);
|
||||||
|
|
||||||
|
if (t.totalHours > 1) {
|
||||||
|
return Intl.NumberFormat(currentLocale, { style: 'unit', unit: 'hour', unitDisplay: 'long' }).format(
|
||||||
|
Math.round(t.totalHours),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (t.totalMinutes > 1) {
|
||||||
|
return Intl.NumberFormat(currentLocale, { style: 'unit', unit: 'minute', unitDisplay: 'long' }).format(
|
||||||
|
Math.round(t.totalMinutes),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Intl.NumberFormat(currentLocale, { style: 'unit', unit: 'second', unitDisplay: 'long' }).format(
|
||||||
|
Math.round(t.totalSeconds),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function durationAsNumber(durationMs: number): string {
|
function durationAsNumber(durationMs: number): string {
|
||||||
const dur = dayjs.duration(durationMs);
|
const { seconds, minutes, hours } = splitDuration(durationMs);
|
||||||
return dur.format(dur.hours() > 1 ? 'HH:mm:ss' : 'mm:ss');
|
|
||||||
|
const minSecFormat = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours.toString().padStart(2, '0')}:${minSecFormat}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return minSecFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDate() {
|
export function useDate() {
|
||||||
const addedLocales = ['en'];
|
|
||||||
|
|
||||||
async function setDayjsLocale(locale: string) {
|
async function setDayjsLocale(locale: string) {
|
||||||
if (!addedLocales.includes(locale)) {
|
currentLocale = locale;
|
||||||
const l = (await import(`~/assets/dayjsLocales/${locale}.js`)) as { default: string };
|
|
||||||
dayjs.locale(l.default);
|
|
||||||
} else {
|
|
||||||
dayjs.locale(locale);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { copyFile, existsSync, mkdirSync, readdirSync } from 'node:fs';
|
import { readdirSync } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import { replaceInFileSync } from 'replace-in-file';
|
|
||||||
import type { Plugin } from 'vite';
|
import type { Plugin } from 'vite';
|
||||||
import prismjs from 'vite-plugin-prismjs';
|
import prismjs from 'vite-plugin-prismjs';
|
||||||
import WindiCSS from 'vite-plugin-windicss';
|
import WindiCSS from 'vite-plugin-windicss';
|
||||||
|
@ -54,44 +53,6 @@ export default defineConfig({
|
||||||
|
|
||||||
const filenames = readdirSync('src/assets/locales/').map((filename) => filename.replace('.json', ''));
|
const filenames = readdirSync('src/assets/locales/').map((filename) => filename.replace('.json', ''));
|
||||||
|
|
||||||
if (!existsSync('src/assets/dayjsLocales')) {
|
|
||||||
mkdirSync('src/assets/dayjsLocales');
|
|
||||||
}
|
|
||||||
|
|
||||||
filenames.forEach((name) => {
|
|
||||||
// English is always directly loaded (compiled by Vite) and thus not copied
|
|
||||||
if (name === 'en') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let langName = name;
|
|
||||||
|
|
||||||
// copy dayjs language
|
|
||||||
if (name === 'zh-Hans') {
|
|
||||||
// zh-Hans is called zh in dayjs
|
|
||||||
langName = 'zh';
|
|
||||||
} else if (name === 'zh-Hant') {
|
|
||||||
// zh-Hant is called zh-cn in dayjs
|
|
||||||
langName = 'zh-cn';
|
|
||||||
}
|
|
||||||
|
|
||||||
copyFile(
|
|
||||||
`node_modules/dayjs/esm/locale/${langName}.js`,
|
|
||||||
`src/assets/dayjsLocales/${name}.js`,
|
|
||||||
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
|
||||||
(err) => {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
replaceInFileSync({
|
|
||||||
files: 'src/assets/dayjsLocales/*.js',
|
|
||||||
// remove any dayjs import and any dayjs.locale call
|
|
||||||
from: /(?:import dayjs.*'|dayjs\.locale.*);/g,
|
|
||||||
to: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'vue-i18n-supported-locales',
|
name: 'vue-i18n-supported-locales',
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue