Compare commits
10 Commits
3c3b98a5ee
...
487482210a
Author | SHA1 | Date |
---|---|---|
かっこかり | 487482210a | |
かっこかり | 3bf63dd9c5 | |
かっこかり | ce95323e49 | |
kakkokari-gtyih | 77b2bb20db | |
かっこかり | 9c27da00ef | |
kakkokari-gtyih | b167d94ba1 | |
kakkokari-gtyih | 162cf4071f | |
kakkokari-gtyih | dacf79191e | |
kakkokari-gtyih | 2444b52892 | |
kakkokari-gtyih | a61e638a3b |
|
@ -1,7 +1,7 @@
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### General
|
### General
|
||||||
-
|
- Feat: データエクスポートが完了した際に通知を発行するように
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能
|
- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能
|
||||||
|
@ -14,6 +14,7 @@
|
||||||
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
|
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
|
||||||
- Fix: 縦横比が極端なカスタム絵文字を表示する際にレイアウトが崩れる箇所があるのを修正
|
- Fix: 縦横比が極端なカスタム絵文字を表示する際にレイアウトが崩れる箇所があるのを修正
|
||||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/725)
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/725)
|
||||||
|
- Fix: 設定変更時のリロード確認ダイアログが複数個表示されることがある問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように
|
- Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように
|
||||||
|
|
|
@ -1352,6 +1352,10 @@ export interface Locale extends ILocale {
|
||||||
* ファイルを追加
|
* ファイルを追加
|
||||||
*/
|
*/
|
||||||
"addFile": string;
|
"addFile": string;
|
||||||
|
/**
|
||||||
|
* ファイルを表示
|
||||||
|
*/
|
||||||
|
"showFile": string;
|
||||||
/**
|
/**
|
||||||
* ドライブは空です
|
* ドライブは空です
|
||||||
*/
|
*/
|
||||||
|
@ -3121,7 +3125,7 @@ export interface Locale extends ILocale {
|
||||||
*/
|
*/
|
||||||
"narrow": string;
|
"narrow": string;
|
||||||
/**
|
/**
|
||||||
* 設定はページリロード後に反映されます。今すぐリロードしますか?
|
* 設定はページリロード後に反映されます。
|
||||||
*/
|
*/
|
||||||
"reloadToApplySetting": string;
|
"reloadToApplySetting": string;
|
||||||
/**
|
/**
|
||||||
|
@ -9177,6 +9181,10 @@ export interface Locale extends ILocale {
|
||||||
* 通知の履歴をリセットする
|
* 通知の履歴をリセットする
|
||||||
*/
|
*/
|
||||||
"flushNotification": string;
|
"flushNotification": string;
|
||||||
|
/**
|
||||||
|
* {x}のエクスポートが完了しました
|
||||||
|
*/
|
||||||
|
"exportOfXCompleted": ParameterizedString<"x">;
|
||||||
"_types": {
|
"_types": {
|
||||||
/**
|
/**
|
||||||
* すべて
|
* すべて
|
||||||
|
@ -9230,6 +9238,14 @@ export interface Locale extends ILocale {
|
||||||
* 実績の獲得
|
* 実績の獲得
|
||||||
*/
|
*/
|
||||||
"achievementEarned": string;
|
"achievementEarned": string;
|
||||||
|
/**
|
||||||
|
* エクスポートが完了した
|
||||||
|
*/
|
||||||
|
"exportCompleted": string;
|
||||||
|
/**
|
||||||
|
* 通知のテスト
|
||||||
|
*/
|
||||||
|
"test": string;
|
||||||
/**
|
/**
|
||||||
* 連携アプリからの通知
|
* 連携アプリからの通知
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -334,6 +334,7 @@ renameFolder: "フォルダー名を変更"
|
||||||
deleteFolder: "フォルダーを削除"
|
deleteFolder: "フォルダーを削除"
|
||||||
folder: "フォルダー"
|
folder: "フォルダー"
|
||||||
addFile: "ファイルを追加"
|
addFile: "ファイルを追加"
|
||||||
|
showFile: "ファイルを表示"
|
||||||
emptyDrive: "ドライブは空です"
|
emptyDrive: "ドライブは空です"
|
||||||
emptyFolder: "フォルダーは空です"
|
emptyFolder: "フォルダーは空です"
|
||||||
unableToDelete: "削除できません"
|
unableToDelete: "削除できません"
|
||||||
|
@ -778,7 +779,7 @@ left: "左"
|
||||||
center: "中央"
|
center: "中央"
|
||||||
wide: "広い"
|
wide: "広い"
|
||||||
narrow: "狭い"
|
narrow: "狭い"
|
||||||
reloadToApplySetting: "設定はページリロード後に反映されます。今すぐリロードしますか?"
|
reloadToApplySetting: "設定はページリロード後に反映されます。"
|
||||||
needReloadToApply: "反映には再起動が必要です。"
|
needReloadToApply: "反映には再起動が必要です。"
|
||||||
showTitlebar: "タイトルバーを表示する"
|
showTitlebar: "タイトルバーを表示する"
|
||||||
clearCache: "キャッシュをクリア"
|
clearCache: "キャッシュをクリア"
|
||||||
|
@ -2426,6 +2427,7 @@ _notification:
|
||||||
renotedBySomeUsers: "{n}人がリノートしました"
|
renotedBySomeUsers: "{n}人がリノートしました"
|
||||||
followedBySomeUsers: "{n}人にフォローされました"
|
followedBySomeUsers: "{n}人にフォローされました"
|
||||||
flushNotification: "通知の履歴をリセットする"
|
flushNotification: "通知の履歴をリセットする"
|
||||||
|
exportOfXCompleted: "{x}のエクスポートが完了しました"
|
||||||
|
|
||||||
_types:
|
_types:
|
||||||
all: "すべて"
|
all: "すべて"
|
||||||
|
@ -2441,6 +2443,8 @@ _notification:
|
||||||
followRequestAccepted: "フォローが受理された"
|
followRequestAccepted: "フォローが受理された"
|
||||||
roleAssigned: "ロールが付与された"
|
roleAssigned: "ロールが付与された"
|
||||||
achievementEarned: "実績の獲得"
|
achievementEarned: "実績の獲得"
|
||||||
|
exportCompleted: "エクスポートが完了した"
|
||||||
|
test: "通知のテスト"
|
||||||
app: "連携アプリからの通知"
|
app: "連携アプリからの通知"
|
||||||
|
|
||||||
_actions:
|
_actions:
|
||||||
|
|
|
@ -123,11 +123,14 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
if (antenna.src === 'home') {
|
if (antenna.src === 'home') {
|
||||||
// TODO
|
// TODO
|
||||||
} else if (antenna.src === 'list') {
|
} else if (antenna.src === 'list') {
|
||||||
const listUsers = (await this.userListMembershipsRepository.findBy({
|
if (antenna.userListId == null) return false;
|
||||||
userListId: antenna.userListId!,
|
const exists = await this.userListMembershipsRepository.exists({
|
||||||
})).map(x => x.userId);
|
where: {
|
||||||
|
userListId: antenna.userListId,
|
||||||
if (!listUsers.includes(note.userId)) return false;
|
userId: note.userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!exists) return false;
|
||||||
} else if (antenna.src === 'users') {
|
} else if (antenna.src === 'users') {
|
||||||
const accts = antenna.users.map(x => {
|
const accts = antenna.users.map(x => {
|
||||||
const { username, host } = Acct.parse(x);
|
const { username, host } = Acct.parse(x);
|
||||||
|
|
|
@ -162,6 +162,10 @@ export class NotificationEntityService implements OnModuleInit {
|
||||||
...(notification.type === 'achievementEarned' ? {
|
...(notification.type === 'achievementEarned' ? {
|
||||||
achievement: notification.achievement,
|
achievement: notification.achievement,
|
||||||
} : {}),
|
} : {}),
|
||||||
|
...(notification.type === 'exportCompleted' ? {
|
||||||
|
exportedEntity: notification.exportedEntity,
|
||||||
|
fileId: notification.fileId,
|
||||||
|
} : {}),
|
||||||
...(notification.type === 'app' ? {
|
...(notification.type === 'app' ? {
|
||||||
body: notification.customBody,
|
body: notification.customBody,
|
||||||
header: notification.customHeader,
|
header: notification.customHeader,
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { MiUser } from './User.js';
|
||||||
import { MiNote } from './Note.js';
|
import { MiNote } from './Note.js';
|
||||||
import { MiAccessToken } from './AccessToken.js';
|
import { MiAccessToken } from './AccessToken.js';
|
||||||
import { MiRole } from './Role.js';
|
import { MiRole } from './Role.js';
|
||||||
|
import { MiDriveFile } from './DriveFile.js';
|
||||||
|
import { exportableEntities } from '@/types.js';
|
||||||
|
|
||||||
export type MiNotification = {
|
export type MiNotification = {
|
||||||
type: 'note';
|
type: 'note';
|
||||||
|
@ -77,6 +79,12 @@ export type MiNotification = {
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
achievement: string;
|
achievement: string;
|
||||||
|
} | {
|
||||||
|
type: 'exportCompleted';
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
exportedEntity: typeof exportableEntities[number];
|
||||||
|
fileId: MiDriveFile['id'];
|
||||||
} | {
|
} | {
|
||||||
type: 'app';
|
type: 'app';
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
|
import { ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
|
||||||
import { notificationTypes } from '@/types.js';
|
import { notificationTypes, exportableEntities } from '@/types.js';
|
||||||
|
|
||||||
const baseSchema = {
|
const baseSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -298,6 +298,26 @@ export const packedNotificationSchema = {
|
||||||
enum: ACHIEVEMENT_TYPES,
|
enum: ACHIEVEMENT_TYPES,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
...baseSchema.properties,
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
enum: ['exportCompleted'],
|
||||||
|
},
|
||||||
|
exportedEntity: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
enum: exportableEntities,
|
||||||
|
},
|
||||||
|
fileId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { createTemp } from '@/misc/create-temp.js';
|
import { createTemp } from '@/misc/create-temp.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type { DBExportAntennasData } from '../types.js';
|
import type { DBExportAntennasData } from '../types.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
|
@ -35,6 +36,7 @@ export class ExportAntennasProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('export-antennas');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-antennas');
|
||||||
}
|
}
|
||||||
|
@ -95,6 +97,11 @@ export class ExportAntennasProcessorService {
|
||||||
const fileName = 'antennas-' + DateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
|
const fileName = 'antennas-' + DateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
|
||||||
this.logger.succ('Exported to: ' + driveFile.id);
|
this.logger.succ('Exported to: ' + driveFile.id);
|
||||||
|
|
||||||
|
this.notificationService.createNotification(user.id, 'exportCompleted', {
|
||||||
|
exportedEntity: 'antenna',
|
||||||
|
fileId: driveFile.id,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import type Logger from '@/logger.js';
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import { createTemp } from '@/misc/create-temp.js';
|
import { createTemp } from '@/misc/create-temp.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
|
@ -30,6 +31,7 @@ export class ExportBlockingProcessorService {
|
||||||
private blockingsRepository: BlockingsRepository,
|
private blockingsRepository: BlockingsRepository,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
|
@ -109,6 +111,11 @@ export class ExportBlockingProcessorService {
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
|
||||||
|
|
||||||
this.logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
|
|
||||||
|
this.notificationService.createNotification(user.id, 'exportCompleted', {
|
||||||
|
exportedEntity: 'blocking',
|
||||||
|
fileId: driveFile.id,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { bindThis } from '@/decorators.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import { Packed } from '@/misc/json-schema.js';
|
import { Packed } from '@/misc/json-schema.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
import type { DbJobDataWithUser } from '../types.js';
|
import type { DbJobDataWithUser } from '../types.js';
|
||||||
|
@ -43,6 +44,7 @@ export class ExportClipsProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('export-clips');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-clips');
|
||||||
}
|
}
|
||||||
|
@ -79,6 +81,11 @@ export class ExportClipsProcessorService {
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
|
||||||
|
|
||||||
this.logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
|
|
||||||
|
this.notificationService.createNotification(user.id, 'exportCompleted', {
|
||||||
|
exportedEntity: 'clip',
|
||||||
|
fileId: driveFile.id,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import type Logger from '@/logger.js';
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import { createTemp, createTempDir } from '@/misc/create-temp.js';
|
import { createTemp, createTempDir } from '@/misc/create-temp.js';
|
||||||
import { DownloadService } from '@/core/DownloadService.js';
|
import { DownloadService } from '@/core/DownloadService.js';
|
||||||
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
|
@ -37,6 +38,7 @@ export class ExportCustomEmojisProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private downloadService: DownloadService,
|
private downloadService: DownloadService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis');
|
||||||
}
|
}
|
||||||
|
@ -134,6 +136,12 @@ export class ExportCustomEmojisProcessorService {
|
||||||
const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true });
|
const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true });
|
||||||
|
|
||||||
this.logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
|
|
||||||
|
this.notificationService.createNotification(user.id, 'exportCompleted', {
|
||||||
|
exportedEntity: 'customEmoji',
|
||||||
|
fileId: driveFile.id,
|
||||||
|
});
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
archiveCleanup();
|
archiveCleanup();
|
||||||
resolve();
|
resolve();
|
||||||
|
|
|
@ -16,6 +16,7 @@ import type { MiPoll } from '@/models/Poll.js';
|
||||||
import type { MiNote } from '@/models/Note.js';
|
import type { MiNote } from '@/models/Note.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
import type { DbJobDataWithUser } from '../types.js';
|
import type { DbJobDataWithUser } from '../types.js';
|
||||||
|
@ -37,6 +38,7 @@ export class ExportFavoritesProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('export-favorites');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-favorites');
|
||||||
}
|
}
|
||||||
|
@ -123,6 +125,11 @@ export class ExportFavoritesProcessorService {
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
|
||||||
|
|
||||||
this.logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
|
|
||||||
|
this.notificationService.createNotification(user.id, 'exportCompleted', {
|
||||||
|
exportedEntity: 'favorite',
|
||||||
|
fileId: driveFile.id,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js';
|
||||||
import { createTemp } from '@/misc/create-temp.js';
|
import { createTemp } from '@/misc/create-temp.js';
|
||||||
import type { MiFollowing } from '@/models/Following.js';
|
import type { MiFollowing } from '@/models/Following.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
|
@ -36,6 +37,7 @@ export class ExportFollowingProcessorService {
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('export-following');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-following');
|
||||||
}
|
}
|
||||||
|
@ -113,6 +115,11 @@ export class ExportFollowingProcessorService {
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
|
||||||
|
|
||||||
this.logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
|
|
||||||
|
this.notificationService.createNotification(user.id, 'exportCompleted', {
|
||||||
|
exportedEntity: 'following',
|
||||||
|
fileId: driveFile.id,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import type Logger from '@/logger.js';
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import { createTemp } from '@/misc/create-temp.js';
|
import { createTemp } from '@/misc/create-temp.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
|
@ -32,6 +33,7 @@ export class ExportMutingProcessorService {
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('export-muting');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-muting');
|
||||||
}
|
}
|
||||||
|
@ -110,6 +112,11 @@ export class ExportMutingProcessorService {
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
|
||||||
|
|
||||||
this.logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
|
|
||||||
|
this.notificationService.createNotification(user.id, 'exportCompleted', {
|
||||||
|
exportedEntity: 'muting',
|
||||||
|
fileId: driveFile.id,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { bindThis } from '@/decorators.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import { Packed } from '@/misc/json-schema.js';
|
import { Packed } from '@/misc/json-schema.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { JsonArrayStream } from '@/misc/JsonArrayStream.js';
|
import { JsonArrayStream } from '@/misc/JsonArrayStream.js';
|
||||||
import { FileWriterStream } from '@/misc/FileWriterStream.js';
|
import { FileWriterStream } from '@/misc/FileWriterStream.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
@ -112,6 +113,7 @@ export class ExportNotesProcessorService {
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
private driveFileEntityService: DriveFileEntityService,
|
private driveFileEntityService: DriveFileEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('export-notes');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-notes');
|
||||||
}
|
}
|
||||||
|
@ -150,6 +152,11 @@ export class ExportNotesProcessorService {
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
|
||||||
|
|
||||||
this.logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
|
|
||||||
|
this.notificationService.createNotification(user.id, 'exportCompleted', {
|
||||||
|
exportedEntity: 'note',
|
||||||
|
fileId: driveFile.id,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import type Logger from '@/logger.js';
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import { createTemp } from '@/misc/create-temp.js';
|
import { createTemp } from '@/misc/create-temp.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
|
@ -35,6 +36,7 @@ export class ExportUserListsProcessorService {
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists');
|
||||||
}
|
}
|
||||||
|
@ -89,6 +91,11 @@ export class ExportUserListsProcessorService {
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
|
||||||
|
|
||||||
this.logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
|
|
||||||
|
this.notificationService.createNotification(user.id, 'exportCompleted', {
|
||||||
|
exportedEntity: 'userList',
|
||||||
|
fileId: driveFile.id,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
|
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
|
||||||
* roleAssigned - ロールが付与された
|
* roleAssigned - ロールが付与された
|
||||||
* achievementEarned - 実績を獲得
|
* achievementEarned - 実績を獲得
|
||||||
|
* exportCompleted - エクスポートが完了
|
||||||
* app - アプリ通知
|
* app - アプリ通知
|
||||||
* test - テスト通知(サーバー側)
|
* test - テスト通知(サーバー側)
|
||||||
*/
|
*/
|
||||||
|
@ -32,6 +33,7 @@ export const notificationTypes = [
|
||||||
'followRequestAccepted',
|
'followRequestAccepted',
|
||||||
'roleAssigned',
|
'roleAssigned',
|
||||||
'achievementEarned',
|
'achievementEarned',
|
||||||
|
'exportCompleted',
|
||||||
'app',
|
'app',
|
||||||
'test',
|
'test',
|
||||||
] as const;
|
] as const;
|
||||||
|
@ -51,6 +53,10 @@ export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
|
||||||
export const followingVisibilities = ['public', 'followers', 'private'] as const;
|
export const followingVisibilities = ['public', 'followers', 'private'] as const;
|
||||||
export const followersVisibilities = ['public', 'followers', 'private'] as const;
|
export const followersVisibilities = ['public', 'followers', 'private'] as const;
|
||||||
|
|
||||||
|
export const exportableEntities = ['antenna', 'blocking', 'clip', 'customEmoji', 'favorite', 'following', 'muting', 'note', 'userList'] as const;
|
||||||
|
|
||||||
|
export const importableEntities = ['antenna', 'blocking', 'customEmoji', 'following', 'muting', 'userList'] as const;
|
||||||
|
|
||||||
export const moderationLogTypes = [
|
export const moderationLogTypes = [
|
||||||
'updateServerSettings',
|
'updateServerSettings',
|
||||||
'suspend',
|
'suspend',
|
||||||
|
|
|
@ -67,6 +67,8 @@ export const notificationTypes = [
|
||||||
'followRequestAccepted',
|
'followRequestAccepted',
|
||||||
'roleAssigned',
|
'roleAssigned',
|
||||||
'achievementEarned',
|
'achievementEarned',
|
||||||
|
'exportCompleted',
|
||||||
|
'test',
|
||||||
'app',
|
'app',
|
||||||
] as const;
|
] as const;
|
||||||
export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
|
export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
|
||||||
|
|
|
@ -13,7 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<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 === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
|
||||||
<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
|
<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/>
|
<MkAvatar v-else-if="'user' in notification" :class="$style.icon" :user="notification.user" link preview/>
|
||||||
<img v-else-if="'icon' in notification" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/>
|
<MkAvatar v-else-if="notification.type === 'exportCompleted'" :class="$style.icon" :user="$i" link preview/>
|
||||||
|
<img v-else-if="'icon' in notification && notification.icon != null" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/>
|
||||||
<div
|
<div
|
||||||
:class="[$style.subIcon, {
|
:class="[$style.subIcon, {
|
||||||
[$style.t_follow]: notification.type === 'follow',
|
[$style.t_follow]: notification.type === 'follow',
|
||||||
|
@ -25,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
[$style.t_quote]: notification.type === 'quote',
|
[$style.t_quote]: notification.type === 'quote',
|
||||||
[$style.t_pollEnded]: notification.type === 'pollEnded',
|
[$style.t_pollEnded]: notification.type === 'pollEnded',
|
||||||
[$style.t_achievementEarned]: notification.type === 'achievementEarned',
|
[$style.t_achievementEarned]: notification.type === 'achievementEarned',
|
||||||
|
[$style.t_exportCompleted]: notification.type === 'exportCompleted',
|
||||||
[$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null,
|
[$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null,
|
||||||
}]"
|
}]"
|
||||||
>
|
>
|
||||||
|
@ -37,6 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i>
|
<i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i>
|
||||||
<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
|
<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
|
||||||
<i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i>
|
<i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i>
|
||||||
|
<i v-else-if="notification.type === 'exportCompleted'" class="ti ti-archive"></i>
|
||||||
<template v-else-if="notification.type === 'roleAssigned'">
|
<template v-else-if="notification.type === 'roleAssigned'">
|
||||||
<img v-if="notification.role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/>
|
<img v-if="notification.role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/>
|
||||||
<i v-else class="ti ti-badges"></i>
|
<i v-else class="ti ti-badges"></i>
|
||||||
|
@ -57,6 +60,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 === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
|
||||||
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
|
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
|
||||||
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</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>
|
<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>
|
||||||
<span v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'">{{ i18n.tsx._notification.likedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
|
<span v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'">{{ i18n.tsx._notification.likedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
|
||||||
<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
|
<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
|
||||||
|
@ -98,6 +102,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements">
|
<MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements">
|
||||||
{{ i18n.ts._achievements._types['_' + notification.achievement].title }}
|
{{ i18n.ts._achievements._types['_' + notification.achievement].title }}
|
||||||
</MkA>
|
</MkA>
|
||||||
|
<MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`">
|
||||||
|
{{ i18n.ts.showFile }}
|
||||||
|
</MkA>
|
||||||
<template v-else-if="notification.type === 'follow'">
|
<template v-else-if="notification.type === 'follow'">
|
||||||
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
|
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -161,6 +168,20 @@ const props = withDefaults(defineProps<{
|
||||||
full: false,
|
full: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' };
|
||||||
|
|
||||||
|
const exportEntityName = {
|
||||||
|
antenna: i18n.ts.antennas,
|
||||||
|
blocking: i18n.ts.blockedUsers,
|
||||||
|
clip: i18n.ts.clips,
|
||||||
|
customEmoji: i18n.ts.customEmojis,
|
||||||
|
favorite: i18n.ts.favorites,
|
||||||
|
following: i18n.ts.following,
|
||||||
|
muting: i18n.ts.mutedUsers,
|
||||||
|
note: i18n.ts.notes,
|
||||||
|
userList: i18n.ts.lists,
|
||||||
|
} as const satisfies Record<ExportCompletedNotification['exportedEntity'], string>;
|
||||||
|
|
||||||
const followRequestDone = ref(false);
|
const followRequestDone = ref(false);
|
||||||
|
|
||||||
const acceptFollowRequest = () => {
|
const acceptFollowRequest = () => {
|
||||||
|
@ -298,6 +319,12 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.t_exportCompleted {
|
||||||
|
padding: 3px;
|
||||||
|
background: var(--eventOther);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.t_roleAssigned {
|
.t_roleAssigned {
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
background: var(--eventOther);
|
background: var(--eventOther);
|
||||||
|
|
|
@ -258,7 +258,7 @@ import { langs } from '@@/js/config.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
|
@ -270,16 +270,6 @@ const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
||||||
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
||||||
const dataSaver = ref(defaultStore.state.dataSaver);
|
const dataSaver = ref(defaultStore.state.dataSaver);
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere'));
|
const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere'));
|
||||||
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
|
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
|
||||||
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
|
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
|
||||||
|
@ -369,7 +359,7 @@ watch([
|
||||||
confirmWhenRevealingSensitiveMedia,
|
confirmWhenRevealingSensitiveMedia,
|
||||||
contextMenu,
|
contextMenu,
|
||||||
], async () => {
|
], async () => {
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
|
const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
|
||||||
|
|
|
@ -54,7 +54,7 @@ import MkContainer from '@/components/MkContainer.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { navbarItemDef } from '@/navbar.js';
|
import { navbarItemDef } from '@/navbar.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
|
||||||
|
@ -67,16 +67,6 @@ const items = ref(defaultStore.state.menu.map(x => ({
|
||||||
|
|
||||||
const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
|
const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addItem() {
|
async function addItem() {
|
||||||
const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k));
|
const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k));
|
||||||
const { canceled, result: item } = await os.select({
|
const { canceled, result: item } = await os.select({
|
||||||
|
@ -100,7 +90,7 @@ function removeItem(index: number) {
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
defaultStore.set('menu', items.value.map(x => x.type));
|
defaultStore.set('menu', items.value.map(x => x.type));
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
|
@ -111,7 +101,7 @@ function reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(menuDisplay, async () => {
|
watch(menuDisplay, async () => {
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -73,7 +73,7 @@ import { notificationTypes } from '@@/js/const.js';
|
||||||
|
|
||||||
const $i = signinRequired();
|
const $i = signinRequired();
|
||||||
|
|
||||||
const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'achievementEarned'];
|
const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'achievementEarned', 'test', 'exportCompleted'] as const satisfies (typeof notificationTypes[number])[];
|
||||||
|
|
||||||
const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
|
const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
|
||||||
const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);
|
const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);
|
||||||
|
|
|
@ -98,7 +98,7 @@ import { defaultStore } from '@/store.js';
|
||||||
import { signout, signinRequired } from '@/account.js';
|
import { signout, signinRequired } from '@/account.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
|
|
||||||
const $i = signinRequired();
|
const $i = signinRequired();
|
||||||
|
@ -132,16 +132,6 @@ async function deleteAccount() {
|
||||||
await signout();
|
await signout();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateRepliesAll(withReplies: boolean) {
|
async function updateRepliesAll(withReplies: boolean) {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
@ -155,7 +145,7 @@ async function updateRepliesAll(withReplies: boolean) {
|
||||||
watch([
|
watch([
|
||||||
enableCondensedLineForAcct,
|
enableCondensedLineForAcct,
|
||||||
], async () => {
|
], async () => {
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -88,19 +88,9 @@ import { uniqueBy } from '@/scripts/array.js';
|
||||||
import { fetchThemes, getThemes } from '@/theme-store.js';
|
import { fetchThemes, getThemes } from '@/theme-store.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
const installedThemes = ref(getThemes());
|
const installedThemes = ref(getThemes());
|
||||||
const builtinThemes = getBuiltinThemesRef();
|
const builtinThemes = getBuiltinThemesRef();
|
||||||
|
|
||||||
|
@ -148,13 +138,13 @@ watch(syncDeviceDarkMode, () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(wallpaper, () => {
|
watch(wallpaper, async () => {
|
||||||
if (wallpaper.value == null) {
|
if (wallpaper.value == null) {
|
||||||
miLocalStorage.removeItem('wallpaper');
|
miLocalStorage.removeItem('wallpaper');
|
||||||
} else {
|
} else {
|
||||||
miLocalStorage.setItem('wallpaper', wallpaper.value);
|
miLocalStorage.setItem('wallpaper', wallpaper.value);
|
||||||
}
|
}
|
||||||
reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||||
|
|
||||||
|
let isReloadConfirming = false;
|
||||||
|
|
||||||
|
export async function reloadAsk(opts: {
|
||||||
|
unison?: boolean;
|
||||||
|
reason?: string;
|
||||||
|
}) {
|
||||||
|
if (isReloadConfirming) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isReloadConfirming = true;
|
||||||
|
|
||||||
|
const { canceled } = await os.confirm(opts.reason == null ? {
|
||||||
|
type: 'info',
|
||||||
|
text: i18n.ts.reloadConfirm,
|
||||||
|
} : {
|
||||||
|
type: 'info',
|
||||||
|
title: i18n.ts.reloadConfirm,
|
||||||
|
text: opts.reason,
|
||||||
|
}).finally(() => {
|
||||||
|
isReloadConfirming = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
if (opts.unison) {
|
||||||
|
unisonReload();
|
||||||
|
} else {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}
|
|
@ -4254,6 +4254,17 @@ export type components = {
|
||||||
type: 'achievementEarned';
|
type: 'achievementEarned';
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
achievement: 'notes1' | 'notes10' | 'notes100' | 'notes500' | 'notes1000' | 'notes5000' | 'notes10000' | 'notes20000' | 'notes30000' | 'notes40000' | 'notes50000' | 'notes60000' | 'notes70000' | 'notes80000' | 'notes90000' | 'notes100000' | 'login3' | 'login7' | 'login15' | 'login30' | 'login60' | 'login100' | 'login200' | 'login300' | 'login400' | 'login500' | 'login600' | 'login700' | 'login800' | 'login900' | 'login1000' | 'passedSinceAccountCreated1' | 'passedSinceAccountCreated2' | 'passedSinceAccountCreated3' | 'loggedInOnBirthday' | 'loggedInOnNewYearsDay' | 'noteClipped1' | 'noteFavorited1' | 'myNoteFavorited1' | 'profileFilled' | 'markedAsCat' | 'following1' | 'following10' | 'following50' | 'following100' | 'following300' | 'followers1' | 'followers10' | 'followers50' | 'followers100' | 'followers300' | 'followers500' | 'followers1000' | 'collectAchievements30' | 'viewAchievements3min' | 'iLoveMisskey' | 'foundTreasure' | 'client30min' | 'client60min' | 'noteDeletedWithin1min' | 'postedAtLateNight' | 'postedAt0min0sec' | 'selfQuote' | 'htl20npm' | 'viewInstanceChart' | 'outputHelloWorldOnScratchpad' | 'open3windows' | 'driveFolderCircularReference' | 'reactWithoutRead' | 'clickedClickHere' | 'justPlainLucky' | 'setNameToSyuilo' | 'cookieClicked' | 'brainDiver' | 'smashTestNotificationButton' | 'tutorialCompleted' | 'bubbleGameExplodingHead' | 'bubbleGameDoubleExplodingHead';
|
achievement: 'notes1' | 'notes10' | 'notes100' | 'notes500' | 'notes1000' | 'notes5000' | 'notes10000' | 'notes20000' | 'notes30000' | 'notes40000' | 'notes50000' | 'notes60000' | 'notes70000' | 'notes80000' | 'notes90000' | 'notes100000' | 'login3' | 'login7' | 'login15' | 'login30' | 'login60' | 'login100' | 'login200' | 'login300' | 'login400' | 'login500' | 'login600' | 'login700' | 'login800' | 'login900' | 'login1000' | 'passedSinceAccountCreated1' | 'passedSinceAccountCreated2' | 'passedSinceAccountCreated3' | 'loggedInOnBirthday' | 'loggedInOnNewYearsDay' | 'noteClipped1' | 'noteFavorited1' | 'myNoteFavorited1' | 'profileFilled' | 'markedAsCat' | 'following1' | 'following10' | 'following50' | 'following100' | 'following300' | 'followers1' | 'followers10' | 'followers50' | 'followers100' | 'followers300' | 'followers500' | 'followers1000' | 'collectAchievements30' | 'viewAchievements3min' | 'iLoveMisskey' | 'foundTreasure' | 'client30min' | 'client60min' | 'noteDeletedWithin1min' | 'postedAtLateNight' | 'postedAt0min0sec' | 'selfQuote' | 'htl20npm' | 'viewInstanceChart' | 'outputHelloWorldOnScratchpad' | 'open3windows' | 'driveFolderCircularReference' | 'reactWithoutRead' | 'clickedClickHere' | 'justPlainLucky' | 'setNameToSyuilo' | 'cookieClicked' | 'brainDiver' | 'smashTestNotificationButton' | 'tutorialCompleted' | 'bubbleGameExplodingHead' | 'bubbleGameDoubleExplodingHead';
|
||||||
|
}) | ({
|
||||||
|
/** Format: id */
|
||||||
|
id: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
createdAt: string;
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'exportCompleted';
|
||||||
|
/** @enum {string} */
|
||||||
|
exportedEntity: 'antenna' | 'blocking' | 'clip' | 'customEmoji' | 'favorite' | 'following' | 'muting' | 'note' | 'userList';
|
||||||
|
/** Format: id */
|
||||||
|
fileId: string;
|
||||||
}) | ({
|
}) | ({
|
||||||
/** Format: id */
|
/** Format: id */
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -18438,8 +18449,8 @@ export type operations = {
|
||||||
untilId?: string;
|
untilId?: string;
|
||||||
/** @default true */
|
/** @default true */
|
||||||
markAsRead?: boolean;
|
markAsRead?: boolean;
|
||||||
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||||
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -18506,8 +18517,8 @@ export type operations = {
|
||||||
untilId?: string;
|
untilId?: string;
|
||||||
/** @default true */
|
/** @default true */
|
||||||
markAsRead?: boolean;
|
markAsRead?: boolean;
|
||||||
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
|
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
|
||||||
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
|
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -210,6 +210,25 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
|
||||||
tag: `achievement:${data.body.achievement}`,
|
tag: `achievement:${data.body.achievement}`,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
case 'exportCompleted': {
|
||||||
|
const entityName = {
|
||||||
|
antenna: i18n.ts.antennas,
|
||||||
|
blocking: i18n.ts.blockedUsers,
|
||||||
|
clip: i18n.ts.clips,
|
||||||
|
customEmoji: i18n.ts.customEmojis,
|
||||||
|
favorite: i18n.ts.favorites,
|
||||||
|
following: i18n.ts.following,
|
||||||
|
muting: i18n.ts.mutedUsers,
|
||||||
|
note: i18n.ts.notes,
|
||||||
|
userList: i18n.ts.lists,
|
||||||
|
} as const satisfies Record<typeof data.body.exportedEntity, string>;
|
||||||
|
|
||||||
|
return [i18n.tsx._notification.exportOfXCompleted({ x: entityName[data.body.exportedEntity] }), {
|
||||||
|
badge: iconUrl('circle-check'),
|
||||||
|
data,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
case 'pollEnded':
|
case 'pollEnded':
|
||||||
return [i18n.ts._notification.pollEnded, {
|
return [i18n.ts._notification.pollEnded, {
|
||||||
body: data.body.note.text ?? '',
|
body: data.body.note.text ?? '',
|
||||||
|
|
Loading…
Reference in New Issue