client: Use named constants for time calculations #183

Merged
norm merged 6 commits from client-time-constants into main 2022-10-04 18:05:41 +00:00
10 changed files with 65 additions and 49 deletions

View file

@ -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>

View file

@ -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() });
}
}

View file

@ -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;
}
};

View file

@ -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;

View file

@ -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',

View file

@ -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,

View file

@ -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(() => {

View file

@ -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));

View file

@ -1,6 +1,8 @@
import { DAY, HOUR } from '@/const';
const dateTimeIntervals = {
'day': 86400000,
'hour': 3600000,
'day': DAY,
'hour': HOUR,
'ms': 1,
};

View file

@ -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 => {