mirror of
https://github.com/kokonect-link/cherrypick
synced 2025-01-18 15:53:10 +09:00
enhance(frontend/backend): 예약된 노트 게시에 실패할 경우 사용자에게 알림 ([penginn-net/kokonect@a0e47980](a0e4798047
))
This commit is contained in:
parent
d4ab680078
commit
d415d091f1
@ -47,6 +47,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024110](CHANG
|
||||
- 기간은 최소 `1`일부터 최대 `30`일까지 설정할 수 있습니다.
|
||||
- `0`일로 설정하면 최소 기간인 `1`일로 설정되며, `30`일을 초과하는 값을 입력하면 최대 기간인 `30`일로 설정됩니다.
|
||||
- 여기서 설정된 기간은 `초대제로 전환` 옵션과 `공개 노트 허용` 옵션 모두에 적용됩니다.
|
||||
- Enhance: 예약된 노트 게시에 실패할 경우 사용자에게 알림 ([penginn-net/kokonect@a0e47980](https://github.com/penginn-net/kokonect/commit/a0e47980470b49e79e84ff3b7ccaf2b4502928c8))
|
||||
|
||||
### Client
|
||||
- Enhance: 미디어 그리드 레이아옷 조정
|
||||
|
@ -2820,12 +2820,19 @@ _notification:
|
||||
achievementEarned: "Achievement unlocked"
|
||||
exportCompleted: "The export has been completed"
|
||||
login: "Sign In"
|
||||
scheduleNote: "Scheduled note posting failed"
|
||||
test: "Notification test"
|
||||
app: "Notifications from linked apps"
|
||||
_actions:
|
||||
followBack: "followed you back"
|
||||
reply: "Reply"
|
||||
renote: "Renote"
|
||||
_scheduleNote:
|
||||
unknown: "The cause is unknown"
|
||||
renoteTargetNotFound: "Renote target note not found"
|
||||
channelTargetNotFound: "Channel not found"
|
||||
replyTargetNotFound: "Reply target note not found"
|
||||
invalidFilesCount: "No attachments"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "Always show main column"
|
||||
columnAlign: "Align columns"
|
||||
|
26
locales/index.d.ts
vendored
26
locales/index.d.ts
vendored
@ -10989,6 +10989,10 @@ export interface Locale extends ILocale {
|
||||
* ログイン
|
||||
*/
|
||||
"login": string;
|
||||
/**
|
||||
* 予約投稿に失敗
|
||||
*/
|
||||
"scheduleNote": string;
|
||||
/**
|
||||
* 通知のテスト
|
||||
*/
|
||||
@ -11012,6 +11016,28 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"renote": string;
|
||||
};
|
||||
"_scheduleNote": {
|
||||
/**
|
||||
* 原因は不明です
|
||||
*/
|
||||
"unknown": string;
|
||||
/**
|
||||
* 引用元がありません
|
||||
*/
|
||||
"renoteTargetNotFound": string;
|
||||
/**
|
||||
* 対象のチャンネルがありません
|
||||
*/
|
||||
"channelTargetNotFound": string;
|
||||
/**
|
||||
* 返信先がありません
|
||||
*/
|
||||
"replyTargetNotFound": string;
|
||||
/**
|
||||
* 添付ファイルがありません
|
||||
*/
|
||||
"invalidFilesCount": string;
|
||||
};
|
||||
};
|
||||
"_deck": {
|
||||
/**
|
||||
|
@ -2895,6 +2895,7 @@ _notification:
|
||||
achievementEarned: "実績の獲得"
|
||||
exportCompleted: "エクスポートが完了した"
|
||||
login: "ログイン"
|
||||
scheduleNote: "予約投稿に失敗"
|
||||
test: "通知のテスト"
|
||||
app: "連携アプリからの通知"
|
||||
|
||||
@ -2903,6 +2904,13 @@ _notification:
|
||||
reply: "返信"
|
||||
renote: "リノート"
|
||||
|
||||
_scheduleNote:
|
||||
unknown: "原因は不明です"
|
||||
renoteTargetNotFound: "引用元がありません"
|
||||
channelTargetNotFound: "対象のチャンネルがありません"
|
||||
replyTargetNotFound: "返信先がありません"
|
||||
invalidFilesCount: "添付ファイルがありません"
|
||||
|
||||
_deck:
|
||||
alwaysShowMainColumn: "常にメインカラムを表示"
|
||||
columnAlign: "カラムの寄せ"
|
||||
|
@ -2823,12 +2823,19 @@ _notification:
|
||||
achievementEarned: "도전 과제 획득"
|
||||
exportCompleted: "내보내기를 완료함"
|
||||
login: "로그인"
|
||||
scheduleNote: "게시가 예약된 노트의 게시가 실패함"
|
||||
test: "알림 테스트"
|
||||
app: "연동된 앱을 통한 알림"
|
||||
_actions:
|
||||
followBack: "팔로우"
|
||||
reply: "답글"
|
||||
renote: "리노트"
|
||||
_scheduleNote:
|
||||
unknown: "알 수 없는 오류가 발생했어요"
|
||||
renoteTargetNotFound: "인용할 대상이 없어요"
|
||||
channelTargetNotFound: "해당 채널이 존재하지 않아요"
|
||||
replyTargetNotFound: "답장할 대상이 없어요"
|
||||
invalidFilesCount: "첨부 파일이 없어요"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "메인 칼럼 항상 표시"
|
||||
columnAlign: "칼럼 정렬"
|
||||
|
@ -202,6 +202,9 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
...(notification.type === 'login' ? {
|
||||
ip: notification.userIp,
|
||||
} : {}),
|
||||
...(notification.type === 'scheduleNote' ? {
|
||||
errorType: notification.errorType,
|
||||
} : {}),
|
||||
...(notification.type === 'app' ? {
|
||||
body: notification.customBody,
|
||||
header: notification.customHeader,
|
||||
|
@ -98,6 +98,11 @@ export type MiNotification = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
userIp: string;
|
||||
} | {
|
||||
type: 'scheduleNote';
|
||||
id: string;
|
||||
createdAt: string;
|
||||
errorType: string;
|
||||
} | {
|
||||
type: 'app';
|
||||
id: string;
|
||||
|
@ -336,6 +336,20 @@ export const packedNotificationSchema = {
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['scheduleNote'],
|
||||
},
|
||||
errorType: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
@ -9,6 +9,7 @@ import { bindThis } from '@/decorators.js';
|
||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||
import type { ChannelsRepository, DriveFilesRepository, MiDriveFile, NoteScheduleRepository, NotesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { ScheduleNotePostJobData } from '../types.js';
|
||||
@ -32,6 +33,7 @@ export class ScheduleNotePostProcessorService {
|
||||
|
||||
private noteCreateService: NoteCreateService,
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
private notificationService: NotificationService,
|
||||
) {
|
||||
this.logger = this.queueLoggerService.logger.createSubLogger('schedule-note-post');
|
||||
}
|
||||
@ -72,6 +74,25 @@ export class ScheduleNotePostProcessorService {
|
||||
//キューに積んだときは有った物が消滅してたら予約投稿をキャンセルする
|
||||
this.logger.warn('cancel schedule note');
|
||||
await this.noteScheduleRepository.remove(data);
|
||||
|
||||
if (data.userId && me) { //ユーザーが特定できる場合に失敗を通知
|
||||
let errorType = 'unknown';
|
||||
if (note.renote && !renote) {
|
||||
errorType = 'renoteTargetNotFound';
|
||||
}
|
||||
if (note.reply && !reply) {
|
||||
errorType = 'replyTargetNotFound';
|
||||
}
|
||||
if (note.channel && !channel) {
|
||||
errorType = 'channelTargetNotFound';
|
||||
}
|
||||
if (note.files.length !== files.length) {
|
||||
errorType = 'invalidFilesCount';
|
||||
}
|
||||
this.notificationService.createNotification(data.userId, 'scheduleNote', {
|
||||
errorType,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
await this.noteCreateService.create(me, {
|
||||
|
@ -38,6 +38,7 @@ export const notificationTypes = [
|
||||
'achievementEarned',
|
||||
'exportCompleted',
|
||||
'login',
|
||||
'scheduleNote',
|
||||
'app',
|
||||
'test',
|
||||
] as const;
|
||||
|
@ -3004,7 +3004,7 @@ type Notification_2 = components['schemas']['Notification'];
|
||||
type NotificationsCreateRequest = operations['notifications___create']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned"];
|
||||
export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned", "scheduleNote"];
|
||||
|
||||
// @public (undocumented)
|
||||
export function nyaize(text: string): string;
|
||||
|
@ -4715,6 +4715,14 @@ export type components = {
|
||||
/** @enum {string} */
|
||||
type: 'login';
|
||||
ip: string;
|
||||
} | {
|
||||
/** Format: id */
|
||||
id: string;
|
||||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
/** @enum {string} */
|
||||
type: 'scheduleNote';
|
||||
errorType: string;
|
||||
} | ({
|
||||
/** Format: id */
|
||||
id: string;
|
||||
@ -19667,8 +19675,8 @@ export type operations = {
|
||||
untilId?: string;
|
||||
/** @default true */
|
||||
markAsRead?: boolean;
|
||||
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote')[];
|
||||
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote')[];
|
||||
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'pollVote')[];
|
||||
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'pollVote')[];
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -19735,8 +19743,8 @@ export type operations = {
|
||||
untilId?: string;
|
||||
/** @default true */
|
||||
markAsRead?: boolean;
|
||||
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
|
||||
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
|
||||
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
|
||||
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ import type {
|
||||
UserLite,
|
||||
} from './autogen/models.js';
|
||||
|
||||
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned'] as const;
|
||||
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned', 'scheduleNote'] as const;
|
||||
|
||||
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
|
||||
|
||||
|
@ -70,6 +70,7 @@ export const notificationTypes = [
|
||||
'achievementEarned',
|
||||
'exportCompleted',
|
||||
'login',
|
||||
'scheduleNote',
|
||||
'test',
|
||||
'app',
|
||||
] as const;
|
||||
|
@ -12,6 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
|
||||
<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
|
||||
<div v-else-if="notification.type === 'note:grouped'" :class="[$style.icon, $style.icon_noteGroup]"><i class="ti ti-pencil" style="line-height: 1;"></i></div>
|
||||
<div v-else-if="notification.type === 'scheduleNote'" :class="[$style.icon, $style.icon_scheduleNote]"><i class="ti ti-alert-triangle" style="line-height: 1;"></i></div>
|
||||
<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
|
||||
<MkAvatar v-else-if="'user' in notification" :class="$style.icon" :user="notification.user" link preview/>
|
||||
<img v-else-if="'icon' in notification && notification.icon != null" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/>
|
||||
@ -64,6 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
|
||||
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
|
||||
<span v-else-if="notification.type === 'login'">{{ i18n.ts._notification.login }}</span>
|
||||
<span v-else-if="notification.type === 'scheduleNote'">{{ i18n.ts._notification._types.scheduleNote }}</span>
|
||||
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
|
||||
<span v-else-if="notification.type === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span>
|
||||
<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
|
||||
@ -141,6 +143,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectGroupInvitation()"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else-if="notification.type === 'scheduleNote'" :class="$style.text">{{ i18n.ts._notification._scheduleNote[notification.errorType] }}</span>
|
||||
<span v-else-if="notification.type === 'test'" :class="$style.text">{{ i18n.ts._notification.notificationWillBeDisplayedLikeThis }}</span>
|
||||
<span v-else-if="notification.type === 'app'" :class="$style.text">
|
||||
<Mfm :text="notification.body" :nowrap="false"/>
|
||||
@ -285,7 +288,8 @@ const rejectGroupInvitation = () => {
|
||||
.icon_reactionGroup,
|
||||
.icon_reactionGroupHeart,
|
||||
.icon_renoteGroup,
|
||||
.icon_noteGroup {
|
||||
.icon_noteGroup,
|
||||
.icon_scheduleNote {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
@ -312,6 +316,13 @@ const rejectGroupInvitation = () => {
|
||||
background: var(--eventRenote);
|
||||
}
|
||||
|
||||
.icon_scheduleNote {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: var(--warn);
|
||||
background: var(--eventOther);
|
||||
}
|
||||
|
||||
.icon_app {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
@ -262,6 +262,12 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
|
||||
data,
|
||||
}];
|
||||
|
||||
case 'scheduleNote':
|
||||
return [i18n.ts._notification._types.scheduleNote, {
|
||||
body: data.body.errorType,
|
||||
data,
|
||||
}];
|
||||
|
||||
case 'app':
|
||||
return [data.body.header ?? data.body.body, {
|
||||
body: data.body.header ? data.body.body : '',
|
||||
|
Loading…
Reference in New Issue
Block a user