feat(privacy): private mode

This commit is contained in:
オスカー、 2024-07-20 23:58:32 +09:00
parent de023d8c99
commit 73f1a1ba86
Signed by: SWREI
GPG Key ID: 139D6573F92DA9F7
7 changed files with 80 additions and 47 deletions

44
locales/index.d.ts vendored
View File

@ -5104,13 +5104,21 @@ export interface Locale extends ILocale {
*/ */
"thankYou": string; "thankYou": string;
/** /**
* *
*/
"privateMode": string;
/**
*
*/ */
"hideSensitiveInformation": string; "hideSensitiveInformation": string;
/** /**
* *
*/ */
"youAreHidingSensitiveInformation": string; "youAreHidingSensitiveInformation": string;
/**
*
*/
"temporarilySeeThis": string;
"_bubbleGame": { "_bubbleGame": {
/** /**
* *
@ -10395,67 +10403,67 @@ export interface Locale extends ILocale {
}; };
"_hideSensitiveInformation": { "_hideSensitiveInformation": {
/** /**
* *
*/ */
"use": string; "use": string;
/** /**
* * Misskeyを利用する際にプライバシー保護に役立ちます
*/ */
"about": string; "about": string;
/** /**
* *
*/ */
"itsHidden": string; "itsHidden": string;
/** /**
* *
*/ */
"itsNotHidden": string; "itsNotHidden": string;
/** /**
* * 稿
*/ */
"directMessages": string; "directMessages": string;
/** /**
* * 稿
*/ */
"directMessagesUse": string; "directMessagesUse": string;
/** /**
* * 稿
*/ */
"directMessagesDescription": string; "directMessagesDescription": string;
/** /**
* *
*/ */
"drive": string; "drive": string;
/** /**
* *
*/ */
"driveUse": string; "driveUse": string;
/** /**
* *
*/ */
"driveDescription": string; "driveDescription": string;
/** /**
* *
*/ */
"moderationLog": string; "moderationLog": string;
/** /**
* *
*/ */
"moderationLogUse": string; "moderationLogUse": string;
/** /**
* *
*/ */
"moderationLogDescription": string; "moderationLogDescription": string;
/** /**
* *
*/ */
"roles": string; "roles": string;
/** /**
* *
*/ */
"rolesUse": string; "rolesUse": string;
/** /**
* *
*/ */
"rolesDescription": string; "rolesDescription": string;
}; };

View File

@ -1271,8 +1271,10 @@ here: "こちら"
credits: "スタッフロール" credits: "スタッフロール"
timeWillCome: "いつかこの欄にあなたの名前が書かれる日が来るのでしょうか?" timeWillCome: "いつかこの欄にあなたの名前が書かれる日が来るのでしょうか?"
thankYou: "オスカーはあなたと一緒にサーフィンします。 いつまでも。" thankYou: "オスカーはあなたと一緒にサーフィンします。 いつまでも。"
hideSensitiveInformation: "" privateMode: "プライベートモード"
youAreHidingSensitiveInformation: "" hideSensitiveInformation: "個人情報の非表示"
youAreHidingSensitiveInformation: "「プライベートモード」で非表示になっています。"
temporarilySeeThis: "無視して表示する"
_bubbleGame: _bubbleGame:
howToPlay: "遊び方" howToPlay: "遊び方"
@ -2766,19 +2768,19 @@ _skebStatus:
nRequests: "取引実績 {n}件" nRequests: "取引実績 {n}件"
_hideSensitiveInformation: _hideSensitiveInformation:
use: "" use: "「プライベートモード」を有効にする"
about: "" about: "この機能を有効にすると、他の人が自分の画面を見たり、公共の場所などでMisskeyを利用する際にプライバシー保護に役立ちます。"
itsHidden: "" itsHidden: "非表示"
itsNotHidden: "" itsNotHidden: "表示"
directMessages: "" directMessages: "ダイレクト投稿"
directMessagesUse: "" directMessagesUse: "ダイレクト投稿を非表示にする"
directMessagesDescription: "" directMessagesDescription: "このオプションを有効にすると、ダイレクト投稿の内容が基本的に表示されなくなります。"
drive: "" drive: "ドライブ"
driveUse: "" driveUse: "ファイルリストを非表示にする"
driveDescription: "" driveDescription: "このオプションを有効にすると、ドライブのファイルリストが表示されなくなります(ドライブ内のファイルの添付が難しくなります)。"
moderationLog: "" moderationLog: "モデレーションノート"
moderationLogUse: "" moderationLogUse: "モデレーションノートを非表示にする"
moderationLogDescription: "" moderationLogDescription: "このオプションを有効にすると、ユーザープロフィールにモデレーターが作成したモデレーションノートが表示されなくなります。"
roles: "" roles: "ロール"
rolesUse: "" rolesUse: "割り当てられたロールを非表示にする"
rolesDescription: "" rolesDescription: "このオプションを有効にすると、ユーザープロファイルにすべてのロールリストが表示されなくなります。"

View File

@ -1257,8 +1257,10 @@ alwaysConfirmFollow: "팔로우할 때 항상 확인하기"
credits: "엔딩 크레딧" credits: "엔딩 크레딧"
timeWillCome: "언젠가 이 칸에 당신의 이름이 쓰여지는 날이 올까요?" timeWillCome: "언젠가 이 칸에 당신의 이름이 쓰여지는 날이 올까요?"
thankYou: "오스카는 당신과 함께 서핑할 것입니다. 언제까지고." thankYou: "오스카는 당신과 함께 서핑할 것입니다. 언제까지고."
privateMode: "프라이빗 모드"
hideSensitiveInformation: "민감한 정보 숨기기" hideSensitiveInformation: "민감한 정보 숨기기"
youAreHidingSensitiveInformation: "'프라이빗 모드'에 의해 숨겨졌습니다." youAreHidingSensitiveInformation: "'프라이빗 모드'에 의해 숨겨졌습니다."
temporarilySeeThis: "무시하고 표시하기"
_bubbleGame: _bubbleGame:
howToPlay: "설명" howToPlay: "설명"
hold: "홀드" hold: "홀드"
@ -2666,4 +2668,4 @@ _hideSensitiveInformation:
moderationLogDescription: "이 옵션을 활성화하면 유저 프로필에서 중재자가 작성한 중재 기록이 표시되지 않게 됩니다." moderationLogDescription: "이 옵션을 활성화하면 유저 프로필에서 중재자가 작성한 중재 기록이 표시되지 않게 됩니다."
roles: "역할" roles: "역할"
rolesUse: "할당된 역할 숨기기" rolesUse: "할당된 역할 숨기기"
rolesDescription: "이 옵션을 활성화하면 유저 프로필에서 '공개'로 설정되지 않은 역할이 표시되지 않게 됩니다." rolesDescription: "이 옵션을 활성화하면 유저 프로필에서 모든 역할 목록이 표시되지 않게 됩니다."

View File

@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
@drop.prevent.stop="onDrop" @drop.prevent.stop="onDrop"
@contextmenu.stop="onContextmenu" @contextmenu.stop="onContextmenu"
> >
<div ref="contents"> <div ref="contents" v-if="!hideDriveFileList">
<div v-show="folders.length > 0" ref="foldersContainer" :class="$style.folders"> <div v-show="folders.length > 0" ref="foldersContainer" :class="$style.folders">
<XFolder <XFolder
v-for="(f, i) in folders" v-for="(f, i) in folders"
@ -87,6 +87,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div> <div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div>
</div> </div>
</div> </div>
<div ref="contents" :class="$style.privateMode" v-else>
<div v-if="!fetching" :class="$style.empty">
<div>
<strong>{{ i18n.ts.privateMode }}</strong>
<br/>
{{ i18n.ts.youAreHidingSensitiveInformation }}
</div>
</div>
<MkButton small inline @click="hideDriveFileList = false">{{ i18n.ts.temporarilySeeThis }}</MkButton>
</div>
<MkLoading v-if="fetching"/> <MkLoading v-if="fetching"/>
</div> </div>
<div v-if="draghover" :class="$style.dropzone"></div> <div v-if="draghover" :class="$style.dropzone"></div>
@ -142,6 +152,7 @@ const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]);
const uploadings = uploads; const uploadings = uploads;
const connection = useStream().useChannel('drive'); const connection = useStream().useChannel('drive');
const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // $ref使 const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // $ref使
const hideDriveFileList = ref<boolean>(defaultStore.state.hideSensitiveInformation && defaultStore.state.hideDriveFileList);
// //
const draghover = ref(false); const draghover = ref(false);
@ -768,6 +779,11 @@ onBeforeUnmount(() => {
} }
} }
.privateMode {
text-align: center;
align-items: center;
}
.folders, .folders,
.files { .files {
display: flex; display: flex;

View File

@ -154,7 +154,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
</I18n> </I18n>
</div> </div>
<div v-else :class="$style.muted" :style="hideMutedNotes ? 'display: none' : undefined" @click="isRedacted = false; muted = false"> <div v-else :class="$style.muted" @click="isRedacted = false; muted = false">
<I18n :src="i18n.ts.youAreHidingSensitiveInformation" tag="small"/> <I18n :src="i18n.ts.youAreHidingSensitiveInformation" tag="small"/>
</div> </div>
</template> </template>
@ -250,11 +250,6 @@ const isRenote = (
note.value.fileIds && note.value.fileIds.length === 0 && note.value.fileIds && note.value.fileIds.length === 0 &&
note.value.poll == null note.value.poll == null
); );
const isRedacted = (
defaultStore.state.hideDirectMessages &&
defaultStore.state.hideSensitiveInformation &&
note.value.visibility === 'specified'
);
const rootEl = shallowRef<HTMLElement>(); const rootEl = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>(); const menuButton = shallowRef<HTMLElement>();
@ -272,6 +267,11 @@ const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
const collapsed = ref(appearNote.value.cw == null && isLong); const collapsed = ref(appearNote.value.cw == null && isLong);
const isDeleted = ref(false); const isDeleted = ref(false);
const muted = ref(checkMute(appearNote.value, $i?.mutedWords ?? [])); const muted = ref(checkMute(appearNote.value, $i?.mutedWords ?? []));
const isRedacted = ref<boolean>(
defaultStore.state.hideDirectMessages &&
defaultStore.state.hideSensitiveInformation &&
note.value.visibility === 'specified'
);
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null); const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
const translating = ref(false); const translating = ref(false);
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);

View File

@ -64,8 +64,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch> <MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch>
</div> </div>
</MkFolder> </MkFolder>
<MkSwitch v-model="keepCw">{{ i18n.ts.keepCw }}</MkSwitch>
</div> </div>
<MkSwitch v-model="keepCw">{{ i18n.ts.keepCw }}</MkSwitch>
</FormSection> </FormSection>
<FormSection> <FormSection>

View File

@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span> <span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span>
</div> </div>
</div> </div>
<div v-if="user.roles.length > 0" class="roles"> <div v-if="user.roles.length > 0 && !hideRoleList" class="roles">
<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }"> <span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">
<MkA v-adaptive-bg :to="`/roles/${role.id}`"> <MkA v-adaptive-bg :to="`/roles/${role.id}`">
<img v-if="role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="role.iconUrl"/> <img v-if="role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="role.iconUrl"/>
@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkA> </MkA>
</span> </span>
</div> </div>
<div v-if="iAmModerator" class="moderationNote"> <div v-if="!hideModerationNote" class="moderationNote">
<MkTextarea v-if="editModerationNote || (moderationNote != null && moderationNote !== '')" v-model="moderationNote" manualSave> <MkTextarea v-if="editModerationNote || (moderationNote != null && moderationNote !== '')" v-model="moderationNote" manualSave>
<template #label>{{ i18n.ts.moderationNote }}</template> <template #label>{{ i18n.ts.moderationNote }}</template>
</MkTextarea> </MkTextarea>
@ -212,6 +212,7 @@ import { confetti } from '@/scripts/confetti.js';
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js'; import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js'; import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
import { useRouter } from '@/router/supplier.js'; import { useRouter } from '@/router/supplier.js';
import { defaultStore } from '@/store.js';
function calcAge(birthdate: string): number { function calcAge(birthdate: string): number {
const date = new Date(birthdate); const date = new Date(birthdate);
@ -254,6 +255,9 @@ const isEditingMemo = ref(false);
const moderationNote = ref(props.user.moderationNote); const moderationNote = ref(props.user.moderationNote);
const editModerationNote = ref(false); const editModerationNote = ref(false);
const hideModerationNote = !iAmModerator || (defaultStore.state.hideSensitiveInformation && defaultStore.state.hideModerationLog);
const hideRoleList = defaultStore.state.hideSensitiveInformation && defaultStore.state.hideRoleList;
watch(moderationNote, async () => { watch(moderationNote, async () => {
await misskeyApi('admin/update-user-note', { userId: props.user.id, text: moderationNote.value }); await misskeyApi('admin/update-user-note', { userId: props.user.id, text: moderationNote.value });
}); });
@ -266,6 +270,7 @@ const style = computed(() => {
}); });
const age = computed(() => { const age = computed(() => {
if (props.user.birthday == null) return 0;
return calcAge(props.user.birthday); return calcAge(props.user.birthday);
}); });