client: Use named constants for time calculations (#183)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint-backend Pipeline was successful
ci/woodpecker/push/lint-client Pipeline was successful
ci/woodpecker/push/lint-foundkey-js Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint-backend Pipeline was successful
ci/woodpecker/push/lint-client Pipeline was successful
ci/woodpecker/push/lint-foundkey-js Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
Constants were borrowed from `const.ts` from the backend but also includes `WEEK`, `MONTH`, and `YEAR` constants as well. Co-authored-by: Francis Dinh <normandy@biribiri.dev> Reviewed-on: #183
This commit is contained in:
parent
717ab910ab
commit
9dddb1eb6d
10 changed files with 65 additions and 49 deletions
|
@ -4,6 +4,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import MkTime from '@/components/global/time.vue';
|
import MkTime from '@/components/global/time.vue';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
import { YEAR } from '@/const';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
birthday: Date | string;
|
birthday: Date | string;
|
||||||
|
@ -13,7 +14,7 @@ const age = $computed(() => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const birthday = (typeof props.birthday === 'string') ? new Date(props.birthday) : props.birthday;
|
const birthday = (typeof props.birthday === 'string') ? new Date(props.birthday) : props.birthday;
|
||||||
|
|
||||||
return Math.floor((now.getTime() - birthday.getTime()) / 1000 / 31536000);
|
return Math.floor((now.getTime() - birthday.getTime()) / YEAR);
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
import { onUnmounted } from 'vue';
|
import { onUnmounted } from 'vue';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
import { lang } from '@/config';
|
import { lang } from '@/config';
|
||||||
|
import { DAY, WEEK, MONTH, YEAR, HOUR, MINUTE, SECOND } from '@/const';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
time: Date | string;
|
time: Date | string;
|
||||||
|
@ -34,26 +35,26 @@ const absolute = ((): string => {
|
||||||
|
|
||||||
let now = $ref(new Date());
|
let now = $ref(new Date());
|
||||||
const relative = $computed(() => {
|
const relative = $computed(() => {
|
||||||
const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/;
|
const ago = now.getTime() - _time.getTime();
|
||||||
|
|
||||||
if (ago >= 31536000) {
|
if (ago >= YEAR) {
|
||||||
return i18n.t('_ago.yearsAgo', { n: Math.round(ago / 31536000).toString() });
|
return i18n.t('_ago.yearsAgo', { n: Math.round(ago / YEAR).toString() });
|
||||||
} else if (ago >= 2592000) {
|
} else if (ago >= MONTH) {
|
||||||
return i18n.t('_ago.monthsAgo', { n: Math.round(ago / 2592000).toString() });
|
return i18n.t('_ago.monthsAgo', { n: Math.round(ago / MONTH).toString() });
|
||||||
} else if (ago >= 604800) {
|
} else if (ago >= WEEK) {
|
||||||
return i18n.t('_ago.weeksAgo', { n: Math.round(ago / 604800).toString() });
|
return i18n.t('_ago.weeksAgo', { n: Math.round(ago / WEEK).toString() });
|
||||||
} else if (ago >= 86400) {
|
} else if (ago >= DAY) {
|
||||||
return i18n.t('_ago.daysAgo', { n: Math.round(ago / 86400).toString() });
|
return i18n.t('_ago.daysAgo', { n: Math.round(ago / DAY).toString() });
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the format is 'date', the relative date precision is no more than days ago
|
// if the format is 'date', the relative date precision is no more than days ago
|
||||||
if (props.format !== 'date') {
|
if (props.format !== 'date') {
|
||||||
if (ago >= 3600) {
|
if (ago >= HOUR) {
|
||||||
return i18n.t('_ago.hoursAgo', { n: Math.round(ago / 3600).toString() });
|
return i18n.t('_ago.hoursAgo', { n: Math.round(ago / HOUR).toString() });
|
||||||
} else if (ago >= 60) {
|
} else if (ago >= MINUTE) {
|
||||||
return i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() });
|
return i18n.t('_ago.minutesAgo', { n: (~~(ago / MINUTE)).toString() });
|
||||||
} else if (ago >= 10) {
|
} else if (ago >= 10 * SECOND) {
|
||||||
return i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() });
|
return i18n.t('_ago.secondsAgo', { n: (~~(ago % MINUTE)).toString() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ import MkButton from './ui/button.vue';
|
||||||
import { formatDateTimeString } from '@/scripts/format-time-string';
|
import { formatDateTimeString } from '@/scripts/format-time-string';
|
||||||
import { addTime } from '@/scripts/time';
|
import { addTime } from '@/scripts/time';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
import { DAY, HOUR, MINUTE, SECOND } from '@/const';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
@ -92,11 +93,11 @@ if (props.modelValue.expiresAt) {
|
||||||
expiration.value = 'infinite';
|
expiration.value = 'infinite';
|
||||||
}
|
}
|
||||||
|
|
||||||
function onInput(i, value) {
|
function onInput(i: number, value: string): void {
|
||||||
choices.value[i] = value;
|
choices.value[i] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function add() {
|
function add(): void {
|
||||||
choices.value.push('');
|
choices.value.push('');
|
||||||
// TODO
|
// TODO
|
||||||
// nextTick(() => {
|
// nextTick(() => {
|
||||||
|
@ -104,25 +105,22 @@ function add() {
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove(i) {
|
function remove(i: number): void {
|
||||||
choices.value = choices.value.filter((_, _i) => _i !== i);
|
choices.value = choices.value.filter((_, _i) => _i !== i);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get() {
|
function get() {
|
||||||
const calcAt = () => {
|
const calcAt = (): number => {
|
||||||
return new Date(`${atDate.value} ${atTime.value}`).getTime();
|
return new Date(`${atDate.value} ${atTime.value}`).getTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
const calcAfter = () => {
|
const calcAfter = (): number | null => {
|
||||||
let base = parseInt(after.value);
|
let base = parseInt(after.value);
|
||||||
switch (unit.value) {
|
switch (unit.value) {
|
||||||
case 'day': base *= 24;
|
case 'day': return base * DAY;
|
||||||
// fallthrough
|
case 'hour': return base * HOUR;
|
||||||
case 'hour': base *= 60;
|
case 'minute': return base * MINUTE;
|
||||||
// fallthrough
|
case 'second': return base * SECOND;
|
||||||
case 'minute': base *= 60;
|
|
||||||
// fallthrough
|
|
||||||
case 'second': return base *= 1000;
|
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -61,6 +61,7 @@ import * as os from '@/os';
|
||||||
import { login } from '@/account';
|
import { login } from '@/account';
|
||||||
import { instance } from '@/instance';
|
import { instance } from '@/instance';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
import { MINUTE } from '@/const';
|
||||||
|
|
||||||
let signing = $ref(false);
|
let signing = $ref(false);
|
||||||
let user = $ref(null);
|
let user = $ref(null);
|
||||||
|
@ -124,7 +125,7 @@ function queryKey() {
|
||||||
type: 'public-key',
|
type: 'public-key',
|
||||||
transports: ['usb', 'nfc', 'ble', 'internal'],
|
transports: ['usb', 'nfc', 'ble', 'internal'],
|
||||||
})),
|
})),
|
||||||
timeout: 60 * 1000,
|
timeout: MINUTE,
|
||||||
},
|
},
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
queryingKey = false;
|
queryingKey = false;
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
// ブラウザで直接表示することを許可するファイルの種類のリスト
|
// Time constants
|
||||||
// ここに含まれないものは application/octet-stream としてレスポンスされる
|
export const SECOND = 1000;
|
||||||
// SVGはXSSを生むので許可しない
|
export const MINUTE = 60 * SECOND;
|
||||||
|
export const HOUR = 60 * MINUTE;
|
||||||
|
export const DAY = 24 * HOUR;
|
||||||
|
export const WEEK = 7 * DAY;
|
||||||
|
export const MONTH = 30 * DAY;
|
||||||
|
export const YEAR = 365 * DAY;
|
||||||
|
|
||||||
|
// List of file types allowed to be viewed directly in the browser.
|
||||||
|
// Anything not included here will be reported as application/octet-stream
|
||||||
|
// SVG is not allowed because it can lead to XSS
|
||||||
export const FILE_TYPE_BROWSERSAFE = [
|
export const FILE_TYPE_BROWSERSAFE = [
|
||||||
// Images
|
// Images
|
||||||
'image/png',
|
'image/png',
|
||||||
|
|
|
@ -75,6 +75,7 @@ import MkSwitch from '@/components/form/switch.vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { $i } from '@/account';
|
import { $i } from '@/account';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
import { MINUTE } from '@/const';
|
||||||
|
|
||||||
const twoFactorData = ref<any>(null);
|
const twoFactorData = ref<any>(null);
|
||||||
const supportsCredentials = ref(!!navigator.credentials);
|
const supportsCredentials = ref(!!navigator.credentials);
|
||||||
|
@ -188,7 +189,7 @@ function addSecurityKey() {
|
||||||
displayName: $i!.name,
|
displayName: $i!.name,
|
||||||
},
|
},
|
||||||
pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
|
pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
|
||||||
timeout: 60000,
|
timeout: MINUTE,
|
||||||
attestation: 'direct',
|
attestation: 'direct',
|
||||||
},
|
},
|
||||||
saving: true,
|
saving: true,
|
||||||
|
|
|
@ -7,11 +7,12 @@ import * as os from '@/os';
|
||||||
import { userActions } from '@/store';
|
import { userActions } from '@/store';
|
||||||
import { $i, iAmModerator } from '@/account';
|
import { $i, iAmModerator } from '@/account';
|
||||||
import { mainRouter } from '@/router';
|
import { mainRouter } from '@/router';
|
||||||
|
import { DAY, HOUR, MINUTE, WEEK } from '@/const';
|
||||||
|
|
||||||
export function getUserMenu(user) {
|
export function getUserMenu(user) {
|
||||||
const meId = $i ? $i.id : null;
|
const meId = $i ? $i.id : null;
|
||||||
|
|
||||||
async function pushList() {
|
async function pushList(): Promise<void> {
|
||||||
const t = i18n.ts.selectList; // なぜか後で参照すると null になるので最初にメモリに確保しておく
|
const t = i18n.ts.selectList; // なぜか後で参照すると null になるので最初にメモリに確保しておく
|
||||||
const lists = await os.api('users/lists/list');
|
const lists = await os.api('users/lists/list');
|
||||||
if (lists.length === 0) {
|
if (lists.length === 0) {
|
||||||
|
@ -34,7 +35,7 @@ export function getUserMenu(user) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function inviteGroup() {
|
async function inviteGroup(): Promise<void> {
|
||||||
const groups = await os.api('users/groups/owned');
|
const groups = await os.api('users/groups/owned');
|
||||||
if (groups.length === 0) {
|
if (groups.length === 0) {
|
||||||
os.alert({
|
os.alert({
|
||||||
|
@ -56,7 +57,7 @@ export function getUserMenu(user) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleMute() {
|
async function toggleMute(): Promise<void> {
|
||||||
if (user.isMuted) {
|
if (user.isMuted) {
|
||||||
os.apiWithDialog('mute/delete', {
|
os.apiWithDialog('mute/delete', {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
@ -82,10 +83,10 @@ export function getUserMenu(user) {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
const expiresAt = period === 'indefinitely' ? null
|
const expiresAt = period === 'indefinitely' ? null
|
||||||
: period === 'tenMinutes' ? Date.now() + (1000 * 60 * 10)
|
: period === 'tenMinutes' ? Date.now() + (MINUTE * 10)
|
||||||
: period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
|
: period === 'oneHour' ? Date.now() + HOUR
|
||||||
: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
|
: period === 'oneDay' ? Date.now() + DAY
|
||||||
: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
|
: period === 'oneWeek' ? Date.now() + WEEK
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
os.apiWithDialog('mute/create', {
|
os.apiWithDialog('mute/create', {
|
||||||
|
@ -97,7 +98,7 @@ export function getUserMenu(user) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleBlock() {
|
async function toggleBlock(): Promise<void> {
|
||||||
if (!await getConfirmed(user.isBlocking ? i18n.ts.unblockConfirm : i18n.ts.blockConfirm)) return;
|
if (!await getConfirmed(user.isBlocking ? i18n.ts.unblockConfirm : i18n.ts.blockConfirm)) return;
|
||||||
|
|
||||||
os.apiWithDialog(user.isBlocking ? 'blocking/delete' : 'blocking/create', {
|
os.apiWithDialog(user.isBlocking ? 'blocking/delete' : 'blocking/create', {
|
||||||
|
@ -107,7 +108,7 @@ export function getUserMenu(user) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleSilence() {
|
async function toggleSilence(): Promise<void> {
|
||||||
if (!await getConfirmed(i18n.t(user.isSilenced ? 'unsilenceConfirm' : 'silenceConfirm'))) return;
|
if (!await getConfirmed(i18n.t(user.isSilenced ? 'unsilenceConfirm' : 'silenceConfirm'))) return;
|
||||||
|
|
||||||
os.apiWithDialog(user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
|
os.apiWithDialog(user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
|
||||||
|
@ -117,7 +118,7 @@ export function getUserMenu(user) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleSuspend() {
|
async function toggleSuspend(): Promise<void> {
|
||||||
if (!await getConfirmed(i18n.t(user.isSuspended ? 'unsuspendConfirm' : 'suspendConfirm'))) return;
|
if (!await getConfirmed(i18n.t(user.isSuspended ? 'unsuspendConfirm' : 'suspendConfirm'))) return;
|
||||||
|
|
||||||
os.apiWithDialog(user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
|
os.apiWithDialog(user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
|
||||||
|
@ -127,7 +128,7 @@ export function getUserMenu(user) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function reportAbuse() {
|
function reportAbuse(): void {
|
||||||
os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), {
|
os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), {
|
||||||
user,
|
user,
|
||||||
}, {}, 'closed');
|
}, {}, 'closed');
|
||||||
|
@ -143,7 +144,7 @@ export function getUserMenu(user) {
|
||||||
return !confirm.canceled;
|
return !confirm.canceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function invalidateFollow() {
|
async function invalidateFollow(): Promise<void> {
|
||||||
os.apiWithDialog('following/invalidate', {
|
os.apiWithDialog('following/invalidate', {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|
|
@ -13,6 +13,7 @@ export type Theme = {
|
||||||
|
|
||||||
import lightTheme from '@/themes/_light.json5';
|
import lightTheme from '@/themes/_light.json5';
|
||||||
import darkTheme from '@/themes/_dark.json5';
|
import darkTheme from '@/themes/_dark.json5';
|
||||||
|
import { SECOND } from '@/const';
|
||||||
|
|
||||||
export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X'));
|
export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X'));
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ export function applyTheme(theme: Theme, persist = true) {
|
||||||
|
|
||||||
timeout = window.setTimeout(() => {
|
timeout = window.setTimeout(() => {
|
||||||
document.documentElement.classList.remove('_themeChanging_');
|
document.documentElement.classList.remove('_themeChanging_');
|
||||||
}, 1000);
|
}, SECOND);
|
||||||
|
|
||||||
// Deep copy
|
// Deep copy
|
||||||
const _theme = JSON.parse(JSON.stringify(theme));
|
const _theme = JSON.parse(JSON.stringify(theme));
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import { DAY, HOUR } from '@/const';
|
||||||
|
|
||||||
const dateTimeIntervals = {
|
const dateTimeIntervals = {
|
||||||
'day': 86400000,
|
'day': DAY,
|
||||||
'hour': 3600000,
|
'hour': HOUR,
|
||||||
'ms': 1,
|
'ms': 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { GetFormResultType } from '@/scripts/form';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
import { defaultStore } from '@/store';
|
import { defaultStore } from '@/store';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
import { SECOND } from '@/const';
|
||||||
|
|
||||||
const name = 'memo';
|
const name = 'memo';
|
||||||
|
|
||||||
|
@ -54,7 +55,7 @@ const saveMemo = (): void => {
|
||||||
const onChange = (): void => {
|
const onChange = (): void => {
|
||||||
changed.value = true;
|
changed.value = true;
|
||||||
window.clearTimeout(timeoutId);
|
window.clearTimeout(timeoutId);
|
||||||
timeoutId = window.setTimeout(saveMemo, 1000);
|
timeoutId = window.setTimeout(saveMemo, SECOND);
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(() => defaultStore.reactiveState.memo, newText => {
|
watch(() => defaultStore.reactiveState.memo, newText => {
|
||||||
|
|
Loading…
Reference in a new issue