fix(frontend): リノートの判定が甘いのを修正 (#14396)
* fix(frontend): リノートの判定が甘いのを修正 * fix * Update Changelog * fix * use type assertion * fix + add comments * lint * misskey-jsに移動 * PureRenote -> Renote * isRenote -> isPureRenote
This commit is contained in:
		
							parent
							
								
									61cc3b5642
								
							
						
					
					
						commit
						059eb6d379
					
				
					 9 changed files with 73 additions and 46 deletions
				
			
		| 
						 | 
				
			
			@ -12,6 +12,7 @@
 | 
			
		|||
- Fix: iOSでユーザー名などがリンクとして誤検知される現象を抑制
 | 
			
		||||
- Fix: mCaptchaを使用していてもbotプロテクションに関する警告が消えないのを修正
 | 
			
		||||
- Fix: ユーザーのモデレーションページにおいてユーザー名にドットが入っているとシステムアカウントとして表示されてしまう問題を修正
 | 
			
		||||
- Fix: 特定の条件下でノートの削除ボタンが出ないのを修正
 | 
			
		||||
 | 
			
		||||
### Server
 | 
			
		||||
- Enhance: 凍結されたアカウントのフォローリクエストを表示しないように
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -200,6 +200,7 @@ import { host } from '@/config.js';
 | 
			
		|||
import { isEnabledUrlPreview } from '@/instance.js';
 | 
			
		||||
import { type Keymap } from '@/scripts/hotkey.js';
 | 
			
		||||
import { focusPrev, focusNext } from '@/scripts/focus.js';
 | 
			
		||||
import { getAppearNote } from '@/scripts/get-appear-note.js';
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<{
 | 
			
		||||
	note: Misskey.entities.Note;
 | 
			
		||||
| 
						 | 
				
			
			@ -242,14 +243,7 @@ if (noteViewInterruptors.length > 0) {
 | 
			
		|||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const isRenote = (
 | 
			
		||||
	note.value.renote != null &&
 | 
			
		||||
	note.value.reply == null &&
 | 
			
		||||
	note.value.text == null &&
 | 
			
		||||
	note.value.cw == null &&
 | 
			
		||||
	note.value.fileIds && note.value.fileIds.length === 0 &&
 | 
			
		||||
	note.value.poll == null
 | 
			
		||||
);
 | 
			
		||||
const isRenote = Misskey.note.isPureRenote(note.value);
 | 
			
		||||
 | 
			
		||||
const rootEl = shallowRef<HTMLElement>();
 | 
			
		||||
const menuButton = shallowRef<HTMLElement>();
 | 
			
		||||
| 
						 | 
				
			
			@ -257,7 +251,7 @@ const renoteButton = shallowRef<HTMLElement>();
 | 
			
		|||
const renoteTime = shallowRef<HTMLElement>();
 | 
			
		||||
const reactButton = shallowRef<HTMLElement>();
 | 
			
		||||
const clipButton = shallowRef<HTMLElement>();
 | 
			
		||||
const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
 | 
			
		||||
const appearNote = computed(() => getAppearNote(note.value));
 | 
			
		||||
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
 | 
			
		||||
const isMyRenote = $i && ($i.id === note.value.userId);
 | 
			
		||||
const showContent = ref(false);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -235,6 +235,7 @@ import MkPagination, { type Paging } from '@/components/MkPagination.vue';
 | 
			
		|||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
 | 
			
		||||
import MkButton from '@/components/MkButton.vue';
 | 
			
		||||
import { isEnabledUrlPreview } from '@/instance.js';
 | 
			
		||||
import { getAppearNote } from '@/scripts/get-appear-note.js';
 | 
			
		||||
import { type Keymap } from '@/scripts/hotkey.js';
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<{
 | 
			
		||||
| 
						 | 
				
			
			@ -267,14 +268,7 @@ if (noteViewInterruptors.length > 0) {
 | 
			
		|||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const isRenote = (
 | 
			
		||||
	note.value.renote != null &&
 | 
			
		||||
	note.value.reply == null &&
 | 
			
		||||
	note.value.text == null &&
 | 
			
		||||
	note.value.cw == null &&
 | 
			
		||||
	note.value.fileIds && note.value.fileIds.length === 0 &&
 | 
			
		||||
	note.value.poll == null
 | 
			
		||||
);
 | 
			
		||||
const isRenote = Misskey.note.isPureRenote(note.value);
 | 
			
		||||
 | 
			
		||||
const rootEl = shallowRef<HTMLElement>();
 | 
			
		||||
const menuButton = shallowRef<HTMLElement>();
 | 
			
		||||
| 
						 | 
				
			
			@ -282,7 +276,7 @@ const renoteButton = shallowRef<HTMLElement>();
 | 
			
		|||
const renoteTime = shallowRef<HTMLElement>();
 | 
			
		||||
const reactButton = shallowRef<HTMLElement>();
 | 
			
		||||
const clipButton = shallowRef<HTMLElement>();
 | 
			
		||||
const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
 | 
			
		||||
const appearNote = computed(() => getAppearNote(note.value));
 | 
			
		||||
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
 | 
			
		||||
const isMyRenote = $i && ($i.id === note.value.userId);
 | 
			
		||||
const showContent = ref(false);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								packages/frontend/src/scripts/get-appear-note.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/frontend/src/scripts/get-appear-note.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
/*
 | 
			
		||||
 * SPDX-FileCopyrightText: syuilo and misskey-project
 | 
			
		||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import * as Misskey from 'misskey-js';
 | 
			
		||||
 | 
			
		||||
export function getAppearNote(note: Misskey.entities.Note) {
 | 
			
		||||
	return Misskey.note.isPureRenote(note) ? note.renote : note;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ import { clipsCache, favoritedChannelsCache } from '@/cache.js';
 | 
			
		|||
import { MenuItem } from '@/types/menu.js';
 | 
			
		||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
 | 
			
		||||
import { isSupportShare } from '@/scripts/navigator.js';
 | 
			
		||||
import { getAppearNote } from '@/scripts/get-appear-note.js';
 | 
			
		||||
 | 
			
		||||
export async function getNoteClipMenu(props: {
 | 
			
		||||
	note: Misskey.entities.Note;
 | 
			
		||||
| 
						 | 
				
			
			@ -34,14 +35,7 @@ export async function getNoteClipMenu(props: {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const isRenote = (
 | 
			
		||||
		props.note.renote != null &&
 | 
			
		||||
		props.note.text == null &&
 | 
			
		||||
		props.note.fileIds.length === 0 &&
 | 
			
		||||
		props.note.poll == null
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	const appearNote = isRenote ? props.note.renote as Misskey.entities.Note : props.note;
 | 
			
		||||
	const appearNote = getAppearNote(props.note);
 | 
			
		||||
 | 
			
		||||
	const clips = await clipsCache.fetch();
 | 
			
		||||
	const menu: MenuItem[] = [...clips.map(clip => ({
 | 
			
		||||
| 
						 | 
				
			
			@ -164,14 +158,7 @@ export function getNoteMenu(props: {
 | 
			
		|||
	isDeleted: Ref<boolean>;
 | 
			
		||||
	currentClip?: Misskey.entities.Clip;
 | 
			
		||||
}) {
 | 
			
		||||
	const isRenote = (
 | 
			
		||||
		props.note.renote != null &&
 | 
			
		||||
		props.note.text == null &&
 | 
			
		||||
		props.note.fileIds.length === 0 &&
 | 
			
		||||
		props.note.poll == null
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	const appearNote = isRenote ? props.note.renote as Misskey.entities.Note : props.note;
 | 
			
		||||
	const appearNote = getAppearNote(props.note);
 | 
			
		||||
 | 
			
		||||
	const cleanups = [] as (() => void)[];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -248,6 +235,7 @@ export function getNoteMenu(props: {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	async function unclip(): Promise<void> {
 | 
			
		||||
		if (!props.currentClip) return;
 | 
			
		||||
		os.apiWithDialog('clips/remove-note', { clipId: props.currentClip.id, noteId: appearNote.id });
 | 
			
		||||
		props.isDeleted.value = true;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -267,8 +255,8 @@ export function getNoteMenu(props: {
 | 
			
		|||
 | 
			
		||||
	function share(): void {
 | 
			
		||||
		navigator.share({
 | 
			
		||||
			title: i18n.tsx.noteOf({ user: appearNote.user.name }),
 | 
			
		||||
			text: appearNote.text,
 | 
			
		||||
			title: i18n.tsx.noteOf({ user: appearNote.user.name ?? appearNote.user.username }),
 | 
			
		||||
			text: appearNote.text ?? '',
 | 
			
		||||
			url: `${url}/notes/${appearNote.id}`,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -509,14 +497,7 @@ export function getRenoteMenu(props: {
 | 
			
		|||
	renoteButton: ShallowRef<HTMLElement | undefined>;
 | 
			
		||||
	mock?: boolean;
 | 
			
		||||
}) {
 | 
			
		||||
	const isRenote = (
 | 
			
		||||
		props.note.renote != null &&
 | 
			
		||||
		props.note.text == null &&
 | 
			
		||||
		props.note.fileIds.length === 0 &&
 | 
			
		||||
		props.note.poll == null
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	const appearNote = isRenote ? props.note.renote as Misskey.entities.Note : props.note;
 | 
			
		||||
	const appearNote = getAppearNote(props.note);
 | 
			
		||||
 | 
			
		||||
	const channelRenoteItems: MenuItem[] = [];
 | 
			
		||||
	const normalRenoteItems: MenuItem[] = [];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1172,6 +1172,7 @@ declare namespace entities {
 | 
			
		|||
    export {
 | 
			
		||||
        ID,
 | 
			
		||||
        DateString,
 | 
			
		||||
        PureRenote,
 | 
			
		||||
        PageEvent,
 | 
			
		||||
        ModerationLog,
 | 
			
		||||
        ServerStats,
 | 
			
		||||
| 
						 | 
				
			
			@ -2277,6 +2278,9 @@ type ISigninHistoryRequest = operations['i___signin-history']['requestBody']['co
 | 
			
		|||
// @public (undocumented)
 | 
			
		||||
type ISigninHistoryResponse = operations['i___signin-history']['responses']['200']['content']['application/json'];
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
function isPureRenote(note: Note): note is PureRenote;
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
type IUnpinRequest = operations['i___unpin']['requestBody']['content']['application/json'];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2513,6 +2517,13 @@ type MyAppsResponse = operations['my___apps']['responses']['200']['content']['ap
 | 
			
		|||
// @public (undocumented)
 | 
			
		||||
type Note = components['schemas']['Note'];
 | 
			
		||||
 | 
			
		||||
declare namespace note {
 | 
			
		||||
    export {
 | 
			
		||||
        isPureRenote
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
export { note }
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
type NoteFavorite = components['schemas']['NoteFavorite'];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2753,6 +2764,15 @@ type PinnedUsersResponse = operations['pinned-users']['responses']['200']['conte
 | 
			
		|||
// @public (undocumented)
 | 
			
		||||
type PromoReadRequest = operations['promo___read']['requestBody']['content']['application/json'];
 | 
			
		||||
 | 
			
		||||
// Warning: (ae-forgotten-export) The symbol "AllNullRecord" needs to be exported by the entry point index.d.ts
 | 
			
		||||
// Warning: (ae-forgotten-export) The symbol "NonNullableRecord" needs to be exported by the entry point index.d.ts
 | 
			
		||||
//
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
type PureRenote = Omit<Note, 'renote' | 'renoteId' | 'reply' | 'replyId' | 'text' | 'cw' | 'files' | 'fileIds' | 'poll'> & AllNullRecord<Pick<Note, 'reply' | 'replyId' | 'text' | 'cw' | 'poll'>> & {
 | 
			
		||||
    files: [];
 | 
			
		||||
    fileIds: [];
 | 
			
		||||
} & NonNullableRecord<Pick<Note, 'renote' | 'renoteId'>>;
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
type QueueCount = components['schemas']['QueueCount'];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3232,7 +3252,7 @@ type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody']['
 | 
			
		|||
 | 
			
		||||
// Warnings were encountered during analysis:
 | 
			
		||||
//
 | 
			
		||||
// src/entities.ts:35:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
 | 
			
		||||
// src/entities.ts:49:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
 | 
			
		||||
// src/streaming.types.ts:220:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
 | 
			
		||||
// src/streaming.types.ts:230:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ import {
 | 
			
		|||
	Announcement,
 | 
			
		||||
	EmojiDetailed,
 | 
			
		||||
	MeDetailed,
 | 
			
		||||
	Note,
 | 
			
		||||
	Page,
 | 
			
		||||
	Role,
 | 
			
		||||
	RolePolicies,
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +17,19 @@ export * from './autogen/models.js';
 | 
			
		|||
export type ID = string;
 | 
			
		||||
export type DateString = string;
 | 
			
		||||
 | 
			
		||||
type NonNullableRecord<T> = {
 | 
			
		||||
	[P in keyof T]-?: NonNullable<T[P]>;
 | 
			
		||||
};
 | 
			
		||||
type AllNullRecord<T> = {
 | 
			
		||||
	[P in keyof T]: null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type PureRenote =
 | 
			
		||||
	Omit<Note, 'renote' | 'renoteId' | 'reply' | 'replyId' | 'text' | 'cw' | 'files' | 'fileIds' | 'poll'>
 | 
			
		||||
	& AllNullRecord<Pick<Note, 'reply' | 'replyId' | 'text' | 'cw' | 'poll'>>
 | 
			
		||||
	& { files: []; fileIds: []; }
 | 
			
		||||
	& NonNullableRecord<Pick<Note, 'renote' | 'renoteId'>>;
 | 
			
		||||
 | 
			
		||||
export type PageEvent = {
 | 
			
		||||
	pageId: Page['id'];
 | 
			
		||||
	event: string;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,4 +30,5 @@ export const reversiUpdateKeys = consts.reversiUpdateKeys;
 | 
			
		|||
import * as api from './api.js';
 | 
			
		||||
import * as entities from './entities.js';
 | 
			
		||||
import * as acct from './acct.js';
 | 
			
		||||
export { api, entities, acct };
 | 
			
		||||
import * as note from './note.js';
 | 
			
		||||
export { api, entities, acct, note };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								packages/misskey-js/src/note.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/misskey-js/src/note.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
import type { Note, PureRenote } from './entities.js';
 | 
			
		||||
 | 
			
		||||
export function isPureRenote(note: Note): note is PureRenote {
 | 
			
		||||
	return (
 | 
			
		||||
		note.renote != null &&
 | 
			
		||||
		note.reply == null &&
 | 
			
		||||
		note.text == null &&
 | 
			
		||||
		note.cw == null &&
 | 
			
		||||
		(note.fileIds == null || note.fileIds.length === 0) &&
 | 
			
		||||
		note.poll == null
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue