This commit is contained in:
anatawa12 2026-02-02 00:19:37 +09:00 committed by GitHub
commit 9b95853947
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 86 additions and 7 deletions

View File

@ -14,6 +14,7 @@
- Enhance: ウィジェットの表示設定をプレビューを見ながら行えるように
- Enhance: ウィジェットの設定項目のラベルの多言語対応
- Enhance: 画面幅が広いときにメディアを横並びで表示できるようにするオプションを追加
- Enhance: センシティブチャンネルの投稿が多くの場所で畳まれるようになりました
- Enhance: パフォーマンスの向上
- Fix: ドライブクリーナーでファイルを削除しても画面に反映されない問題を修正 #16061
- Fix: 非ログイン時にログインを求めるダイアログが表示された後にダイアログのぼかしが解除されず操作不能になることがある問題を修正
@ -55,6 +56,7 @@ v2025.12.0で行われた「configの`trustProxy`のデフォルト値を`false`
- Fix: デッキUIでメニュー位置を下にしているとプロファイル削除ボタンが表示されないのを修正
- Fix: 一部のUnicode絵文字のリアクションがボタンにならない問題を修正
### Server
- Enhance: Misskey内部でのIPアドレスペースでのレートリミットを無効化できるように
- リバースプロキシやCDNなど別のレイヤで別途レートリミットを設定する場合や、ローカルでのテスト用途等として利用することを想定しています。

View File

@ -840,6 +840,8 @@ emailNotification: "メール通知"
publish: "公開"
inChannelSearch: "チャンネル内検索"
useReactionPickerForContextMenu: "右クリックでリアクションピッカーを開く"
collapseSensitiveChannel: "センシティブチャンネルの投稿を隠す"
collapseSensitiveChannelDescription: "そのチャンネル内・通知・ホームタイムラインなど、一部の場所では引き続き表示されます。"
typingUsers: "{users}が入力中"
jumpToSpecifiedDate: "特定の日付にジャンプ"
showingPastTimeline: "過去のタイムラインを表示しています"
@ -1282,6 +1284,7 @@ backToTitle: "タイトルへ"
hemisphere: "お住まいの地域"
withSensitive: "センシティブなファイルを含むノートを表示"
userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿"
userSaysSomethingInSensitiveChannel: "{name}のセンシティブチャンネルでの投稿"
enableHorizontalSwipe: "スワイプしてタブを切り替える"
loading: "読み込み中"
surrender: "やめる"

View File

@ -169,6 +169,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkA>
</template>
</I18n>
<I18n v-else-if="muted === 'sensitiveChannel'" :src="i18n.ts.userSaysSomethingInSensitiveChannel" tag="small">
<template #name>
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
<MkUserName :user="appearNote.user"/>
</MkA>
</template>
</I18n>
<I18n v-else-if="showSoftWordMutedWord !== true" :src="i18n.ts.userSaysSomething" tag="small">
<template #name>
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
@ -261,6 +268,9 @@ const emit = defineEmits<{
(ev: 'removeReaction', emoji: string): void;
}>();
// for some timelines, like home timeline which only shows the following channels,
// we never collapse sensitive channel notes so we allow inject to override the preference
const collapseSensitiveChannelContext = inject(DI.collapseSensitiveChannel, true);
const inTimeline = inject<boolean>('inTimeline', false);
const tl_withSensitive = inject<Ref<boolean>>('tl_withSensitive', ref(true));
const inChannel = inject('inChannel', null);
@ -330,9 +340,9 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
/* eslint-disable no-redeclare */
/** checkOnlyでは純粋なワードミュート結果をbooleanで返却する */
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly?: false): Array<string | string[]> | false | 'sensitiveMute';
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly?: false): Array<string | string[]> | false | 'sensitiveMute' | 'sensitiveChannel';
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): Array<string | string[]> | boolean | 'sensitiveMute' {
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): Array<string | string[]> | boolean | 'sensitiveMute' | 'sensitiveChannel' {
if (mutedWords != null) {
const result = checkWordMute(noteToCheck, $i, mutedWords);
if (Array.isArray(result)) {
@ -352,6 +362,20 @@ function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string
if (checkOnly) return false;
if (noteToCheck.channel?.isSensitive) {
if (prefer.s.collapseSensitiveChannel) {
switch (collapseSensitiveChannelContext) {
case true: return 'sensitiveChannel';
case 'renote-only':
if (note.channel?.id !== appearNote.channel?.id) {
return 'sensitiveChannel';
}
break;
case false:
break;
}
}
}
if (inTimeline && tl_withSensitive.value === false && noteToCheck.files?.some((v) => v.isSensitive)) {
return 'sensitiveMute';
}

View File

@ -18,4 +18,5 @@ export const DI = {
mfmEmojiReactCallback: Symbol() as InjectionKey<(emoji: string) => void>,
inModal: Symbol() as InjectionKey<boolean>,
inAppSearchMarkerId: Symbol() as InjectionKey<Ref<string | null>>,
collapseSensitiveChannel: Symbol() as InjectionKey<boolean | 'renote-only'>,
};

View File

@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, watch, ref, markRaw, shallowRef } from 'vue';
import { computed, watch, ref, markRaw, shallowRef, provide } from 'vue';
import * as Misskey from 'misskey-js';
import { url } from '@@/js/config.js';
import { useInterval } from '@@/js/use-interval.js';
@ -99,6 +99,7 @@ import { notesSearchAvailable } from '@/utility/check-permissions.js';
import { miLocalStorage } from '@/local-storage.js';
import { useRouter } from '@/router.js';
import { Paginator } from '@/utility/paginator.js';
import { DI } from '@/di.js';
const router = useRouter();
@ -106,6 +107,9 @@ const props = defineProps<{
channelId: string;
}>();
//
provide(DI.collapseSensitiveChannel, false);
const tab = ref('overview');
const channel = ref<Misskey.entities.Channel | null>(null);

View File

@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, markRaw, ref } from 'vue';
import { computed, markRaw, provide, ref } from 'vue';
import { notificationTypes } from 'misskey-js';
import type { PageHeaderItem } from '@/types/page-header.js';
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
@ -29,11 +29,15 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { Paginator } from '@/utility/paginator.js';
import { DI } from '@/di';
const tab = ref('all');
const includeTypes = ref<string[] | null>(null);
const excludeTypes = computed(() => includeTypes.value ? notificationTypes.filter(t => !includeTypes.value!.includes(t)) : null);
// 稿
provide(DI.collapseSensitiveChannel, false);
const mentionsPaginator = markRaw(new Paginator('notes/mentions', {
limit: 10,
}));

View File

@ -244,6 +244,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch>
</MkPreferenceContainer>
</SearchMarker>
<SearchMarker :keywords="['collapse', 'sensitive', 'channel', 'fold']">
<MkPreferenceContainer k="collapseSensitiveChannel">
<MkSwitch v-model="collapseSensitiveChannel">
<template #label><SearchLabel>{{ i18n.ts.collapseSensitiveChannel }}</SearchLabel></template>
<template #caption>{{ i18n.ts.collapseSensitiveChannelDescription }}</template>
</MkSwitch>
</MkPreferenceContainer>
</SearchMarker>
</div>
<SearchMarker :keywords="['reaction', 'size', 'scale', 'display']">
@ -911,6 +920,7 @@ const showReactionsCount = prefer.model('showReactionsCount');
const enableQuickAddMfmFunction = prefer.model('enableQuickAddMfmFunction');
const forceShowAds = prefer.model('forceShowAds');
const loadRawImages = prefer.model('loadRawImages');
const collapseSensitiveChannel = prefer.model('collapseSensitiveChannel');
const imageNewTab = prefer.model('imageNewTab');
const showFixedPostForm = prefer.model('showFixedPostForm');
const showFixedPostFormInChannel = prefer.model('showFixedPostFormInChannel');

View File

@ -45,9 +45,13 @@ import { deepMerge } from '@/utility/merge.js';
import { miLocalStorage } from '@/local-storage.js';
import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
import { prefer } from '@/preferences.js';
import { DI } from '@/di.js';
const tlComponent = useTemplateRef('tlComponent');
//
provide(DI.collapseSensitiveChannel, 'renote-only');
type TimelinePageSrc = BasicTimelineType | `list:${string}`;
const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global');

View File

@ -209,6 +209,9 @@ export const PREF_DEF = definePreferences({
nsfw: {
default: 'respect' as 'respect' | 'force' | 'ignore',
},
collapseSensitiveChannel: {
default: true,
},
highlightSensitiveMedia: {
default: false,
},

View File

@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, ref, shallowRef, watch, useTemplateRef } from 'vue';
import { onMounted, ref, shallowRef, watch, useTemplateRef, provide } from 'vue';
import * as Misskey from 'misskey-js';
import XColumn from './column.vue';
import type { Column } from '@/deck.js';
@ -33,12 +33,16 @@ import { favoritedChannelsCache } from '@/cache.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
import { DI } from '@/di.js';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
//
provide(DI.collapseSensitiveChannel, false);
const timeline = useTemplateRef('timeline');
const channel = shallowRef<Misskey.entities.Channel>();
const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });

View File

@ -12,19 +12,23 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { defineAsyncComponent, useTemplateRef } from 'vue';
import { defineAsyncComponent, provide, useTemplateRef } from 'vue';
import XColumn from './column.vue';
import type { Column } from '@/deck.js';
import { updateColumn } from '@/deck.js';
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { DI } from '@/di.js';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
// 稿
provide(DI.collapseSensitiveChannel, false);
const notificationsComponent = useTemplateRef('notificationsComponent');
async function func() {

View File

@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, watch, ref, useTemplateRef, computed } from 'vue';
import { onMounted, watch, ref, useTemplateRef, computed, provide } from 'vue';
import XColumn from './column.vue';
import type { Column } from '@/deck.js';
import type { MenuItem } from '@/types/menu.js';
@ -44,12 +44,16 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { hasWithReplies, isAvailableBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
import { DI } from '@/di.js';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
//
provide(DI.collapseSensitiveChannel, 'renote-only');
const timeline = useTemplateRef('timeline');
const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });

View File

@ -3372,6 +3372,14 @@ export interface Locale extends ILocale {
*
*/
"useReactionPickerForContextMenu": string;
/**
* 稿
*/
"collapseSensitiveChannel": string;
/**
*
*/
"collapseSensitiveChannelDescription": string;
/**
* {users}
*/
@ -5140,6 +5148,10 @@ export interface Locale extends ILocale {
* {name}稿
*/
"userSaysSomethingSensitive": ParameterizedString<"name">;
/**
* {name}稿
*/
"userSaysSomethingInSensitiveChannel": ParameterizedString<"name">;
/**
*
*/