forked from FoundKeyGang/FoundKey
client: Use named constants for time calculations (#183)
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: FoundKeyGang/FoundKey#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>
|
||||
import MkTime from '@/components/global/time.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { YEAR } from '@/const';
|
||||
|
||||
const props = defineProps<{
|
||||
birthday: Date | string;
|
||||
|
@ -13,7 +14,7 @@ const age = $computed(() => {
|
|||
const now = new Date();
|
||||
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>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import { onUnmounted } from 'vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { lang } from '@/config';
|
||||
import { DAY, WEEK, MONTH, YEAR, HOUR, MINUTE, SECOND } from '@/const';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
time: Date | string;
|
||||
|
@ -34,26 +35,26 @@ const absolute = ((): string => {
|
|||
|
||||
let now = $ref(new Date());
|
||||
const relative = $computed(() => {
|
||||
const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/;
|
||||
const ago = now.getTime() - _time.getTime();
|
||||
|
||||
if (ago >= 31536000) {
|
||||
return i18n.t('_ago.yearsAgo', { n: Math.round(ago / 31536000).toString() });
|
||||
} else if (ago >= 2592000) {
|
||||
return i18n.t('_ago.monthsAgo', { n: Math.round(ago / 2592000).toString() });
|
||||
} else if (ago >= 604800) {
|
||||
return i18n.t('_ago.weeksAgo', { n: Math.round(ago / 604800).toString() });
|
||||
} else if (ago >= 86400) {
|
||||
return i18n.t('_ago.daysAgo', { n: Math.round(ago / 86400).toString() });
|
||||
if (ago >= YEAR) {
|
||||
return i18n.t('_ago.yearsAgo', { n: Math.round(ago / YEAR).toString() });
|
||||
} else if (ago >= MONTH) {
|
||||
return i18n.t('_ago.monthsAgo', { n: Math.round(ago / MONTH).toString() });
|
||||
} else if (ago >= WEEK) {
|
||||
return i18n.t('_ago.weeksAgo', { n: Math.round(ago / WEEK).toString() });
|
||||
} else if (ago >= DAY) {
|
||||
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 (props.format !== 'date') {
|
||||
if (ago >= 3600) {
|
||||
return i18n.t('_ago.hoursAgo', { n: Math.round(ago / 3600).toString() });
|
||||
} else if (ago >= 60) {
|
||||
return i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() });
|
||||
} else if (ago >= 10) {
|
||||
return i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() });
|
||||
if (ago >= HOUR) {
|
||||
return i18n.t('_ago.hoursAgo', { n: Math.round(ago / HOUR).toString() });
|
||||
} else if (ago >= MINUTE) {
|
||||
return i18n.t('_ago.minutesAgo', { n: (~~(ago / MINUTE)).toString() });
|
||||
} else if (ago >= 10 * SECOND) {
|
||||
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 { addTime } from '@/scripts/time';
|
||||
import { i18n } from '@/i18n';
|
||||
import { DAY, HOUR, MINUTE, SECOND } from '@/const';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: {
|
||||
|
@ -92,11 +93,11 @@ if (props.modelValue.expiresAt) {
|
|||
expiration.value = 'infinite';
|
||||
}
|
||||
|
||||
function onInput(i, value) {
|
||||
function onInput(i: number, value: string): void {
|
||||
choices.value[i] = value;
|
||||
}
|
||||
|
||||
function add() {
|
||||
function add(): void {
|
||||
choices.value.push('');
|
||||
// TODO
|
||||
// nextTick(() => {
|
||||
|
@ -104,25 +105,22 @@ function add() {
|
|||
// });
|
||||
}
|
||||
|
||||
function remove(i) {
|
||||
function remove(i: number): void {
|
||||
choices.value = choices.value.filter((_, _i) => _i !== i);
|
||||
}
|
||||
|
||||
function get() {
|
||||
const calcAt = () => {
|
||||
const calcAt = (): number => {
|
||||
return new Date(`${atDate.value} ${atTime.value}`).getTime();
|
||||
};
|
||||
|
||||
const calcAfter = () => {
|
||||
const calcAfter = (): number | null => {
|
||||
let base = parseInt(after.value);
|
||||
switch (unit.value) {
|
||||
case 'day': base *= 24;
|
||||
// fallthrough
|
||||
case 'hour': base *= 60;
|
||||
// fallthrough
|
||||
case 'minute': base *= 60;
|
||||
// fallthrough
|
||||
case 'second': return base *= 1000;
|
||||
case 'day': return base * DAY;
|
||||
case 'hour': return base * HOUR;
|
||||
case 'minute': return base * MINUTE;
|
||||
case 'second': return base * SECOND;
|
||||
default: return null;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -61,6 +61,7 @@ import * as os from '@/os';
|
|||
import { login } from '@/account';
|
||||
import { instance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
import { MINUTE } from '@/const';
|
||||
|
||||
let signing = $ref(false);
|
||||
let user = $ref(null);
|
||||
|
@ -124,7 +125,7 @@ function queryKey() {
|
|||
type: 'public-key',
|
||||
transports: ['usb', 'nfc', 'ble', 'internal'],
|
||||
})),
|
||||
timeout: 60 * 1000,
|
||||
timeout: MINUTE,
|
||||
},
|
||||
}).catch(() => {
|
||||
queryingKey = false;
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
// ブラウザで直接表示することを許可するファイルの種類のリスト
|
||||
// ここに含まれないものは application/octet-stream としてレスポンスされる
|
||||
// SVGはXSSを生むので許可しない
|
||||
// Time constants
|
||||
export const SECOND = 1000;
|
||||
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 = [
|
||||
// Images
|
||||
'image/png',
|
||||
|
|
|
@ -75,6 +75,7 @@ import MkSwitch from '@/components/form/switch.vue';
|
|||
import * as os from '@/os';
|
||||
import { $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
import { MINUTE } from '@/const';
|
||||
|
||||
const twoFactorData = ref<any>(null);
|
||||
const supportsCredentials = ref(!!navigator.credentials);
|
||||
|
@ -188,7 +189,7 @@ function addSecurityKey() {
|
|||
displayName: $i!.name,
|
||||
},
|
||||
pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
|
||||
timeout: 60000,
|
||||
timeout: MINUTE,
|
||||
attestation: 'direct',
|
||||
},
|
||||
saving: true,
|
||||
|
|
|
@ -7,11 +7,12 @@ import * as os from '@/os';
|
|||
import { userActions } from '@/store';
|
||||
import { $i, iAmModerator } from '@/account';
|
||||
import { mainRouter } from '@/router';
|
||||
import { DAY, HOUR, MINUTE, WEEK } from '@/const';
|
||||
|
||||
export function getUserMenu(user) {
|
||||
const meId = $i ? $i.id : null;
|
||||
|
||||
async function pushList() {
|
||||
async function pushList(): Promise<void> {
|
||||
const t = i18n.ts.selectList; // なぜか後で参照すると null になるので最初にメモリに確保しておく
|
||||
const lists = await os.api('users/lists/list');
|
||||
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');
|
||||
if (groups.length === 0) {
|
||||
os.alert({
|
||||
|
@ -56,7 +57,7 @@ export function getUserMenu(user) {
|
|||
});
|
||||
}
|
||||
|
||||
async function toggleMute() {
|
||||
async function toggleMute(): Promise<void> {
|
||||
if (user.isMuted) {
|
||||
os.apiWithDialog('mute/delete', {
|
||||
userId: user.id,
|
||||
|
@ -82,10 +83,10 @@ export function getUserMenu(user) {
|
|||
if (canceled) return;
|
||||
|
||||
const expiresAt = period === 'indefinitely' ? null
|
||||
: period === 'tenMinutes' ? Date.now() + (1000 * 60 * 10)
|
||||
: period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
|
||||
: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
|
||||
: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
|
||||
: period === 'tenMinutes' ? Date.now() + (MINUTE * 10)
|
||||
: period === 'oneHour' ? Date.now() + HOUR
|
||||
: period === 'oneDay' ? Date.now() + DAY
|
||||
: period === 'oneWeek' ? Date.now() + WEEK
|
||||
: null;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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')), {
|
||||
user,
|
||||
}, {}, 'closed');
|
||||
|
@ -143,7 +144,7 @@ export function getUserMenu(user) {
|
|||
return !confirm.canceled;
|
||||
}
|
||||
|
||||
async function invalidateFollow() {
|
||||
async function invalidateFollow(): Promise<void> {
|
||||
os.apiWithDialog('following/invalidate', {
|
||||
userId: user.id,
|
||||
}).then(() => {
|
||||
|
|
|
@ -13,6 +13,7 @@ export type Theme = {
|
|||
|
||||
import lightTheme from '@/themes/_light.json5';
|
||||
import darkTheme from '@/themes/_dark.json5';
|
||||
import { SECOND } from '@/const';
|
||||
|
||||
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(() => {
|
||||
document.documentElement.classList.remove('_themeChanging_');
|
||||
}, 1000);
|
||||
}, SECOND);
|
||||
|
||||
// Deep copy
|
||||
const _theme = JSON.parse(JSON.stringify(theme));
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { DAY, HOUR } from '@/const';
|
||||
|
||||
const dateTimeIntervals = {
|
||||
'day': 86400000,
|
||||
'hour': 3600000,
|
||||
'day': DAY,
|
||||
'hour': HOUR,
|
||||
'ms': 1,
|
||||
};
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import { GetFormResultType } from '@/scripts/form';
|
|||
import MkContainer from '@/components/ui/container.vue';
|
||||
import { defaultStore } from '@/store';
|
||||
import { i18n } from '@/i18n';
|
||||
import { SECOND } from '@/const';
|
||||
|
||||
const name = 'memo';
|
||||
|
||||
|
@ -54,7 +55,7 @@ const saveMemo = (): void => {
|
|||
const onChange = (): void => {
|
||||
changed.value = true;
|
||||
window.clearTimeout(timeoutId);
|
||||
timeoutId = window.setTimeout(saveMemo, 1000);
|
||||
timeoutId = window.setTimeout(saveMemo, SECOND);
|
||||
};
|
||||
|
||||
watch(() => defaultStore.reactiveState.memo, newText => {
|
||||
|
|
Loading…
Reference in a new issue