feat: make instance private

This commit is contained in:
아르페 2023-12-01 14:26:18 +09:00
parent 0c4972c12a
commit ea0c96d5b4
No known key found for this signature in database
GPG key ID: B1EFBBF5C93FF78F
16 changed files with 78 additions and 13 deletions

View file

@ -1216,7 +1216,10 @@ beSureToReadThisAsItIsImportant: "Please read this important information."
iHaveReadXCarefullyAndAgree: "I have read the text \"{x}\" and agree."
doNotSendNotificationEmailsForAbuseReport: "Do not send out notification emails for reporting"
disableTimelinePreview: "Hide timeline preview in landing page"
disableTimelinePreviewWarning: "By turning on, '{explore}' in landing page will be replaced to '{letsLookAtTimeline}'"
disableTimelinePreviewDescription: "By turning on, '{explore}' in landing page will be replaced to '{letsLookAtTimeline}'"
signupIsNotAvailable: "For now, you can't sign up on this instance."
makeInstancePrivate: "Make instance private"
makeInstancePrivateDescription: "By turning on, all visitors can't sign up (even if they invited) on this instance and can't view timeline, trends, highlights, etc."
emailToReceiveAbuseReport: "Email address to receive notification of the report"
emailToReceiveAbuseReportCaption: "Specify the email address to receive notification of the report. If this field is left blank, the mail server's email address will be used."
dialog: "Dialog"

View file

@ -1222,7 +1222,10 @@ beSureToReadThisAsItIsImportant: "重要ですので必ずお読みください
iHaveReadXCarefullyAndAgree: "「{x}」の内容をよく読み、同意します。"
doNotSendNotificationEmailsForAbuseReport: "通報の通知メールを発送しないようにする"
disableTimelinePreview: "非ログインのメインページにTLを表示しない"
disableTimelinePreviewWarning: "このオプションを使用すると、「{explore}」ボタンが「{letsLookAtTimeline}」に変わります。"
disableTimelinePreviewDescription: "このオプションを使用すると、「{explore}」ボタンが「{letsLookAtTimeline}」に変わります。"
signupIsNotAvailable: "현재 이 인스턴스는 신규 사용자를 받고 있지 않습니다."
makeInstancePrivate: "인스턴스를 비공개로 만들기"
makeInstancePrivateDescription: "이 옵션을 사용할 경우, 초대를 통한 신규 가입을 포함해 모든 가입이 제한되며, 비로그인 사용자는 발견하기, 타임라인 등을 일체 볼 수 없게 됩니다."
emailToReceiveAbuseReport: "通報通知を受け取るためのメールアドレス"
emailToReceiveAbuseReportCaption: "通報通知を受け取るためのメールアドレスを指定します。ここの入力欄を空にするとメールサーバーのメールアドレスが使用されます。"
dialog: "ダイアログ"

View file

@ -1214,7 +1214,10 @@ beSureToReadThisAsItIsImportant: "중요하므로 반드시 읽어주세요."
iHaveReadXCarefullyAndAgree: "\"{x}\"의 내용을 읽고 동의할게요."
doNotSendNotificationEmailsForAbuseReport: "신고 알림 메일을 발송하지 않기"
disableTimelinePreview: "비로그인 페이지에 타임라인 미리보기를 표시하지 않기"
disableTimelinePreviewWarning: "이 옵션을 사용하면 '{explore}' 버튼이 '{letsLookAtTimeline}' 버튼으로 변경됩니다."
disableTimelinePreviewDescription: "이 옵션을 사용하면 '{explore}' 버튼이 '{letsLookAtTimeline}' 버튼으로 변경됩니다."
signupIsNotAvailable: "현재 이 인스턴스는 신규 사용자를 받고 있지 않습니다."
makeInstancePrivate: "인스턴스를 비공개로 만들기"
makeInstancePrivateDescription: "이 옵션을 사용할 경우, 초대를 통한 신규 가입을 포함해 모든 가입이 제한되며, 비로그인 사용자는 발견하기, 타임라인 등을 일체 볼 수 없게 됩니다."
emailToReceiveAbuseReport: "신고 알림을 받을 수 있는 이메일 주소"
emailToReceiveAbuseReportCaption: "신고 알림을 받을 이메일 주소를 지정해 주세요. 이곳의 입력란을 비워두면 메일 서버의 이메일 주소가 사용돼요."
dialog: "다이얼로그"

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class privateInstance1701407688151 {
name = 'privateInstance1701407688151'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "privateInstance" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "privateInstance"`);
}
}

View file

@ -673,4 +673,9 @@ export class MiMeta {
default: false,
})
public disableTimelinePreview: boolean;
@Column('boolean', {
default: false,
})
public privateInstance: boolean;
}

View file

@ -549,6 +549,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
skipVersion: instance.skipVersion,
skipCherryPickVersion: instance.skipCherryPickVersion,
disableTimelinePreview: instance.disableTimelinePreview,
privateInstance: instance.privateInstance,
};
});
}

View file

@ -163,6 +163,7 @@ export const paramDef = {
skipVersion: { type: 'boolean' },
skipCherryPickVersion: { type: 'string', nullable: true },
disableTimelinePreview: { type: 'boolean' },
privateInstance: { type: 'boolean' },
},
required: [],
} as const;

View file

@ -219,6 +219,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
privateInstance: {
type: 'boolean',
optional: false, nullable: false,
},
features: {
type: 'object',
optional: true, nullable: false,
@ -358,6 +362,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
mediaProxy: this.config.mediaProxy,
disableTimelinePreview: instance.disableTimelinePreview,
privateInstance: instance.privateInstance,
...(ps.detail ? {
cacheRemoteFiles: instance.cacheRemoteFiles,

View file

@ -394,6 +394,8 @@ export type LiteInstanceMetadata = {
notesPerOneAd: number;
translatorAvailable: boolean;
serverRules: string[];
disableTimelinePreview: boolean;
privateInstance: boolean;
};
export type DetailedInstanceMetadata = LiteInstanceMetadata & {

View file

@ -21,11 +21,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="instance.disableRegistration" :class="$style.mainWarn">
<MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
</div>
<div v-if="instance.privateInstance" :class="$style.mainWarn">
<MkInfo warn>{{ i18n.ts.signupIsNotAvailable }}</MkInfo>
</div>
<div class="_gaps_s" :class="$style.mainActions">
<MkButton :class="$style.mainAction" full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton>
<MkButton v-if="!instance.privateInstance" :class="$style.mainAction" full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton>
<!-- <MkButton :class="$style.mainAction" full rounded @click="exploreOtherServers()">{{ i18n.ts.exploreOtherServers }}</MkButton> -->
<MkButton v-if="!instance.disableTimelinePreview" :class="$style.mainAction" full rounded @click="mainRouter.push('/explore')">{{ i18n.ts.explore }}</MkButton>
<MkButton v-if="instance.disableTimelinePreview" :class="$style.mainAction" full rounded @click="mainRouter.push('/timeline')">{{ i18n.ts.letsLookAtTimeline }}</MkButton>
<MkButton v-if="!instance.disableTimelinePreview && !instance.privateInstance" :class="$style.mainAction" full rounded @click="mainRouter.push('/explore')">{{ i18n.ts.explore }}</MkButton>
<MkButton v-if="instance.disableTimelinePreview && !instance.privateInstance" :class="$style.mainAction" full rounded @click="mainRouter.push('/timeline')">{{ i18n.ts.letsLookAtTimeline }}</MkButton>
<MkButton :class="$style.mainAction" full rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton>
</div>
</div>
@ -40,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.statsItemCount"><MkNumber :value="stats.originalNotesCount"/></div>
</div>
</div>
<div v-if="!instance.disableTimelinePreview" :class="[$style.tl, $style.panel]">
<div v-if="!instance.disableTimelinePreview && !instance.privateInstance" :class="[$style.tl, $style.panel]">
<div :class="$style.tlHeader">{{ i18n.ts.letsLookAtTimeline }}</div>
<div :class="$style.tlBody">
<MkTimeline src="local"/>

View file

@ -14,6 +14,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.enableRegistration }}</template>
</MkSwitch>
<MkSwitch v-model="privateInstance">
<template #label>{{ i18n.ts.makeInstancePrivate }}</template>
<template #caption>{{ i18n.ts.makeInstancePrivateDescription }}</template>
</MkSwitch>
<MkSwitch v-model="emailRequiredForSignup">
<template #label>{{ i18n.ts.emailRequiredForSignup }}</template>
</MkSwitch>
@ -75,6 +80,7 @@ import MkButton from '@/components/MkButton.vue';
import FormLink from '@/components/form/link.vue';
let enableRegistration: boolean = $ref(false);
let privateInstance: boolean = $ref(false);
let emailRequiredForSignup: boolean = $ref(false);
let sensitiveWords: string = $ref('');
let hiddenTags: string = $ref('');
@ -85,6 +91,7 @@ let privacyPolicyUrl: string | null = $ref(null);
async function init() {
const meta = await os.api('admin/meta');
enableRegistration = !meta.disableRegistration;
privateInstance = meta.privateInstance;
emailRequiredForSignup = meta.emailRequiredForSignup;
sensitiveWords = meta.sensitiveWords.join('\n');
hiddenTags = meta.hiddenTags.join('\n');
@ -96,6 +103,7 @@ async function init() {
function save() {
os.apiWithDialog('admin/update-meta', {
disableRegistration: !enableRegistration,
privateInstance: privateInstance,
emailRequiredForSignup,
tosUrl,
privacyPolicyUrl,

View file

@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_panel" style="padding: 16px;">
<MkSwitch v-model="disableTimelinePreview">
<template #label>{{ i18n.ts.disableTimelinePreview }}</template>
<template #caption>{{ i18n.t('disableTimelinePreviewWarning', { explore: i18n.ts.explore, letsLookAtTimeline: i18n.ts.letsLookAtTimeline }) }}</template>
<template #caption>{{ i18n.t('disableTimelinePreviewDescription', { explore: i18n.ts.explore, letsLookAtTimeline: i18n.ts.letsLookAtTimeline }) }}</template>
</MkSwitch>
</div>
</div>

View file

@ -62,6 +62,7 @@ import MkButton from '@/components/MkButton.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { useRouter } from '@/router.js';
import { $i } from '@/account.js';
import { instance } from '@/instance.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
@ -72,6 +73,8 @@ const props = defineProps<{
type?: string;
}>();
if (instance.privateInstance && $i == null) router.push('/');
let key = $ref('');
let tab = $ref('featured');
let searchQuery = $ref('');

View file

@ -26,6 +26,9 @@ import XFeatured from './explore.featured.vue';
import XUsers from './explore.users.vue';
import XRoles from './explore.roles.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { instance } from '@/instance.js';
import { useRouter } from '@/router.js';
import { $i } from '@/account.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
@ -36,6 +39,10 @@ const props = withDefaults(defineProps<{
initialTab: 'featured',
});
const router = useRouter();
if (instance.privateInstance && $i == null) router.push('/');
let tab = $ref(props.initialTab);
let tagsEl = $shallowRef<InstanceType<typeof MkFoldableSection>>();

View file

@ -81,6 +81,7 @@ import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { $i } from '@/account.js';
import { useRouter } from '@/router.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { miLocalStorage } from '@/local-storage.js';
import { antennasCache, userListsCache } from '@/cache.js';
@ -91,6 +92,8 @@ import { unisonReload } from '@/scripts/unison-reload.js';
let showEl = $ref(false);
const isFriendly = ref(miLocalStorage.getItem('ui') === 'friendly');
const router = useRouter();
const MOBILE_THRESHOLD = 500;
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
@ -100,6 +103,8 @@ window.addEventListener('resize', () => {
if (!isFriendly.value) provide('shouldOmitHeaderTitle', true);
if (instance.privateInstance && $i == null) router.push('/');
const isLocalTimelineAvailable = ($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable);
const isGlobalTimelineAvailable = ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable);
const keymap = {

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div v-if="meta" class="rsqzvsbo">
<MkFeaturedPhotos class="bg"/>
<XTimeline class="tl"/>
<XTimeline v-if="!instance.disableTimelinePreview && !instance.privateInstance" class="tl"/>
<div class="shape1"></div>
<div class="shape2"></div>
<img src="/client-assets/cherrypick.svg" class="cherrypick"/>
@ -22,10 +22,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div v-if="instances && instances.length > 0" class="federation">
<MarqueeText :duration="40">
<MkA v-for="instance in instances" :key="instance.id" :class="$style.federationInstance" :to="`/instance-info/${instance.host}`" behavior="window">
<!--<MkInstanceCardMini :instance="instance"/>-->
<img v-if="instance.iconUrl" class="icon" :src="getInstanceIcon(instance)" alt=""/>
<span class="name _monospace">{{ instance.host }}</span>
<MkA v-for="ins in instances" :key="ins.id" :class="$style.federationInstance" :to="`/instance-info/${ins.host}`" behavior="window">
<!--<MkInstanceCardMini :instance="ins"/>-->
<img v-if="ins.iconUrl" class="icon" :src="getInstanceIcon(ins)" alt=""/>
<span class="name _monospace">{{ ins.host }}</span>
</MkA>
</MarqueeText>
</div>