mirror of
https://github.com/MisskeyIO/misskey
synced 2024-11-30 15:58:16 +09:00
enhance(frontend): Input の変更から API を叩く箇所の debounce と AbortController の追加 (MisskeyIO#722)
- MkSignin のログインで、user が取得できるまでログインがクリックできないようにした - パスワードマネージャの自動ログインが動かなくなるが、元々動作していなかったため問題ないと思われる
This commit is contained in:
parent
47b6b97c9c
commit
970ad7c81e
@ -46,6 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
|
import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
|
||||||
import sanitizeHtml from 'sanitize-html';
|
import sanitizeHtml from 'sanitize-html';
|
||||||
|
import { debounce } from 'throttle-debounce';
|
||||||
import contains from '@/scripts/contains.js';
|
import contains from '@/scripts/contains.js';
|
||||||
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js';
|
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js';
|
||||||
import { acct } from '@/filters/user.js';
|
import { acct } from '@/filters/user.js';
|
||||||
@ -189,6 +190,7 @@ const mfmTags = ref<string[]>([]);
|
|||||||
const mfmParams = ref<string[]>([]);
|
const mfmParams = ref<string[]>([]);
|
||||||
const select = ref(-1);
|
const select = ref(-1);
|
||||||
const zIndex = os.claimZIndex('high');
|
const zIndex = os.claimZIndex('high');
|
||||||
|
const abortController = ref<AbortController>();
|
||||||
|
|
||||||
function complete<T extends keyof CompleteInfo>(type: T, value: CompleteInfo[T]['payload']) {
|
function complete<T extends keyof CompleteInfo>(type: T, value: CompleteInfo[T]['payload']) {
|
||||||
emit('done', { type, value });
|
emit('done', { type, value });
|
||||||
@ -217,6 +219,39 @@ function setPosition() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const searchUsers = debounce(1000, (query: string, cacheKey: string) => {
|
||||||
|
if (abortController.value) {
|
||||||
|
abortController.value.abort();
|
||||||
|
}
|
||||||
|
abortController.value = new AbortController();
|
||||||
|
misskeyApi('users/search-by-username-and-host', {
|
||||||
|
username: query,
|
||||||
|
limit: 10,
|
||||||
|
detail: false,
|
||||||
|
}, undefined, abortController.value.signal).then(searchedUsers => {
|
||||||
|
users.value = searchedUsers as any[];
|
||||||
|
fetching.value = false;
|
||||||
|
// キャッシュ
|
||||||
|
sessionStorage.setItem(cacheKey, JSON.stringify(searchedUsers));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchHashtags = debounce(1000, (query: string, cacheKey: string) => {
|
||||||
|
if (abortController.value) {
|
||||||
|
abortController.value.abort();
|
||||||
|
}
|
||||||
|
abortController.value = new AbortController();
|
||||||
|
misskeyApi('hashtags/search', {
|
||||||
|
query,
|
||||||
|
limit: 30,
|
||||||
|
}, undefined, abortController.value.signal).then(searchedHashtags => {
|
||||||
|
hashtags.value = searchedHashtags as any[];
|
||||||
|
fetching.value = false;
|
||||||
|
// キャッシュ
|
||||||
|
sessionStorage.setItem(cacheKey, JSON.stringify(searchedHashtags));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function exec() {
|
function exec() {
|
||||||
select.value = -1;
|
select.value = -1;
|
||||||
if (suggests.value) {
|
if (suggests.value) {
|
||||||
@ -238,16 +273,7 @@ function exec() {
|
|||||||
users.value = JSON.parse(cache);
|
users.value = JSON.parse(cache);
|
||||||
fetching.value = false;
|
fetching.value = false;
|
||||||
} else {
|
} else {
|
||||||
misskeyApi('users/search-by-username-and-host', {
|
searchUsers(props.q, cacheKey);
|
||||||
username: props.q,
|
|
||||||
limit: 10,
|
|
||||||
detail: false,
|
|
||||||
}).then(searchedUsers => {
|
|
||||||
users.value = searchedUsers as any[];
|
|
||||||
fetching.value = false;
|
|
||||||
// キャッシュ
|
|
||||||
sessionStorage.setItem(cacheKey, JSON.stringify(searchedUsers));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (props.type === 'hashtag') {
|
} else if (props.type === 'hashtag') {
|
||||||
if (!props.q || props.q === '') {
|
if (!props.q || props.q === '') {
|
||||||
@ -261,15 +287,7 @@ function exec() {
|
|||||||
hashtags.value = hashtags;
|
hashtags.value = hashtags;
|
||||||
fetching.value = false;
|
fetching.value = false;
|
||||||
} else {
|
} else {
|
||||||
misskeyApi('hashtags/search', {
|
searchHashtags(props.q, cacheKey);
|
||||||
query: props.q,
|
|
||||||
limit: 30,
|
|
||||||
}).then(searchedHashtags => {
|
|
||||||
hashtags.value = searchedHashtags as any[];
|
|
||||||
fetching.value = false;
|
|
||||||
// キャッシュ
|
|
||||||
sessionStorage.setItem(cacheKey, JSON.stringify(searchedHashtags));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (props.type === 'emoji') {
|
} else if (props.type === 'emoji') {
|
||||||
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
{{ message }}
|
{{ message }}
|
||||||
</MkInfo>
|
</MkInfo>
|
||||||
<div v-if="!totpLogin" class="normal-signin _gaps_m">
|
<div v-if="!totpLogin" class="normal-signin _gaps_m">
|
||||||
<MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username webauthn" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
|
<MkInput v-model="username" :debounce="true" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username webauthn" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
|
||||||
<template #prefix>@</template>
|
<template #prefix>@</template>
|
||||||
<template #suffix>@{{ host }}</template>
|
<template #suffix>@{{ host }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #prefix><i class="ti ti-lock"></i></template>
|
<template #prefix><i class="ti ti-lock"></i></template>
|
||||||
<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
|
<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkButton type="submit" large primary rounded :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
|
<MkButton type="submit" large primary rounded :disabled="!user || signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
|
<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
|
||||||
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
|
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
|
||||||
@ -64,6 +64,7 @@ import { login } from '@/account.js';
|
|||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const signing = ref(false);
|
const signing = ref(false);
|
||||||
|
const userAbortController = ref<AbortController>();
|
||||||
const user = ref<Misskey.entities.UserDetailed | null>(null);
|
const user = ref<Misskey.entities.UserDetailed | null>(null);
|
||||||
const username = ref('');
|
const username = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
@ -97,9 +98,13 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function onUsernameChange(): void {
|
function onUsernameChange(): void {
|
||||||
|
if (userAbortController.value) {
|
||||||
|
userAbortController.value.abort();
|
||||||
|
}
|
||||||
|
userAbortController.value = new AbortController();
|
||||||
misskeyApi('users/show', {
|
misskeyApi('users/show', {
|
||||||
username: username.value,
|
username: username.value,
|
||||||
}).then(userResponse => {
|
}, undefined, userAbortController.value.signal).then(userResponse => {
|
||||||
user.value = userResponse;
|
user.value = userResponse;
|
||||||
}, () => {
|
}, () => {
|
||||||
user.value = null;
|
user.value = null;
|
||||||
|
@ -16,16 +16,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #header>{{ i18n.ts.selectUser }}</template>
|
<template #header>{{ i18n.ts.selectUser }}</template>
|
||||||
<div>
|
<div>
|
||||||
<div :class="$style.form">
|
<div :class="$style.form">
|
||||||
<MkInput v-if="localOnly" v-model="username" :autofocus="true" @update:modelValue="search">
|
<MkInput v-if="localOnly" v-model="username" :debounce="true" :autofocus="true" @update:modelValue="search">
|
||||||
<template #label>{{ i18n.ts.username }}</template>
|
<template #label>{{ i18n.ts.username }}</template>
|
||||||
<template #prefix>@</template>
|
<template #prefix>@</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<FormSplit v-else :minWidth="170">
|
<FormSplit v-else :minWidth="170">
|
||||||
<MkInput v-model="username" :autofocus="true" @update:modelValue="search">
|
<MkInput v-model="username" :debounce="true" :autofocus="true" @update:modelValue="search">
|
||||||
<template #label>{{ i18n.ts.username }}</template>
|
<template #label>{{ i18n.ts.username }}</template>
|
||||||
<template #prefix>@</template>
|
<template #prefix>@</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="host" :datalist="[hostname]" @update:modelValue="search">
|
<MkInput v-model="host" :debounce="true" :datalist="[hostname]" @update:modelValue="search">
|
||||||
<template #label>{{ i18n.ts.host }}</template>
|
<template #label>{{ i18n.ts.host }}</template>
|
||||||
<template #prefix>@</template>
|
<template #prefix>@</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
@ -92,18 +92,23 @@ const users = ref<Misskey.entities.UserLite[]>([]);
|
|||||||
const recentUsers = ref<Misskey.entities.UserDetailed[]>([]);
|
const recentUsers = ref<Misskey.entities.UserDetailed[]>([]);
|
||||||
const selected = ref<Misskey.entities.UserLite | null>(null);
|
const selected = ref<Misskey.entities.UserLite | null>(null);
|
||||||
const dialogEl = ref();
|
const dialogEl = ref();
|
||||||
|
const abortController = ref<AbortController>();
|
||||||
|
|
||||||
function search() {
|
function search() {
|
||||||
|
if (abortController.value) {
|
||||||
|
abortController.value.abort();
|
||||||
|
}
|
||||||
if (username.value === '' && host.value === '') {
|
if (username.value === '' && host.value === '') {
|
||||||
users.value = [];
|
users.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
abortController.value = new AbortController();
|
||||||
misskeyApi('users/search-by-username-and-host', {
|
misskeyApi('users/search-by-username-and-host', {
|
||||||
username: username.value,
|
username: username.value,
|
||||||
host: props.localOnly ? '.' : host.value,
|
host: props.localOnly ? '.' : host.value,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
detail: false,
|
detail: false,
|
||||||
}).then(_users => {
|
}, undefined, abortController.value.signal).then(_users => {
|
||||||
users.value = _users.filter((u) => {
|
users.value = _users.filter((u) => {
|
||||||
if (props.includeSelf) {
|
if (props.includeSelf) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSpacer :contentMax="700">
|
<MkSpacer :contentMax="700">
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkInput v-model="endpoint" :datalist="endpoints" @update:modelValue="onEndpointChange()">
|
<MkInput v-model="endpoint" :debounce="true" :datalist="endpoints" @update:modelValue="onEndpointChange()">
|
||||||
<template #label>Endpoint</template>
|
<template #label>Endpoint</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkTextarea v-model="body" code>
|
<MkTextarea v-model="body" code>
|
||||||
@ -50,6 +50,7 @@ const endpoints = ref<string[]>([]);
|
|||||||
const sending = ref(false);
|
const sending = ref(false);
|
||||||
const res = ref('');
|
const res = ref('');
|
||||||
const withCredential = ref(true);
|
const withCredential = ref(true);
|
||||||
|
const endpointAbortController = ref<AbortController>();
|
||||||
|
|
||||||
misskeyApi('endpoints').then(endpointResponse => {
|
misskeyApi('endpoints').then(endpointResponse => {
|
||||||
endpoints.value = endpointResponse;
|
endpoints.value = endpointResponse;
|
||||||
@ -68,7 +69,11 @@ function send() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onEndpointChange() {
|
function onEndpointChange() {
|
||||||
misskeyApi('endpoint', { endpoint: endpoint.value }, withCredential.value ? undefined : null).then(resp => {
|
if (endpointAbortController.value) {
|
||||||
|
endpointAbortController.value.abort();
|
||||||
|
}
|
||||||
|
endpointAbortController.value = new AbortController();
|
||||||
|
misskeyApi('endpoint', { endpoint: endpoint.value }, withCredential.value ? undefined : null, endpointAbortController.value.signal).then(resp => {
|
||||||
const endpointBody = {};
|
const endpointBody = {};
|
||||||
for (const p of resp.params) {
|
for (const p of resp.params) {
|
||||||
endpointBody[p.name] =
|
endpointBody[p.name] =
|
||||||
|
Loading…
Reference in New Issue
Block a user