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:
かっこかり 2024-08-17 11:28:22 +09:00 committed by GitHub
parent 61cc3b5642
commit 059eb6d379
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 73 additions and 46 deletions

View File

@ -12,6 +12,7 @@
- Fix: iOSでユーザー名などがリンクとして誤検知される現象を抑制
- Fix: mCaptchaを使用していてもbotプロテクションに関する警告が消えないのを修正
- Fix: ユーザーのモデレーションページにおいてユーザー名にドットが入っているとシステムアカウントとして表示されてしまう問題を修正
- Fix: 特定の条件下でノートの削除ボタンが出ないのを修正
### Server
- Enhance: 凍結されたアカウントのフォローリクエストを表示しないように

View File

@ -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);

View File

@ -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);

View 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;
}

View File

@ -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[] = [];

View File

@ -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

View File

@ -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;

View File

@ -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 };

View 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
);
}