Merge branch 'develop' into fix-passwordless
This commit is contained in:
commit
373d2aeb04
40 changed files with 849 additions and 165 deletions
BIN
packages/frontend/assets/testcaptcha.png
Normal file
BIN
packages/frontend/assets/testcaptcha.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -10,6 +10,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div id="mcaptcha__widget-container" class="m-captcha-style"></div>
|
||||
<div ref="captchaEl"></div>
|
||||
</div>
|
||||
<div v-if="props.provider == 'testcaptcha'" style="background: #eee; border: solid 1px #888; padding: 8px; color: #000; max-width: 320px; display: flex; gap: 10px; align-items: center; box-shadow: 2px 2px 6px #0004; border-radius: 4px;">
|
||||
<img src="/client-assets/testcaptcha.png" style="width: 60px; height: 60px; "/>
|
||||
<div v-if="testcaptchaPassed">
|
||||
<div style="color: green;">Test captcha passed!</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div style="font-size: 13px; margin-bottom: 4px;">Type "ai-chan-kawaii" to pass captcha</div>
|
||||
<input v-model="testcaptchaInput" data-cy-testcaptcha-input/>
|
||||
<button type="button" data-cy-testcaptcha-submit @click="testcaptchaSubmit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else ref="captchaEl"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -29,7 +40,7 @@ export type Captcha = {
|
|||
getResponse(id: string): string;
|
||||
};
|
||||
|
||||
export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha';
|
||||
export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha' | 'testcaptcha';
|
||||
|
||||
type CaptchaContainer = {
|
||||
readonly [_ in CaptchaProvider]?: Captcha;
|
||||
|
|
@ -54,12 +65,16 @@ const available = ref(false);
|
|||
|
||||
const captchaEl = shallowRef<HTMLDivElement | undefined>();
|
||||
|
||||
const testcaptchaInput = ref('');
|
||||
const testcaptchaPassed = ref(false);
|
||||
|
||||
const variable = computed(() => {
|
||||
switch (props.provider) {
|
||||
case 'hcaptcha': return 'hcaptcha';
|
||||
case 'recaptcha': return 'grecaptcha';
|
||||
case 'turnstile': return 'turnstile';
|
||||
case 'mcaptcha': return 'mcaptcha';
|
||||
case 'testcaptcha': return 'testcaptcha';
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -71,6 +86,7 @@ const src = computed(() => {
|
|||
case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit';
|
||||
case 'turnstile': return 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
|
||||
case 'mcaptcha': return null;
|
||||
case 'testcaptcha': return null;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -78,7 +94,7 @@ const scriptId = computed(() => `script-${props.provider}`);
|
|||
|
||||
const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha);
|
||||
|
||||
if (loaded || props.provider === 'mcaptcha') {
|
||||
if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') {
|
||||
available.value = true;
|
||||
} else if (src.value !== null) {
|
||||
(document.getElementById(scriptId.value) ?? document.head.appendChild(Object.assign(document.createElement('script'), {
|
||||
|
|
@ -91,6 +107,8 @@ if (loaded || props.provider === 'mcaptcha') {
|
|||
|
||||
function reset() {
|
||||
if (captcha.value.reset) captcha.value.reset();
|
||||
testcaptchaPassed.value = false;
|
||||
testcaptchaInput.value = '';
|
||||
}
|
||||
|
||||
async function requestRender() {
|
||||
|
|
@ -127,6 +145,12 @@ function onReceivedMessage(message: MessageEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
function testcaptchaSubmit() {
|
||||
testcaptchaPassed.value = testcaptchaInput.value === 'ai-chan-kawaii';
|
||||
callback(testcaptchaPassed.value ? 'testcaptcha-passed' : undefined);
|
||||
if (!testcaptchaPassed.value) testcaptchaInput.value = '';
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (available.value) {
|
||||
window.addEventListener('message', onReceivedMessage);
|
||||
|
|
|
|||
|
|
@ -100,10 +100,12 @@ export default defineComponent({
|
|||
return [el, separator];
|
||||
} else {
|
||||
if (props.ad && item._shouldInsertAd_) {
|
||||
return [h(MkAd, {
|
||||
return [h('div', {
|
||||
key: item.id + ':ad',
|
||||
style: 'padding: 8px; background-size: auto auto; background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-bg) 8px, var(--MI_THEME-bg) 14px );',
|
||||
}, [h(MkAd, {
|
||||
prefer: ['horizontal', 'horizontal-big'],
|
||||
}), el];
|
||||
})]), el];
|
||||
} else {
|
||||
return el;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
|
||||
<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
|
||||
<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
|
||||
<MkCaptcha v-if="instance.enableTestcaptcha" ref="testcaptcha" v-model="testcaptchaResponse" :class="$style.captcha" provider="testcaptcha"/>
|
||||
</div>
|
||||
|
||||
<MkButton type="submit" :disabled="needCaptcha && captchaFailed" large primary rounded style="margin: 0 auto;" data-cy-signin-page-password-continue>{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
|
||||
|
|
@ -44,6 +45,7 @@ export type PwResponse = {
|
|||
mCaptchaResponse: string | null;
|
||||
reCaptchaResponse: string | null;
|
||||
turnstileResponse: string | null;
|
||||
testcaptchaResponse: string | null;
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
|
@ -75,18 +77,21 @@ const hCaptcha = useTemplateRef('hcaptcha');
|
|||
const mCaptcha = useTemplateRef('mcaptcha');
|
||||
const reCaptcha = useTemplateRef('recaptcha');
|
||||
const turnstile = useTemplateRef('turnstile');
|
||||
const testcaptcha = useTemplateRef('testcaptcha');
|
||||
|
||||
const hCaptchaResponse = ref<string | null>(null);
|
||||
const mCaptchaResponse = ref<string | null>(null);
|
||||
const reCaptchaResponse = ref<string | null>(null);
|
||||
const turnstileResponse = ref<string | null>(null);
|
||||
const testcaptchaResponse = ref<string | null>(null);
|
||||
|
||||
const captchaFailed = computed((): boolean => {
|
||||
return (
|
||||
(instance.enableHcaptcha && !hCaptchaResponse.value) ||
|
||||
(instance.enableMcaptcha && !mCaptchaResponse.value) ||
|
||||
(instance.enableRecaptcha && !reCaptchaResponse.value) ||
|
||||
(instance.enableTurnstile && !turnstileResponse.value)
|
||||
(instance.enableTurnstile && !turnstileResponse.value) ||
|
||||
(instance.enableTestcaptcha && !testcaptchaResponse.value)
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -104,6 +109,7 @@ function onSubmit() {
|
|||
mCaptchaResponse: mCaptchaResponse.value,
|
||||
reCaptchaResponse: reCaptchaResponse.value,
|
||||
turnstileResponse: turnstileResponse.value,
|
||||
testcaptchaResponse: testcaptchaResponse.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -113,6 +119,7 @@ function resetCaptcha() {
|
|||
mCaptcha.value?.reset();
|
||||
reCaptcha.value?.reset();
|
||||
turnstile.value?.reset();
|
||||
testcaptcha.value?.reset();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ import { nextTick, onBeforeUnmount, ref, shallowRef, useTemplateRef } from 'vue'
|
|||
import * as Misskey from 'misskey-js';
|
||||
import { supported as webAuthnSupported, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
|
||||
|
||||
import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/browser-ponyfill';
|
||||
import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
|
||||
import { login } from '@/account.js';
|
||||
|
|
@ -79,9 +81,6 @@ import XPassword, { type PwResponse } from '@/components/MkSignin.password.vue';
|
|||
import XTotp from '@/components/MkSignin.totp.vue';
|
||||
import XPasskey from '@/components/MkSignin.passkey.vue';
|
||||
|
||||
import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/browser-ponyfill';
|
||||
import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'login', v: Misskey.entities.SigninFlowResponse & { finished: true }): void;
|
||||
}>();
|
||||
|
|
@ -189,6 +188,7 @@ async function onPasswordSubmitted(pw: PwResponse) {
|
|||
'm-captcha-response': pw.captcha.mCaptchaResponse,
|
||||
'g-recaptcha-response': pw.captcha.reCaptchaResponse,
|
||||
'turnstile-response': pw.captcha.turnstileResponse,
|
||||
'testcaptcha-response': pw.captcha.testcaptchaResponse,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
|
||||
<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
|
||||
<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
|
||||
<MkCaptcha v-if="instance.enableTestcaptcha" ref="testcaptcha" v-model="testcaptchaResponse" :class="$style.captcha" provider="testcaptcha"/>
|
||||
<MkButton type="submit" :disabled="shouldDisableSubmitting" large gradate rounded data-cy-signup-submit style="margin: 0 auto;">
|
||||
<template v-if="submitting">
|
||||
<MkLoading :em="true" :colored="false"/>
|
||||
|
|
@ -108,6 +109,7 @@ const hcaptcha = ref<Captcha | undefined>();
|
|||
const mcaptcha = ref<Captcha | undefined>();
|
||||
const recaptcha = ref<Captcha | undefined>();
|
||||
const turnstile = ref<Captcha | undefined>();
|
||||
const testcaptcha = ref<Captcha | undefined>();
|
||||
|
||||
const username = ref<string>('');
|
||||
const password = ref<string>('');
|
||||
|
|
@ -123,6 +125,7 @@ const hCaptchaResponse = ref<string | null>(null);
|
|||
const mCaptchaResponse = ref<string | null>(null);
|
||||
const reCaptchaResponse = ref<string | null>(null);
|
||||
const turnstileResponse = ref<string | null>(null);
|
||||
const testcaptchaResponse = ref<string | null>(null);
|
||||
const usernameAbortController = ref<null | AbortController>(null);
|
||||
const emailAbortController = ref<null | AbortController>(null);
|
||||
|
||||
|
|
@ -132,6 +135,7 @@ const shouldDisableSubmitting = computed((): boolean => {
|
|||
instance.enableMcaptcha && !mCaptchaResponse.value ||
|
||||
instance.enableRecaptcha && !reCaptchaResponse.value ||
|
||||
instance.enableTurnstile && !turnstileResponse.value ||
|
||||
instance.enableTestcaptcha && !testcaptchaResponse.value ||
|
||||
instance.emailRequiredForSignup && emailState.value !== 'ok' ||
|
||||
usernameState.value !== 'ok' ||
|
||||
passwordRetypeState.value !== 'match';
|
||||
|
|
@ -259,6 +263,7 @@ async function onSubmit(): Promise<void> {
|
|||
'm-captcha-response': mCaptchaResponse.value,
|
||||
'g-recaptcha-response': reCaptchaResponse.value,
|
||||
'turnstile-response': turnstileResponse.value,
|
||||
'testcaptcha-response': testcaptchaResponse.value,
|
||||
};
|
||||
|
||||
const res = await fetch(`${config.apiUrl}/signup`, {
|
||||
|
|
@ -301,6 +306,7 @@ function onSignupApiError() {
|
|||
mcaptcha.value?.reset?.();
|
||||
recaptcha.value?.reset?.();
|
||||
turnstile.value?.reset?.();
|
||||
testcaptcha.value?.reset?.();
|
||||
|
||||
os.alert({
|
||||
type: 'error',
|
||||
|
|
|
|||
|
|
@ -54,9 +54,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import contains from '@/scripts/contains.js';
|
||||
import * as os from '@/os.js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
|
||||
|
|
@ -484,6 +484,10 @@ defineExpose({
|
|||
}
|
||||
|
||||
.root {
|
||||
// universal.vueとかで直接--MI-stickyBottomが定義されていたりするのでリセット
|
||||
--MI-stickyTop: 0;
|
||||
--MI-stickyBottom: 0;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
|
|
|||
|
|
@ -30,12 +30,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</component>
|
||||
</div>
|
||||
<div v-else :class="$style.menu">
|
||||
<div :class="$style.menuContainer">
|
||||
<div>Ads by {{ host }}</div>
|
||||
<!--<MkButton class="button" primary>{{ i18n.ts._ad.like }}</MkButton>-->
|
||||
<MkButton v-if="chosen.ratio !== 0" :class="$style.menuButton" @click="reduceFrequency">{{ i18n.ts._ad.reduceFrequencyOfThisAd }}</MkButton>
|
||||
<button class="_textButton" @click="toggleMenu">{{ i18n.ts._ad.back }}</button>
|
||||
</div>
|
||||
<div>Ads by {{ host }}</div>
|
||||
<!--<MkButton class="button" primary>{{ i18n.ts._ad.like }}</MkButton>-->
|
||||
<MkButton v-if="chosen.ratio !== 0" :class="$style.menuButton" @click="reduceFrequency">{{ i18n.ts._ad.reduceFrequencyOfThisAd }}</MkButton>
|
||||
<button class="_textButton" @click="toggleMenu">{{ i18n.ts._ad.back }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
|
|
@ -123,8 +121,7 @@ function reduceFrequency(): void {
|
|||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
background-size: auto auto;
|
||||
background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--bg) 8px, var(--bg) 14px );
|
||||
|
||||
}
|
||||
|
||||
.main {
|
||||
|
|
@ -202,14 +199,11 @@ function reduceFrequency(): void {
|
|||
}
|
||||
|
||||
.menu {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menuContainer {
|
||||
padding: 8px;
|
||||
margin: 0 auto;
|
||||
max-width: 400px;
|
||||
background: var(--MI_THEME-panel);
|
||||
border: solid 1px var(--MI_THEME-divider);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,19 +4,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div ref="rootEl">
|
||||
<div ref="headerEl">
|
||||
<div>
|
||||
<div ref="headerEl" :class="$style.header">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div
|
||||
ref="bodyEl"
|
||||
:class="$style.body"
|
||||
:data-sticky-container-header-height="headerHeight"
|
||||
:data-sticky-container-footer-height="footerHeight"
|
||||
style="position: relative; z-index: 0;"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div ref="footerEl">
|
||||
<div ref="footerEl" :class="$style.footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -27,10 +26,8 @@ import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, shallowRef }
|
|||
|
||||
import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@@/js/const.js';
|
||||
|
||||
const rootEl = shallowRef<HTMLElement>();
|
||||
const headerEl = shallowRef<HTMLElement>();
|
||||
const footerEl = shallowRef<HTMLElement>();
|
||||
const bodyEl = shallowRef<HTMLElement>();
|
||||
|
||||
const headerHeight = ref<string | undefined>();
|
||||
const childStickyTop = ref(0);
|
||||
|
|
@ -67,31 +64,11 @@ onMounted(() => {
|
|||
|
||||
watch([parentStickyTop, parentStickyBottom], calc);
|
||||
|
||||
watch(childStickyTop, () => {
|
||||
if (bodyEl.value == null) return;
|
||||
bodyEl.value.style.setProperty('--MI-stickyTop', `${childStickyTop.value}px`);
|
||||
}, {
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
watch(childStickyBottom, () => {
|
||||
if (bodyEl.value == null) return;
|
||||
bodyEl.value.style.setProperty('--MI-stickyBottom', `${childStickyBottom.value}px`);
|
||||
}, {
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
if (headerEl.value != null) {
|
||||
headerEl.value.style.position = 'sticky';
|
||||
headerEl.value.style.top = 'var(--MI-stickyTop, 0)';
|
||||
headerEl.value.style.zIndex = '1';
|
||||
observer.observe(headerEl.value);
|
||||
}
|
||||
|
||||
if (footerEl.value != null) {
|
||||
footerEl.value.style.position = 'sticky';
|
||||
footerEl.value.style.bottom = 'var(--MI-stickyBottom, 0)';
|
||||
footerEl.value.style.zIndex = '1';
|
||||
observer.observe(footerEl.value);
|
||||
}
|
||||
});
|
||||
|
|
@ -99,8 +76,25 @@ onMounted(() => {
|
|||
onUnmounted(() => {
|
||||
observer.disconnect();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
rootEl: rootEl,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang='scss' module>
|
||||
.body {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
--MI-stickyTop: v-bind("childStickyTop + 'px'");
|
||||
--MI-stickyBottom: v-bind("childStickyBottom + 'px'");
|
||||
}
|
||||
|
||||
.header {
|
||||
position: sticky;
|
||||
top: var(--MI-stickyTop, 0);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: sticky;
|
||||
bottom: var(--MI-stickyBottom, 0);
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -266,6 +266,9 @@ const patronsWithIcon = [{
|
|||
}, {
|
||||
name: 'なっかあ',
|
||||
icon: 'https://assets.misskey-hub.net/patrons/c2f5f3e394e74a64912284a2f4ca710e.jpg',
|
||||
}, {
|
||||
name: '如月ユカ',
|
||||
icon: 'https://assets.misskey-hub.net/patrons/f24a042076a041b6811a2f124eb620ca.jpg',
|
||||
}];
|
||||
|
||||
const patrons = [
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template v-else-if="botProtectionForm.savedState.provider === 'mcaptcha'" #suffix>mCaptcha</template>
|
||||
<template v-else-if="botProtectionForm.savedState.provider === 'recaptcha'" #suffix>reCAPTCHA</template>
|
||||
<template v-else-if="botProtectionForm.savedState.provider === 'turnstile'" #suffix>Turnstile</template>
|
||||
<template v-else-if="botProtectionForm.savedState.provider === 'testcaptcha'" #suffix>testCaptcha</template>
|
||||
<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
|
||||
<template v-if="botProtectionForm.modified.value" #footer>
|
||||
<MkFormFooter :form="botProtectionForm"/>
|
||||
|
|
@ -23,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<option value="mcaptcha">mCaptcha</option>
|
||||
<option value="recaptcha">reCAPTCHA</option>
|
||||
<option value="turnstile">Turnstile</option>
|
||||
<option value="testcaptcha">testCaptcha</option>
|
||||
</MkRadios>
|
||||
|
||||
<template v-if="botProtectionForm.state.provider === 'hcaptcha'">
|
||||
|
|
@ -85,6 +87,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkCaptcha provider="turnstile" :sitekey="botProtectionForm.state.turnstileSiteKey || '1x00000000000000000000AA'"/>
|
||||
</FormSlot>
|
||||
</template>
|
||||
<template v-else-if="botProtectionForm.state.provider === 'testcaptcha'">
|
||||
<MkInfo warn><span v-html="i18n.ts.testCaptchaWarning"></span></MkInfo>
|
||||
<FormSlot>
|
||||
<template #label>{{ i18n.ts.preview }}</template>
|
||||
<MkCaptcha provider="testcaptcha"/>
|
||||
</FormSlot>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</template>
|
||||
|
|
@ -101,6 +110,7 @@ import { i18n } from '@/i18n.js';
|
|||
import { useForm } from '@/scripts/use-form.js';
|
||||
import MkFormFooter from '@/components/MkFormFooter.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
|
||||
const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
|
||||
|
||||
|
|
@ -115,7 +125,9 @@ const botProtectionForm = useForm({
|
|||
? 'turnstile'
|
||||
: meta.enableMcaptcha
|
||||
? 'mcaptcha'
|
||||
: null,
|
||||
: meta.enableTestcaptcha
|
||||
? 'testcaptcha'
|
||||
: null,
|
||||
hcaptchaSiteKey: meta.hcaptchaSiteKey,
|
||||
hcaptchaSecretKey: meta.hcaptchaSecretKey,
|
||||
mcaptchaSiteKey: meta.mcaptchaSiteKey,
|
||||
|
|
@ -140,6 +152,7 @@ const botProtectionForm = useForm({
|
|||
enableTurnstile: state.provider === 'turnstile',
|
||||
turnstileSiteKey: state.turnstileSiteKey,
|
||||
turnstileSecretKey: state.turnstileSecretKey,
|
||||
enableTestcaptcha: state.provider === 'testcaptcha',
|
||||
});
|
||||
fetchInstance(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="enableRegistration" @change="onChange_enableRegistration">
|
||||
<template #label>{{ i18n.ts.enableRegistration }}</template>
|
||||
<template #caption>{{ i18n.ts._serverSettings.thisSettingWillAutomaticallyOffWhenModeratorsInactive }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="emailRequiredForSignup" @change="onChange_emailRequiredForSignup">
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@
|
|||
--MI-minBottomSpacingMobile: calc(72px + max(12px, env(safe-area-inset-bottom, 0px)));
|
||||
--MI-minBottomSpacing: var(--MI-minBottomSpacingMobile);
|
||||
|
||||
//--ad: rgb(255 169 0 / 10%);
|
||||
|
||||
@media (max-width: 500px) {
|
||||
--MI-margin: var(--MI-marginHalf);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue