enhance: スキップできるようにするかどうかを鯖管で決定できるように
This commit is contained in:
parent
3604c470aa
commit
3dded16104
|
@ -4887,11 +4887,11 @@ export interface Locale extends ILocale {
|
||||||
/**
|
/**
|
||||||
* チュートリアルをスキップできないようにする
|
* チュートリアルをスキップできないようにする
|
||||||
*/
|
*/
|
||||||
"prohibitSkippingTutorial": string;
|
"prohibitSkippingInitialTutorial": string;
|
||||||
/**
|
/**
|
||||||
* 新規登録したユーザーに表示されるチュートリアルをスキップできないようにします。チュートリアルを完了せずチュートリアルページを回避した場合でも、強制的にリダイレクトされます。
|
* 新規登録したユーザーに表示されるチュートリアルをスキップできないようにします。チュートリアルを完了しなかったりチュートリアルページを回避したりした場合でも、強制的にリダイレクトされます。
|
||||||
*/
|
*/
|
||||||
"prohibitSkippingTutorialDescription": string;
|
"prohibitSkippingInitialTutorialDescription": string;
|
||||||
"_bubbleGame": {
|
"_bubbleGame": {
|
||||||
/**
|
/**
|
||||||
* 遊び方
|
* 遊び方
|
||||||
|
|
|
@ -1217,8 +1217,8 @@ hemisphere: "お住まいの地域"
|
||||||
withSensitive: "センシティブなファイルを含むノートを表示"
|
withSensitive: "センシティブなファイルを含むノートを表示"
|
||||||
userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿"
|
userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿"
|
||||||
enableHorizontalSwipe: "スワイプしてタブを切り替える"
|
enableHorizontalSwipe: "スワイプしてタブを切り替える"
|
||||||
prohibitSkippingTutorial: "チュートリアルをスキップできないようにする"
|
prohibitSkippingInitialTutorial: "チュートリアルをスキップできないようにする"
|
||||||
prohibitSkippingTutorialDescription: "新規登録したユーザーに表示されるチュートリアルをスキップできないようにします。チュートリアルを完了せずチュートリアルページを回避した場合でも、強制的にリダイレクトされます。"
|
prohibitSkippingInitialTutorialDescription: "新規登録したユーザーに表示されるチュートリアルをスキップできないようにします。チュートリアルを完了しなかったりチュートリアルページを回避したりした場合でも、強制的にリダイレクトされます。"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "遊び方"
|
howToPlay: "遊び方"
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class CanSkipInitialTutorial1708933788259 {
|
||||||
|
name = 'CanSkipInitialTutorial1708933788259'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "canSkipInitialTutorial" boolean NOT NULL DEFAULT true`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "canSkipInitialTutorial"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,6 +69,7 @@ export class MetaEntityService {
|
||||||
privacyPolicyUrl: instance.privacyPolicyUrl,
|
privacyPolicyUrl: instance.privacyPolicyUrl,
|
||||||
disableRegistration: instance.disableRegistration,
|
disableRegistration: instance.disableRegistration,
|
||||||
emailRequiredForSignup: instance.emailRequiredForSignup,
|
emailRequiredForSignup: instance.emailRequiredForSignup,
|
||||||
|
canSkipInitialTutorial: instance.canSkipInitialTutorial,
|
||||||
enableHcaptcha: instance.enableHcaptcha,
|
enableHcaptcha: instance.enableHcaptcha,
|
||||||
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
||||||
enableMcaptcha: instance.enableMcaptcha,
|
enableMcaptcha: instance.enableMcaptcha,
|
||||||
|
|
|
@ -179,6 +179,11 @@ export class MiMeta {
|
||||||
})
|
})
|
||||||
public emailRequiredForSignup: boolean;
|
public emailRequiredForSignup: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
public canSkipInitialTutorial: boolean;
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
|
|
@ -79,6 +79,10 @@ export const packedMetaLiteSchema = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
canSkipInitialTutorial: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
enableHcaptcha: {
|
enableHcaptcha: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -33,6 +33,10 @@ export const meta = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
canSkipInitialTutorial: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
enableHcaptcha: {
|
enableHcaptcha: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
@ -489,6 +493,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
privacyPolicyUrl: instance.privacyPolicyUrl,
|
privacyPolicyUrl: instance.privacyPolicyUrl,
|
||||||
disableRegistration: instance.disableRegistration,
|
disableRegistration: instance.disableRegistration,
|
||||||
emailRequiredForSignup: instance.emailRequiredForSignup,
|
emailRequiredForSignup: instance.emailRequiredForSignup,
|
||||||
|
canSkipInitialTutorial: instance.canSkipInitialTutorial,
|
||||||
enableHcaptcha: instance.enableHcaptcha,
|
enableHcaptcha: instance.enableHcaptcha,
|
||||||
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
||||||
enableMcaptcha: instance.enableMcaptcha,
|
enableMcaptcha: instance.enableMcaptcha,
|
||||||
|
|
|
@ -65,6 +65,7 @@ export const paramDef = {
|
||||||
cacheRemoteFiles: { type: 'boolean' },
|
cacheRemoteFiles: { type: 'boolean' },
|
||||||
cacheRemoteSensitiveFiles: { type: 'boolean' },
|
cacheRemoteSensitiveFiles: { type: 'boolean' },
|
||||||
emailRequiredForSignup: { type: 'boolean' },
|
emailRequiredForSignup: { type: 'boolean' },
|
||||||
|
canSkipInitialTutorial: { type: 'boolean' },
|
||||||
enableHcaptcha: { type: 'boolean' },
|
enableHcaptcha: { type: 'boolean' },
|
||||||
hcaptchaSiteKey: { type: 'string', nullable: true },
|
hcaptchaSiteKey: { type: 'string', nullable: true },
|
||||||
hcaptchaSecretKey: { type: 'string', nullable: true },
|
hcaptchaSecretKey: { type: 'string', nullable: true },
|
||||||
|
@ -269,6 +270,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
set.emailRequiredForSignup = ps.emailRequiredForSignup;
|
set.emailRequiredForSignup = ps.emailRequiredForSignup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.canSkipInitialTutorial !== undefined) {
|
||||||
|
set.canSkipInitialTutorial = ps.canSkipInitialTutorial;
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.enableHcaptcha !== undefined) {
|
if (ps.enableHcaptcha !== undefined) {
|
||||||
set.enableHcaptcha = ps.enableHcaptcha;
|
set.enableHcaptcha = ps.enableHcaptcha;
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@ export async function common(createVue: () => App<Element>) {
|
||||||
await deckStore.ready;
|
await deckStore.ready;
|
||||||
|
|
||||||
// 2024年3月1日JST以降に作成されたアカウントで、チュートリアル完了していない場合、チュートリアルにリダイレクト
|
// 2024年3月1日JST以降に作成されたアカウントで、チュートリアル完了していない場合、チュートリアルにリダイレクト
|
||||||
if ($i && new Date($i.createdAt).getTime() >= 1709218800000 && !claimedAchievements.includes('tutorialCompleted') && !location.pathname.startsWith('/onboarding') && !location.pathname.startsWith('/signup-complete')) {
|
if (!instance.canSkipInitialTutorial && $i && new Date($i.createdAt).getTime() >= 1709218800000 && !claimedAchievements.includes('tutorialCompleted') && !location.pathname.startsWith('/onboarding') && !location.pathname.startsWith('/signup-complete')) {
|
||||||
const param = new URLSearchParams();
|
const param = new URLSearchParams();
|
||||||
param.set('redirected_from', location.pathname + location.search + location.hash);
|
param.set('redirected_from', location.pathname + location.search + location.hash);
|
||||||
location.replace('/onboarding?' + param.toString());
|
location.replace('/onboarding?' + param.toString());
|
||||||
|
|
|
@ -275,7 +275,7 @@ async function onSubmit(): Promise<void> {
|
||||||
emit('signup', res);
|
emit('signup', res);
|
||||||
|
|
||||||
if (props.autoSet) {
|
if (props.autoSet) {
|
||||||
return login(res.i);
|
return login(res.i, '/onboarding');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -18,6 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ i18n.ts.emailRequiredForSignup }}</template>
|
<template #label>{{ i18n.ts.emailRequiredForSignup }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
||||||
|
<MkSwitch v-model="prohibitSkippingInitialTutorial">
|
||||||
|
<template #label>{{ i18n.ts.prohibitSkippingInitialTutorial }}</template>
|
||||||
|
<template #caption>{{ i18n.ts.prohibitSkippingInitialTutorialDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
<FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink>
|
<FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink>
|
||||||
|
|
||||||
<MkInput v-model="tosUrl" type="url">
|
<MkInput v-model="tosUrl" type="url">
|
||||||
|
@ -80,6 +85,7 @@ import FormLink from '@/components/form/link.vue';
|
||||||
|
|
||||||
const enableRegistration = ref<boolean>(false);
|
const enableRegistration = ref<boolean>(false);
|
||||||
const emailRequiredForSignup = ref<boolean>(false);
|
const emailRequiredForSignup = ref<boolean>(false);
|
||||||
|
const prohibitSkippingInitialTutorial = ref<boolean>(false);
|
||||||
const sensitiveWords = ref<string>('');
|
const sensitiveWords = ref<string>('');
|
||||||
const prohibitedWords = ref<string>('');
|
const prohibitedWords = ref<string>('');
|
||||||
const hiddenTags = ref<string>('');
|
const hiddenTags = ref<string>('');
|
||||||
|
@ -91,6 +97,7 @@ async function init() {
|
||||||
const meta = await misskeyApi('admin/meta');
|
const meta = await misskeyApi('admin/meta');
|
||||||
enableRegistration.value = !meta.disableRegistration;
|
enableRegistration.value = !meta.disableRegistration;
|
||||||
emailRequiredForSignup.value = meta.emailRequiredForSignup;
|
emailRequiredForSignup.value = meta.emailRequiredForSignup;
|
||||||
|
prohibitSkippingInitialTutorial.value = !meta.canSkipInitialTutorial;
|
||||||
sensitiveWords.value = meta.sensitiveWords.join('\n');
|
sensitiveWords.value = meta.sensitiveWords.join('\n');
|
||||||
prohibitedWords.value = meta.prohibitedWords.join('\n');
|
prohibitedWords.value = meta.prohibitedWords.join('\n');
|
||||||
hiddenTags.value = meta.hiddenTags.join('\n');
|
hiddenTags.value = meta.hiddenTags.join('\n');
|
||||||
|
@ -103,6 +110,7 @@ function save() {
|
||||||
os.apiWithDialog('admin/update-meta', {
|
os.apiWithDialog('admin/update-meta', {
|
||||||
disableRegistration: !enableRegistration.value,
|
disableRegistration: !enableRegistration.value,
|
||||||
emailRequiredForSignup: emailRequiredForSignup.value,
|
emailRequiredForSignup: emailRequiredForSignup.value,
|
||||||
|
canSkipInitialTutorial: !prohibitSkippingInitialTutorial.value,
|
||||||
tosUrl: tosUrl.value,
|
tosUrl: tosUrl.value,
|
||||||
privacyPolicyUrl: privacyPolicyUrl.value,
|
privacyPolicyUrl: privacyPolicyUrl.value,
|
||||||
sensitiveWords: sensitiveWords.value.split('\n'),
|
sensitiveWords: sensitiveWords.value.split('\n'),
|
||||||
|
|
|
@ -35,8 +35,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div>{{ i18n.tsx._initialTutorial._onboardingLanding.welcomeToX({ name: instance.name ?? host }) }}</div>
|
<div>{{ i18n.tsx._initialTutorial._onboardingLanding.welcomeToX({ name: instance.name ?? host }) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>{{ i18n.tsx._initialTutorial._onboardingLanding.description({ name: instance.name ?? host }) }}</div>
|
<div>{{ i18n.tsx._initialTutorial._onboardingLanding.description({ name: instance.name ?? host }) }}</div>
|
||||||
<MkButton large primary rounded gradate style="margin: 16px auto;" @click="next">{{ i18n.ts.start }} <i class="ti ti-arrow-right"></i></MkButton>
|
<MkButton large primary rounded gradate style="margin: 16px auto 0;" @click="next">{{ i18n.ts.start }} <i class="ti ti-arrow-right"></i></MkButton>
|
||||||
<MkInfo style="width: fit-content; margin: 0 auto; text-align: start; white-space: pre-wrap;">{{ i18n.tsx._initialTutorial._onboardingLanding.takesAbout({ min: 5 }) }}</MkInfo>
|
<MkButton v-if="instance.canSkipInitialTutorial" transparent rounded style="margin: 0 auto;" @click="cancel">{{ i18n.ts.cancel }}</MkButton>
|
||||||
|
<MkInfo style="width: fit-content; margin: 0 auto; text-align: start; white-space: pre-wrap;">{{ i18n.tsx._initialTutorial._onboardingLanding.takesAbout({ min: 3 }) }}</MkInfo>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -105,6 +106,7 @@ import { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { host } from '@/config.js';
|
import { host } from '@/config.js';
|
||||||
|
import { confirm as osConfirm } from '@/os.js';
|
||||||
|
|
||||||
import MkAnimBg from '@/components/MkAnimBg.vue';
|
import MkAnimBg from '@/components/MkAnimBg.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
@ -132,6 +134,19 @@ const animationPhase = ref(0);
|
||||||
const query = new URLSearchParams(location.search);
|
const query = new URLSearchParams(location.search);
|
||||||
const originalPath = query.get('redirected_from');
|
const originalPath = query.get('redirected_from');
|
||||||
|
|
||||||
|
async function cancel() {
|
||||||
|
const confirm = await osConfirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.ts._initialTutorial.skipAreYouSure,
|
||||||
|
okText: i18n.ts.yes,
|
||||||
|
cancelText: i18n.ts.no,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirm.canceled) return;
|
||||||
|
|
||||||
|
location.href = '/';
|
||||||
|
}
|
||||||
|
|
||||||
// 画面上部に表示されるアイコンの中心Y座標を取得
|
// 画面上部に表示されるアイコンの中心Y座標を取得
|
||||||
function getIconY(instanceIconEl: HTMLImageElement, welcomePageRootEl: HTMLDivElement) {
|
function getIconY(instanceIconEl: HTMLImageElement, welcomePageRootEl: HTMLDivElement) {
|
||||||
const instanceIconElRect = instanceIconEl.getBoundingClientRect();
|
const instanceIconElRect = instanceIconEl.getBoundingClientRect();
|
||||||
|
|
|
@ -4747,6 +4747,7 @@ export type components = {
|
||||||
defaultLightTheme: string | null;
|
defaultLightTheme: string | null;
|
||||||
disableRegistration: boolean;
|
disableRegistration: boolean;
|
||||||
emailRequiredForSignup: boolean;
|
emailRequiredForSignup: boolean;
|
||||||
|
canSkipInitialTutorial: boolean;
|
||||||
enableHcaptcha: boolean;
|
enableHcaptcha: boolean;
|
||||||
hcaptchaSiteKey: string | null;
|
hcaptchaSiteKey: string | null;
|
||||||
enableMcaptcha: boolean;
|
enableMcaptcha: boolean;
|
||||||
|
@ -4843,6 +4844,7 @@ export type operations = {
|
||||||
cacheRemoteFiles: boolean;
|
cacheRemoteFiles: boolean;
|
||||||
cacheRemoteSensitiveFiles: boolean;
|
cacheRemoteSensitiveFiles: boolean;
|
||||||
emailRequiredForSignup: boolean;
|
emailRequiredForSignup: boolean;
|
||||||
|
canSkipInitialTutorial: boolean;
|
||||||
enableHcaptcha: boolean;
|
enableHcaptcha: boolean;
|
||||||
hcaptchaSiteKey: string | null;
|
hcaptchaSiteKey: string | null;
|
||||||
enableMcaptcha: boolean;
|
enableMcaptcha: boolean;
|
||||||
|
@ -8811,6 +8813,7 @@ export type operations = {
|
||||||
cacheRemoteFiles?: boolean;
|
cacheRemoteFiles?: boolean;
|
||||||
cacheRemoteSensitiveFiles?: boolean;
|
cacheRemoteSensitiveFiles?: boolean;
|
||||||
emailRequiredForSignup?: boolean;
|
emailRequiredForSignup?: boolean;
|
||||||
|
canSkipInitialTutorial?: boolean;
|
||||||
enableHcaptcha?: boolean;
|
enableHcaptcha?: boolean;
|
||||||
hcaptchaSiteKey?: string | null;
|
hcaptchaSiteKey?: string | null;
|
||||||
hcaptchaSecretKey?: string | null;
|
hcaptchaSecretKey?: string | null;
|
||||||
|
|
Loading…
Reference in New Issue