enhance(frontend/2fa): 二要素認証のバックアップコードを保存するように促す (MisskeyIO#611)

This commit is contained in:
まっちゃとーにゅ 2024-04-14 11:38:00 +09:00 committed by GitHub
parent 7da775df10
commit 1f38f58117
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 100 additions and 33 deletions

View file

@ -48,6 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
</template>
</MkSelect>
<MkSwitch v-if="switchLabel" v-model="switchValue" style="display: flex; margin: 1em 0; justify-content: center;">{{ switchLabel }}</MkSwitch>
<details v-if="details" class="_acrylic" style="margin: 1em 0;">
<summary>{{ i18n.ts.details }}</summary>
<div class="_gaps_s" style="text-align: initial;">
@ -58,7 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</details>
<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okDisabled || okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}<span v-if="okDisabled && okWaitInitiated"> ({{ sec }})</span></MkButton>
<MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
</div>
<div v-if="actions" :class="$style.buttons">
@ -69,11 +70,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from 'vue';
import { onBeforeUnmount, onMounted, ref, shallowRef, computed, watch } from 'vue';
import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import { i18n } from '@/i18n.js';
@ -104,6 +106,7 @@ const props = withDefaults(defineProps<{
text?: string | null;
input?: Input;
select?: Select;
switchLabel?: string | null;
details?: Record<string, string>;
actions?: {
text: string;
@ -115,6 +118,8 @@ const props = withDefaults(defineProps<{
showCancelButton?: boolean;
cancelableByBgClick?: boolean;
okText?: string;
okWaitInitiate?: 'dialog' | 'input' | 'switch';
okWaitDuration?: number;
cancelText?: string;
}>(), {
type: 'info',
@ -123,17 +128,20 @@ const props = withDefaults(defineProps<{
text: undefined,
input: undefined,
select: undefined,
switchLabel: undefined,
details: undefined,
actions: undefined,
showOkButton: true,
showCancelButton: false,
cancelableByBgClick: true,
okText: undefined,
okWaitInitiate: undefined,
okWaitDuration: 0,
cancelText: undefined,
});
const emit = defineEmits<{
(ev: 'done', v: { canceled: true } | { canceled: false, result: Result }): void;
(ev: 'done', v: { canceled: true } | { canceled: false, result: Result, toggle: boolean }): void;
(ev: 'closed'): void;
}>();
@ -141,6 +149,16 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
const inputValue = ref<string | number | null>(props.input?.default ?? null);
const selectedValue = ref(props.select?.default ?? null);
const switchValue = ref<boolean>(false);
const sec = ref(props.okWaitDuration);
const okWaitInitiated = computed(() => {
if (props.okWaitInitiate === 'dialog') return true;
if (props.okWaitInitiate === 'input') return inputValue.value !== null;
if (props.okWaitInitiate === 'switch') return switchValue.value;
return false;
});
const okDisabled = computed(() => sec.value > 0);
const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'charactersBelow'>(() => {
if (props.input) {
@ -161,9 +179,9 @@ const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'character
// overload function 使 lint
function done(canceled: true): void;
function done(canceled: false, result: Result): void; // eslint-disable-line no-redeclare
function done(canceled: boolean, result?: Result): void { // eslint-disable-line no-redeclare
emit('done', { canceled, result } as { canceled: true } | { canceled: false, result: Result });
function done(canceled: false, result: Result, toggle: boolean): void; // eslint-disable-line no-redeclare
function done(canceled: boolean, result?: Result, toggle?: boolean ): void { // eslint-disable-line no-redeclare
emit('done', { canceled, result, toggle } as { canceled: true } | { canceled: false, result: Result, toggle: boolean });
modal.value?.close();
}
@ -174,18 +192,13 @@ async function ok() {
props.input ? inputValue.value :
props.select ? selectedValue.value :
true;
done(false, result);
done(false, result, switchValue.value);
}
function cancel() {
done(true);
}
/*
function onBgClick() {
if (props.cancelableByBgClick) cancel();
}
*/
function onKeydown(evt: KeyboardEvent) {
if (evt.key === 'Escape') cancel();
}
@ -198,8 +211,24 @@ function onInputKeydown(evt: KeyboardEvent) {
}
}
watch(okWaitInitiated, () => {
sec.value = props.okWaitDuration;
});
onMounted(() => {
document.addEventListener('keydown', onKeydown);
sec.value = props.okWaitDuration;
if (sec.value > 0) {
const waitTimer = setInterval(() => {
if (!okWaitInitiated.value) return;
if (sec.value < 0) {
clearInterval(waitTimer);
}
sec.value = sec.value - 1;
}, 1000);
}
});
onBeforeUnmount(() => {