Compare commits
18 Commits
e756de996a
...
b7685bf0b8
Author | SHA1 | Date |
---|---|---|
|
b7685bf0b8 | |
|
452af7f725 | |
|
d5795b4198 | |
|
c50ad129a0 | |
|
b43de4ecfa | |
|
4d3775a7c5 | |
|
52d2688d22 | |
|
17289803e8 | |
|
5c9df99b07 | |
|
4b378af63d | |
|
f1421644cd | |
|
f6e481d8fb | |
|
4a698165cc | |
|
4cef192011 | |
|
db14012bbf | |
|
218070eb13 | |
|
8fede2d670 | |
|
d9d90a04e2 |
|
@ -5286,6 +5286,10 @@ export interface Locale extends ILocale {
|
|||
* 下書き
|
||||
*/
|
||||
"draft": string;
|
||||
/**
|
||||
* 下書きと予約投稿
|
||||
*/
|
||||
"draftsAndScheduledNotes": string;
|
||||
/**
|
||||
* リアクションする際に確認する
|
||||
*/
|
||||
|
@ -5557,6 +5561,22 @@ export interface Locale extends ILocale {
|
|||
* 投稿を予約
|
||||
*/
|
||||
"schedulePost": string;
|
||||
/**
|
||||
* {x}に投稿を予約します
|
||||
*/
|
||||
"scheduleToPostOnX": ParameterizedString<"x">;
|
||||
/**
|
||||
* {x}に投稿が予約されています
|
||||
*/
|
||||
"scheduledToPostOnX": ParameterizedString<"x">;
|
||||
/**
|
||||
* 予約
|
||||
*/
|
||||
"schedule": string;
|
||||
/**
|
||||
* 予約
|
||||
*/
|
||||
"scheduled": string;
|
||||
"_compression": {
|
||||
"_quality": {
|
||||
/**
|
||||
|
@ -10336,6 +10356,10 @@ export interface Locale extends ILocale {
|
|||
* 予約ノートが投稿されました
|
||||
*/
|
||||
"scheduledNotePosted": string;
|
||||
/**
|
||||
* 予約ノートの投稿に失敗しました
|
||||
*/
|
||||
"scheduledNotePostFailed": string;
|
||||
/**
|
||||
* 新しい投稿
|
||||
*/
|
||||
|
@ -12653,6 +12677,14 @@ export interface Locale extends ILocale {
|
|||
* 投稿予約
|
||||
*/
|
||||
"schedule": string;
|
||||
/**
|
||||
* 予約投稿一覧
|
||||
*/
|
||||
"listScheduledNotes": string;
|
||||
/**
|
||||
* 予約解除
|
||||
*/
|
||||
"cancelSchedule": string;
|
||||
};
|
||||
/**
|
||||
* 二次元コード
|
||||
|
|
|
@ -1317,6 +1317,7 @@ acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします
|
|||
federationSpecified: "このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。"
|
||||
federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。"
|
||||
draft: "下書き"
|
||||
draftsAndScheduledNotes: "下書きと予約投稿"
|
||||
confirmOnReact: "リアクションする際に確認する"
|
||||
reactAreYouSure: "\" {emoji} \" をリアクションしますか?"
|
||||
markAsSensitiveConfirm: "このメディアをセンシティブとして設定しますか?"
|
||||
|
@ -1384,6 +1385,10 @@ themeIsDefaultBecauseSafeMode: "セーフモードが有効な間はデフォル
|
|||
thankYouForTestingBeta: "ベータ版の検証にご協力いただきありがとうございます!"
|
||||
createUserSpecifiedNote: "ユーザー指定ノートを作成"
|
||||
schedulePost: "投稿を予約"
|
||||
scheduleToPostOnX: "{x}に投稿を予約します"
|
||||
scheduledToPostOnX: "{x}に投稿が予約されています"
|
||||
schedule: "予約"
|
||||
scheduled: "予約"
|
||||
|
||||
_compression:
|
||||
_quality:
|
||||
|
@ -2730,6 +2735,7 @@ _notification:
|
|||
yourFollowRequestAccepted: "フォローリクエストが承認されました"
|
||||
pollEnded: "アンケートの結果が出ました"
|
||||
scheduledNotePosted: "予約ノートが投稿されました"
|
||||
scheduledNotePostFailed: "予約ノートの投稿に失敗しました"
|
||||
newNote: "新しい投稿"
|
||||
unreadAntennaNote: "アンテナ {name}"
|
||||
roleAssigned: "ロールが付与されました"
|
||||
|
@ -3388,6 +3394,8 @@ _drafts:
|
|||
restore: "復元"
|
||||
listDrafts: "下書き一覧"
|
||||
schedule: "投稿予約"
|
||||
listScheduledNotes: "予約投稿一覧"
|
||||
cancelSchedule: "予約解除"
|
||||
|
||||
qr: "二次元コード"
|
||||
_qr:
|
||||
|
|
|
@ -11,12 +11,14 @@ export class ScheduledPost1758677617888 {
|
|||
*/
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note_draft" ADD "scheduledAt" TIMESTAMP WITH TIME ZONE`);
|
||||
await queryRunner.query(`ALTER TABLE "note_draft" ADD "isActuallyScheduled" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {QueryRunner} queryRunner
|
||||
*/
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note_draft" DROP COLUMN "isActuallyScheduled"`);
|
||||
await queryRunner.query(`ALTER TABLE "note_draft" DROP COLUMN "scheduledAt"`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,21 +18,7 @@ import { isRenote, isQuote } from '@/misc/is-renote.js';
|
|||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
||||
export type NoteDraftOptions = {
|
||||
replyId?: MiNote['id'] | null;
|
||||
renoteId?: MiNote['id'] | null;
|
||||
text?: string | null;
|
||||
cw?: string | null;
|
||||
localOnly?: boolean | null;
|
||||
reactionAcceptance?: typeof noteReactionAcceptances[number];
|
||||
visibility?: typeof noteVisibilities[number];
|
||||
fileIds?: MiDriveFile['id'][];
|
||||
visibleUserIds?: MiUser['id'][];
|
||||
hashtag?: string;
|
||||
channelId?: MiChannel['id'] | null;
|
||||
poll?: (IPoll & { expiredAfter?: number | null }) | null;
|
||||
scheduledAt?: Date | null;
|
||||
};
|
||||
export type NoteDraftOptions = Omit<MiNoteDraft, 'id' | 'userId' | 'user' | 'reply' | 'renote' | 'channel'>;
|
||||
|
||||
@Injectable()
|
||||
export class NoteDraftService {
|
||||
|
@ -75,7 +61,6 @@ export class NoteDraftService {
|
|||
@bindThis
|
||||
public async create(me: MiLocalUser, data: NoteDraftOptions): Promise<MiNoteDraft> {
|
||||
//#region check draft limit
|
||||
|
||||
const currentCount = await this.noteDraftsRepository.countBy({
|
||||
userId: me.id,
|
||||
});
|
||||
|
@ -84,23 +69,15 @@ export class NoteDraftService {
|
|||
}
|
||||
//#endregion
|
||||
|
||||
if (data.poll) {
|
||||
if (typeof data.poll.expiresAt === 'number') {
|
||||
if (data.poll.expiresAt < Date.now()) {
|
||||
throw new IdentifiableError('04da457d-b083-4055-9082-955525eda5a5', 'Cannot create expired poll');
|
||||
}
|
||||
} else if (typeof data.poll.expiredAfter === 'number') {
|
||||
data.poll.expiresAt = new Date(Date.now() + data.poll.expiredAfter);
|
||||
}
|
||||
}
|
||||
await this.validate(me, data);
|
||||
|
||||
const appliedDraft = await this.checkAndSetDraftNoteOptions(me, this.noteDraftsRepository.create(), data);
|
||||
const draft = await this.noteDraftsRepository.insertOne({
|
||||
...data,
|
||||
id: this.idService.gen(),
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
appliedDraft.id = this.idService.gen();
|
||||
appliedDraft.userId = me.id;
|
||||
const draft = await this.noteDraftsRepository.insertOne(appliedDraft);
|
||||
|
||||
if (draft.scheduledAt) {
|
||||
if (draft.scheduledAt && draft.isActuallyScheduled) {
|
||||
this.schedule(draft);
|
||||
}
|
||||
|
||||
|
@ -108,7 +85,7 @@ export class NoteDraftService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async update(me: MiLocalUser, draftId: MiNoteDraft['id'], data: NoteDraftOptions): Promise<MiNoteDraft> {
|
||||
public async update(me: MiLocalUser, draftId: MiNoteDraft['id'], data: Partial<NoteDraftOptions>): Promise<MiNoteDraft> {
|
||||
const draft = await this.noteDraftsRepository.findOneBy({
|
||||
id: draftId,
|
||||
userId: me.id,
|
||||
|
@ -118,30 +95,22 @@ export class NoteDraftService {
|
|||
throw new IdentifiableError('49cd6b9d-848e-41ee-b0b9-adaca711a6b1', 'No such note draft');
|
||||
}
|
||||
|
||||
if (data.poll) {
|
||||
if (typeof data.poll.expiresAt === 'number') {
|
||||
if (data.poll.expiresAt < Date.now()) {
|
||||
throw new IdentifiableError('04da457d-b083-4055-9082-955525eda5a5', 'Cannot create expired poll');
|
||||
}
|
||||
} else if (typeof data.poll.expiredAfter === 'number') {
|
||||
data.poll.expiresAt = new Date(Date.now() + data.poll.expiredAfter);
|
||||
}
|
||||
}
|
||||
await this.validate(me, data);
|
||||
|
||||
const appliedDraft = await this.checkAndSetDraftNoteOptions(me, draft, data);
|
||||
const updatedDraft = await this.noteDraftsRepository.createQueryBuilder().update()
|
||||
.set(data)
|
||||
.where('id = :id', { id: draftId })
|
||||
.returning('*')
|
||||
.execute()
|
||||
.then((response) => response.raw[0]);
|
||||
|
||||
await this.noteDraftsRepository.update(draftId, appliedDraft);
|
||||
|
||||
this.clearSchedule(draft).then(() => {
|
||||
if (appliedDraft.scheduledAt) {
|
||||
this.schedule(draft);
|
||||
this.clearSchedule(draftId).then(() => {
|
||||
if (updatedDraft.scheduledAt != null && updatedDraft.isActuallyScheduled) {
|
||||
this.schedule(updatedDraft);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...draft,
|
||||
...appliedDraft,
|
||||
};
|
||||
return updatedDraft;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -157,7 +126,7 @@ export class NoteDraftService {
|
|||
|
||||
await this.noteDraftsRepository.delete(draft.id);
|
||||
|
||||
this.clearSchedule(draft);
|
||||
this.clearSchedule(draftId);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -174,27 +143,20 @@ export class NoteDraftService {
|
|||
return draft;
|
||||
}
|
||||
|
||||
// 関連エンティティを取得し紐づける部分を共通化する
|
||||
@bindThis
|
||||
public async checkAndSetDraftNoteOptions(
|
||||
public async validate(
|
||||
me: MiLocalUser,
|
||||
draft: MiNoteDraft,
|
||||
data: NoteDraftOptions,
|
||||
): Promise<MiNoteDraft> {
|
||||
data.visibility ??= 'public';
|
||||
data.localOnly ??= false;
|
||||
if (data.reactionAcceptance === undefined) data.reactionAcceptance = null;
|
||||
if (data.channelId != null) {
|
||||
data.visibility = 'public';
|
||||
data.visibleUserIds = [];
|
||||
data.localOnly = true;
|
||||
data: Partial<NoteDraftOptions>,
|
||||
): Promise<void> {
|
||||
if (data.pollExpiresAt != null) {
|
||||
if (data.pollExpiresAt.getTime() < Date.now()) {
|
||||
throw new IdentifiableError('04da457d-b083-4055-9082-955525eda5a5', 'Cannot create expired poll');
|
||||
}
|
||||
}
|
||||
|
||||
let appliedDraft = draft;
|
||||
|
||||
//#region visibleUsers
|
||||
let visibleUsers: MiUser[] = [];
|
||||
if (data.visibleUserIds != null) {
|
||||
if (data.visibleUserIds != null && data.visibleUserIds.length > 0) {
|
||||
visibleUsers = await this.usersRepository.findBy({
|
||||
id: In(data.visibleUserIds),
|
||||
});
|
||||
|
@ -204,7 +166,7 @@ export class NoteDraftService {
|
|||
//#region files
|
||||
let files: MiDriveFile[] = [];
|
||||
const fileIds = data.fileIds ?? null;
|
||||
if (fileIds != null) {
|
||||
if (fileIds != null && fileIds.length > 0) {
|
||||
files = await this.driveFilesRepository.createQueryBuilder('file')
|
||||
.where('file.userId = :userId AND file.id IN (:...fileIds)', {
|
||||
userId: me.id,
|
||||
|
@ -308,33 +270,11 @@ export class NoteDraftService {
|
|||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
appliedDraft = {
|
||||
...appliedDraft,
|
||||
visibility: data.visibility,
|
||||
cw: data.cw ?? null,
|
||||
fileIds: fileIds ?? [],
|
||||
replyId: data.replyId ?? null,
|
||||
renoteId: data.renoteId ?? null,
|
||||
channelId: data.channelId ?? null,
|
||||
text: data.text ?? null,
|
||||
hashtag: data.hashtag ?? null,
|
||||
hasPoll: data.poll != null,
|
||||
pollChoices: data.poll ? data.poll.choices : [],
|
||||
pollMultiple: data.poll ? data.poll.multiple : false,
|
||||
pollExpiresAt: data.poll ? data.poll.expiresAt : null,
|
||||
pollExpiredAfter: data.poll ? data.poll.expiredAfter ?? null : null,
|
||||
visibleUserIds: data.visibleUserIds ?? [],
|
||||
localOnly: data.localOnly,
|
||||
reactionAcceptance: data.reactionAcceptance,
|
||||
scheduledAt: data.scheduledAt ?? null,
|
||||
} satisfies MiNoteDraft;
|
||||
|
||||
return appliedDraft;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async schedule(draft: MiNoteDraft): Promise<void> {
|
||||
if (!draft.isActuallyScheduled) return;
|
||||
if (draft.scheduledAt == null) return;
|
||||
if (draft.scheduledAt.getTime() <= Date.now()) return;
|
||||
|
||||
|
@ -355,10 +295,10 @@ export class NoteDraftService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async clearSchedule(draft: MiNoteDraft): Promise<void> {
|
||||
public async clearSchedule(draftId: MiNoteDraft['id']): Promise<void> {
|
||||
const jobs = await this.queueService.postScheduledNoteQueue.getJobs(['delayed', 'waiting', 'active']);
|
||||
for (const job of jobs) {
|
||||
if (job.data.noteDraftId === draft.id) {
|
||||
if (job.data.noteDraftId === draftId) {
|
||||
await job.remove();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,7 @@ export class NoteDraftEntityService implements OnModuleInit {
|
|||
id: noteDraft.id,
|
||||
createdAt: this.idService.parse(noteDraft.id).date.toISOString(),
|
||||
scheduledAt: noteDraft.scheduledAt?.getTime() ?? null,
|
||||
isActuallyScheduled: noteDraft.isActuallyScheduled,
|
||||
userId: noteDraft.userId,
|
||||
user: packedUsers?.get(noteDraft.userId) ?? this.userEntityService.pack(noteDraft.user ?? noteDraft.userId, me),
|
||||
text: text,
|
||||
|
@ -113,13 +114,13 @@ export class NoteDraftEntityService implements OnModuleInit {
|
|||
visibility: noteDraft.visibility,
|
||||
localOnly: noteDraft.localOnly,
|
||||
reactionAcceptance: noteDraft.reactionAcceptance,
|
||||
visibleUserIds: noteDraft.visibility === 'specified' ? noteDraft.visibleUserIds : undefined,
|
||||
hashtag: noteDraft.hashtag ?? undefined,
|
||||
visibleUserIds: noteDraft.visibleUserIds,
|
||||
hashtag: noteDraft.hashtag,
|
||||
fileIds: noteDraft.fileIds,
|
||||
files: packedFiles != null ? this.packAttachedFiles(noteDraft.fileIds, packedFiles) : this.driveFileEntityService.packManyByIds(noteDraft.fileIds),
|
||||
replyId: noteDraft.replyId,
|
||||
renoteId: noteDraft.renoteId,
|
||||
channelId: noteDraft.channelId ?? undefined,
|
||||
channelId: noteDraft.channelId,
|
||||
channel: channel ? {
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
|
|
|
@ -153,17 +153,13 @@ export class MiNoteDraft {
|
|||
|
||||
//#endregion
|
||||
|
||||
// 予約投稿
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
})
|
||||
public scheduledAt: Date | null;
|
||||
|
||||
constructor(data: Partial<MiNoteDraft>) {
|
||||
if (data == null) return;
|
||||
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
(this as any)[k] = v;
|
||||
}
|
||||
}
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isActuallyScheduled: boolean;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { MiNote } from './Note.js';
|
|||
import { MiAccessToken } from './AccessToken.js';
|
||||
import { MiRole } from './Role.js';
|
||||
import { MiDriveFile } from './DriveFile.js';
|
||||
import { MiNoteDraft } from './NoteDraft.js';
|
||||
|
||||
// misskey-js の notificationTypes と同期すべし
|
||||
export type MiNotification = {
|
||||
|
@ -65,6 +66,11 @@ export type MiNotification = {
|
|||
id: string;
|
||||
createdAt: string;
|
||||
noteId: MiNote['id'];
|
||||
} | {
|
||||
type: 'scheduledNotePostFailed';
|
||||
id: string;
|
||||
createdAt: string;
|
||||
noteDraftId: MiNoteDraft['id'];
|
||||
} | {
|
||||
type: 'receiveFollowRequest';
|
||||
id: string;
|
||||
|
|
|
@ -171,5 +171,9 @@ export const packedNoteDraftSchema = {
|
|||
type: 'number',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
isActuallyScheduled: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
@ -222,6 +222,21 @@ export const packedNotificationSchema = {
|
|||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['scheduledNotePostFailed'],
|
||||
},
|
||||
noteDraft: {
|
||||
type: 'object',
|
||||
ref: 'NoteDraft',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
|
|
@ -610,6 +610,7 @@ export const packedMeDetailedOnlySchema = {
|
|||
reaction: { optional: true, ...notificationRecieveConfig },
|
||||
pollEnded: { optional: true, ...notificationRecieveConfig },
|
||||
scheduledNotePosted: { optional: true, ...notificationRecieveConfig },
|
||||
scheduledNotePostFailed: { optional: true, ...notificationRecieveConfig },
|
||||
receiveFollowRequest: { optional: true, ...notificationRecieveConfig },
|
||||
followRequestAccepted: { optional: true, ...notificationRecieveConfig },
|
||||
roleAssigned: { optional: true, ...notificationRecieveConfig },
|
||||
|
|
|
@ -32,16 +32,24 @@ export class PostScheduledNoteProcessorService {
|
|||
@bindThis
|
||||
public async process(job: Bull.Job<PostScheduledNoteJobData>): Promise<void> {
|
||||
const draft = await this.noteDraftsRepository.findOne({ where: { id: job.data.noteDraftId }, relations: ['user'] });
|
||||
if (draft == null || draft.user == null || draft.scheduledAt == null) {
|
||||
if (draft == null || draft.user == null || draft.scheduledAt == null || !draft.isActuallyScheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const note = await this.noteCreateService.create(draft.user, draft);
|
||||
try {
|
||||
const note = await this.noteCreateService.create(draft.user, draft);
|
||||
|
||||
this.noteDraftsRepository.remove(draft);
|
||||
// await不要
|
||||
this.noteDraftsRepository.remove(draft);
|
||||
|
||||
this.notificationService.createNotification(draft.userId, 'scheduledNotePosted', {
|
||||
noteId: note.id,
|
||||
});
|
||||
// await不要
|
||||
this.notificationService.createNotification(draft.userId, 'scheduledNotePosted', {
|
||||
noteId: note.id,
|
||||
});
|
||||
} catch (err) {
|
||||
this.notificationService.createNotification(draft.userId, 'scheduledNotePostFailed', {
|
||||
noteDraftId: draft.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,7 @@ export const meta = {
|
|||
reaction: { optional: true, ...notificationRecieveConfig },
|
||||
pollEnded: { optional: true, ...notificationRecieveConfig },
|
||||
scheduledNotePosted: { optional: true, ...notificationRecieveConfig },
|
||||
scheduledNotePostFailed: { optional: true, ...notificationRecieveConfig },
|
||||
receiveFollowRequest: { optional: true, ...notificationRecieveConfig },
|
||||
followRequestAccepted: { optional: true, ...notificationRecieveConfig },
|
||||
roleAssigned: { optional: true, ...notificationRecieveConfig },
|
||||
|
|
|
@ -210,6 +210,7 @@ export const paramDef = {
|
|||
reaction: notificationRecieveConfig,
|
||||
pollEnded: notificationRecieveConfig,
|
||||
scheduledNotePosted: notificationRecieveConfig,
|
||||
scheduledNotePostFailed: notificationRecieveConfig,
|
||||
receiveFollowRequest: notificationRecieveConfig,
|
||||
followRequestAccepted: notificationRecieveConfig,
|
||||
roleAssigned: notificationRecieveConfig,
|
||||
|
|
|
@ -162,7 +162,7 @@ export const paramDef = {
|
|||
fileIds: {
|
||||
type: 'array',
|
||||
uniqueItems: true,
|
||||
minItems: 1,
|
||||
minItems: 0,
|
||||
maxItems: 16,
|
||||
items: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
|
@ -184,8 +184,9 @@ export const paramDef = {
|
|||
required: ['choices'],
|
||||
},
|
||||
scheduledAt: { type: 'integer', nullable: true },
|
||||
isActuallyScheduled: { type: 'boolean', default: false },
|
||||
},
|
||||
required: [],
|
||||
required: ['visibility', 'visibleUserIds', 'cw', 'hashtag', 'localOnly', 'reactionAcceptance', 'replyId', 'renoteId', 'channelId', 'text', 'fileIds', 'poll', 'scheduledAt', 'isActuallyScheduled'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
@ -197,23 +198,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
super(meta, paramDef, async (ps, me) => {
|
||||
const draft = await this.noteDraftService.create(me, {
|
||||
fileIds: ps.fileIds,
|
||||
poll: ps.poll ? {
|
||||
choices: ps.poll.choices,
|
||||
multiple: ps.poll.multiple ?? false,
|
||||
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
|
||||
expiredAfter: ps.poll.expiredAfter ?? null,
|
||||
} : undefined,
|
||||
text: ps.text ?? null,
|
||||
replyId: ps.replyId ?? undefined,
|
||||
renoteId: ps.renoteId ?? undefined,
|
||||
cw: ps.cw ?? null,
|
||||
...(ps.hashtag ? { hashtag: ps.hashtag } : {}),
|
||||
pollChoices: ps.poll?.choices ?? [],
|
||||
pollMultiple: ps.poll?.multiple ?? false,
|
||||
pollExpiresAt: ps.poll?.expiresAt ? new Date(ps.poll.expiresAt) : null,
|
||||
pollExpiredAfter: ps.poll?.expiredAfter ?? null,
|
||||
hasPoll: ps.poll != null,
|
||||
text: ps.text,
|
||||
replyId: ps.replyId,
|
||||
renoteId: ps.renoteId,
|
||||
cw: ps.cw,
|
||||
hashtag: ps.hashtag,
|
||||
localOnly: ps.localOnly,
|
||||
reactionAcceptance: ps.reactionAcceptance,
|
||||
visibility: ps.visibility,
|
||||
visibleUserIds: ps.visibleUserIds ?? [],
|
||||
channelId: ps.channelId ?? undefined,
|
||||
visibleUserIds: ps.visibleUserIds,
|
||||
channelId: ps.channelId,
|
||||
scheduledAt: ps.scheduledAt ? new Date(ps.scheduledAt) : null,
|
||||
isActuallyScheduled: ps.isActuallyScheduled,
|
||||
}).catch((err) => {
|
||||
if (err instanceof IdentifiableError) {
|
||||
switch (err.id) {
|
||||
|
|
|
@ -41,6 +41,7 @@ export const paramDef = {
|
|||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
scheduled: { type: 'boolean', nullable: true },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
@ -58,6 +59,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const query = this.queryService.makePaginationQuery<MiNoteDraft>(this.noteDraftsRepository.createQueryBuilder('drafts'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('drafts.userId = :meId', { meId: me.id });
|
||||
|
||||
if (ps.scheduled === true) {
|
||||
query.andWhere('drafts.isActuallyScheduled = true');
|
||||
} else if (ps.scheduled === false) {
|
||||
query.andWhere('drafts.isActuallyScheduled = false');
|
||||
}
|
||||
|
||||
const drafts = await query
|
||||
.limit(ps.limit)
|
||||
.getMany();
|
||||
|
|
|
@ -171,14 +171,14 @@ export const paramDef = {
|
|||
type: 'object',
|
||||
properties: {
|
||||
draftId: { type: 'string', nullable: false, format: 'misskey:id' },
|
||||
visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' },
|
||||
visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'] },
|
||||
visibleUserIds: { type: 'array', uniqueItems: true, items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
cw: { type: 'string', nullable: true, minLength: 1, maxLength: 100 },
|
||||
hashtag: { type: 'string', nullable: true, maxLength: 200 },
|
||||
localOnly: { type: 'boolean', default: false },
|
||||
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
|
||||
localOnly: { type: 'boolean' },
|
||||
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'] },
|
||||
replyId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
renoteId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
channelId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
|
@ -194,7 +194,7 @@ export const paramDef = {
|
|||
fileIds: {
|
||||
type: 'array',
|
||||
uniqueItems: true,
|
||||
minItems: 1,
|
||||
minItems: 0,
|
||||
maxItems: 16,
|
||||
items: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
|
@ -216,6 +216,7 @@ export const paramDef = {
|
|||
required: ['choices'],
|
||||
},
|
||||
scheduledAt: { type: 'integer', nullable: true },
|
||||
isActuallyScheduled: { type: 'boolean' },
|
||||
},
|
||||
required: ['draftId'],
|
||||
} as const;
|
||||
|
@ -229,23 +230,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
super(meta, paramDef, async (ps, me) => {
|
||||
const draft = await this.noteDraftService.update(me, ps.draftId, {
|
||||
fileIds: ps.fileIds,
|
||||
poll: ps.poll ? {
|
||||
choices: ps.poll.choices,
|
||||
multiple: ps.poll.multiple ?? false,
|
||||
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
|
||||
expiredAfter: ps.poll.expiredAfter ?? null,
|
||||
} : undefined,
|
||||
text: ps.text ?? null,
|
||||
replyId: ps.replyId ?? undefined,
|
||||
renoteId: ps.renoteId ?? undefined,
|
||||
cw: ps.cw ?? null,
|
||||
...(ps.hashtag ? { hashtag: ps.hashtag } : {}),
|
||||
pollChoices: ps.poll?.choices,
|
||||
pollMultiple: ps.poll?.multiple,
|
||||
pollExpiresAt: ps.poll?.expiresAt ? new Date(ps.poll.expiresAt) : null,
|
||||
pollExpiredAfter: ps.poll?.expiredAfter,
|
||||
text: ps.text,
|
||||
replyId: ps.replyId,
|
||||
renoteId: ps.renoteId,
|
||||
cw: ps.cw,
|
||||
hashtag: ps.hashtag,
|
||||
localOnly: ps.localOnly,
|
||||
reactionAcceptance: ps.reactionAcceptance,
|
||||
visibility: ps.visibility,
|
||||
visibleUserIds: ps.visibleUserIds ?? [],
|
||||
channelId: ps.channelId ?? undefined,
|
||||
visibleUserIds: ps.visibleUserIds,
|
||||
channelId: ps.channelId,
|
||||
scheduledAt: ps.scheduledAt ? new Date(ps.scheduledAt) : null,
|
||||
isActuallyScheduled: ps.isActuallyScheduled,
|
||||
}).catch((err) => {
|
||||
if (err instanceof IdentifiableError) {
|
||||
switch (err.id) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
* reaction - 投稿にリアクションされた
|
||||
* pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
|
||||
* scheduledNotePosted - 予約したノートが投稿された
|
||||
* scheduledNotePostFailed - 予約したノートの投稿に失敗した
|
||||
* receiveFollowRequest - フォローリクエストされた
|
||||
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
|
||||
* roleAssigned - ロールが付与された
|
||||
|
@ -34,6 +35,7 @@ export const notificationTypes = [
|
|||
'reaction',
|
||||
'pollEnded',
|
||||
'scheduledNotePosted',
|
||||
'scheduledNotePostFailed',
|
||||
'receiveFollowRequest',
|
||||
'followRequestAccepted',
|
||||
'roleAssigned',
|
||||
|
|
|
@ -64,6 +64,8 @@ function toBase62(n: number): string {
|
|||
}
|
||||
|
||||
export function getConfig(): UserConfig {
|
||||
const localesHash = toBase62(hash(JSON.stringify(locales)));
|
||||
|
||||
return {
|
||||
base: '/embed_vite/',
|
||||
|
||||
|
@ -148,9 +150,9 @@ export function getConfig(): UserConfig {
|
|||
// dependencies of i18n.ts
|
||||
'config': ['@@/js/config.js'],
|
||||
},
|
||||
entryFileNames: 'scripts/[hash:8].js',
|
||||
chunkFileNames: 'scripts/[hash:8].js',
|
||||
assetFileNames: 'assets/[hash:8][extname]',
|
||||
entryFileNames: `scripts/${localesHash}-[hash:8].js`,
|
||||
chunkFileNames: `scripts/${localesHash}-[hash:8].js`,
|
||||
assetFileNames: `assets/${localesHash}-[hash:8][extname]`,
|
||||
paths(id) {
|
||||
for (const p of externalPackages) {
|
||||
if (p.match.test(id)) {
|
||||
|
|
|
@ -15,110 +15,137 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
@esc="cancel()"
|
||||
>
|
||||
<template #header>
|
||||
{{ i18n.ts.drafts }} ({{ currentDraftsCount }}/{{ $i?.policies.noteDraftLimit }})
|
||||
{{ i18n.ts.draftsAndScheduledNotes }} ({{ currentDraftsCount }}/{{ $i?.policies.noteDraftLimit }})
|
||||
</template>
|
||||
<div class="_spacer">
|
||||
<MkPagination :paginator="paginator" withControl>
|
||||
<template #empty>
|
||||
<MkResult type="empty" :text="i18n.ts._drafts.noDrafts"/>
|
||||
</template>
|
||||
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<div
|
||||
v-for="draft in (items as unknown as Misskey.entities.NoteDraft[])"
|
||||
:key="draft.id"
|
||||
v-panel
|
||||
:class="[$style.draft]"
|
||||
>
|
||||
<div :class="$style.draftBody" class="_gaps_s">
|
||||
<div :class="$style.draftInfo">
|
||||
<div :class="$style.draftMeta">
|
||||
<div v-if="draft.reply" class="_nowrap">
|
||||
<i class="ti ti-arrow-back-up"></i> <I18n :src="i18n.ts._drafts.replyTo" tag="span">
|
||||
<template #user>
|
||||
<Mfm v-if="draft.reply.user.name != null" :text="draft.reply.user.name" :plain="true" :nowrap="true"/>
|
||||
<MkAcct v-else :user="draft.reply.user"/>
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div v-else-if="draft.replyId" class="_nowrap">
|
||||
<i class="ti ti-arrow-back-up"></i> <I18n :src="i18n.ts._drafts.replyTo" tag="span">
|
||||
<template #user>
|
||||
{{ i18n.ts.deletedNote }}
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div v-if="draft.renote && draft.text != null" class="_nowrap">
|
||||
<i class="ti ti-quote"></i> <I18n :src="i18n.ts._drafts.quoteOf" tag="span">
|
||||
<template #user>
|
||||
<Mfm v-if="draft.renote.user.name != null" :text="draft.renote.user.name" :plain="true" :nowrap="true"/>
|
||||
<MkAcct v-else :user="draft.renote.user"/>
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div v-else-if="draft.renoteId" class="_nowrap">
|
||||
<i class="ti ti-quote"></i> <I18n :src="i18n.ts._drafts.quoteOf" tag="span">
|
||||
<template #user>
|
||||
{{ i18n.ts.deletedNote }}
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div v-if="draft.channel" class="_nowrap">
|
||||
<i class="ti ti-device-tv"></i> {{ i18n.tsx._drafts.postTo({ channel: draft.channel.name }) }}
|
||||
<MkStickyContainer>
|
||||
<template #header>
|
||||
<MkTabs
|
||||
v-model:tab="tab"
|
||||
centered
|
||||
:class="$style.tabs"
|
||||
:tabs="[
|
||||
{
|
||||
key: 'drafts',
|
||||
title: i18n.ts.drafts,
|
||||
icon: 'ti ti-pencil-question',
|
||||
},
|
||||
{
|
||||
key: 'scheduled',
|
||||
title: i18n.ts.scheduled,
|
||||
icon: 'ti ti-calendar-clock',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div class="_spacer">
|
||||
<MkPagination :key="tab" :paginator="tab === 'scheduled' ? scheduledPaginator : draftsPaginator" withControl>
|
||||
<template #empty>
|
||||
<MkResult type="empty" :text="i18n.ts._drafts.noDrafts"/>
|
||||
</template>
|
||||
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<div
|
||||
v-for="draft in (items as unknown as Misskey.entities.NoteDraft[])"
|
||||
:key="draft.id"
|
||||
v-panel
|
||||
:class="[$style.draft]"
|
||||
>
|
||||
<div :class="$style.draftBody" class="_gaps_s">
|
||||
<MkInfo v-if="draft.scheduledAt != null && draft.isActuallyScheduled">{{ i18n.tsx.scheduledToPostOnX({ x: new Date(draft.scheduledAt).toLocaleString() }) }}</MkInfo>
|
||||
<div :class="$style.draftInfo">
|
||||
<div :class="$style.draftMeta">
|
||||
<div v-if="draft.reply" class="_nowrap">
|
||||
<i class="ti ti-arrow-back-up"></i> <I18n :src="i18n.ts._drafts.replyTo" tag="span">
|
||||
<template #user>
|
||||
<Mfm v-if="draft.reply.user.name != null" :text="draft.reply.user.name" :plain="true" :nowrap="true"/>
|
||||
<MkAcct v-else :user="draft.reply.user"/>
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div v-else-if="draft.replyId" class="_nowrap">
|
||||
<i class="ti ti-arrow-back-up"></i> <I18n :src="i18n.ts._drafts.replyTo" tag="span">
|
||||
<template #user>
|
||||
{{ i18n.ts.deletedNote }}
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div v-if="draft.renote && draft.text != null" class="_nowrap">
|
||||
<i class="ti ti-quote"></i> <I18n :src="i18n.ts._drafts.quoteOf" tag="span">
|
||||
<template #user>
|
||||
<Mfm v-if="draft.renote.user.name != null" :text="draft.renote.user.name" :plain="true" :nowrap="true"/>
|
||||
<MkAcct v-else :user="draft.renote.user"/>
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div v-else-if="draft.renoteId" class="_nowrap">
|
||||
<i class="ti ti-quote"></i> <I18n :src="i18n.ts._drafts.quoteOf" tag="span">
|
||||
<template #user>
|
||||
{{ i18n.ts.deletedNote }}
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div v-if="draft.channel" class="_nowrap">
|
||||
<i class="ti ti-device-tv"></i> {{ i18n.tsx._drafts.postTo({ channel: draft.channel.name }) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.draftContent">
|
||||
<Mfm :text="getNoteSummary(draft, { showRenote: false, showReply: false })" :plain="true" :author="draft.user"/>
|
||||
</div>
|
||||
<div :class="$style.draftFooter">
|
||||
<div :class="$style.draftVisibility">
|
||||
<span :title="i18n.ts._visibility[draft.visibility]">
|
||||
<i v-if="draft.visibility === 'public'" class="ti ti-world"></i>
|
||||
<i v-else-if="draft.visibility === 'home'" class="ti ti-home"></i>
|
||||
<i v-else-if="draft.visibility === 'followers'" class="ti ti-lock"></i>
|
||||
<i v-else-if="draft.visibility === 'specified'" class="ti ti-mail"></i>
|
||||
</span>
|
||||
<span v-if="draft.localOnly" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
||||
<div :class="$style.draftContent">
|
||||
<Mfm :text="getNoteSummary(draft, { showRenote: false, showReply: false })" :plain="true" :author="draft.user"/>
|
||||
</div>
|
||||
<div :class="$style.draftFooter">
|
||||
<div :class="$style.draftVisibility">
|
||||
<span :title="i18n.ts._visibility[draft.visibility]">
|
||||
<i v-if="draft.visibility === 'public'" class="ti ti-world"></i>
|
||||
<i v-else-if="draft.visibility === 'home'" class="ti ti-home"></i>
|
||||
<i v-else-if="draft.visibility === 'followers'" class="ti ti-lock"></i>
|
||||
<i v-else-if="draft.visibility === 'specified'" class="ti ti-mail"></i>
|
||||
</span>
|
||||
<span v-if="draft.localOnly" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
||||
</div>
|
||||
<MkTime :time="draft.createdAt" :class="$style.draftCreatedAt" mode="detail" colored/>
|
||||
</div>
|
||||
<MkTime :time="draft.createdAt" :class="$style.draftCreatedAt" mode="detail" colored/>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.draftActions" class="_buttons">
|
||||
<MkButton
|
||||
:class="$style.itemButton"
|
||||
small
|
||||
@click="restoreDraft(draft)"
|
||||
>
|
||||
<i class="ti ti-corner-up-left"></i>
|
||||
{{ i18n.ts._drafts.restore }}
|
||||
</MkButton>
|
||||
<MkButton
|
||||
:class="$style.itemButton"
|
||||
small
|
||||
@click="schedule(draft)"
|
||||
>
|
||||
<i class="ti ti-calendar-time"></i>
|
||||
{{ i18n.ts._drafts.schedule }}
|
||||
</MkButton>
|
||||
<MkButton
|
||||
v-tooltip="i18n.ts._drafts.delete"
|
||||
danger
|
||||
small
|
||||
:iconOnly="true"
|
||||
:class="$style.itemButton"
|
||||
style="margin-left: auto;"
|
||||
@click="deleteDraft(draft)"
|
||||
>
|
||||
<i class="ti ti-trash"></i>
|
||||
</MkButton>
|
||||
|
||||
<div :class="$style.draftActions" class="_buttons">
|
||||
<MkButton
|
||||
v-if="draft.scheduledAt != null && draft.isActuallyScheduled"
|
||||
:class="$style.itemButton"
|
||||
small
|
||||
@click="cancelSchedule(draft)"
|
||||
>
|
||||
<i class="ti ti-calendar-x"></i>
|
||||
{{ i18n.ts._drafts.cancelSchedule }}
|
||||
</MkButton>
|
||||
<MkButton
|
||||
v-else
|
||||
:class="$style.itemButton"
|
||||
small
|
||||
@click="restoreDraft(draft)"
|
||||
>
|
||||
<i class="ti ti-corner-up-left"></i>
|
||||
{{ i18n.ts._drafts.restore }}
|
||||
</MkButton>
|
||||
<MkButton
|
||||
v-tooltip="i18n.ts._drafts.delete"
|
||||
danger
|
||||
small
|
||||
:iconOnly="true"
|
||||
:class="$style.itemButton"
|
||||
style="margin-left: auto;"
|
||||
@click="deleteDraft(draft)"
|
||||
>
|
||||
<i class="ti ti-trash"></i>
|
||||
</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</MkStickyContainer>
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
|
||||
|
@ -134,6 +161,12 @@ import * as os from '@/os.js';
|
|||
import { $i } from '@/i.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api';
|
||||
import { Paginator } from '@/utility/paginator.js';
|
||||
import MkTabs from '@/components/MkTabs.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
scheduled?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'restore', draft: Misskey.entities.NoteDraft): void;
|
||||
|
@ -141,8 +174,20 @@ const emit = defineEmits<{
|
|||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const paginator = markRaw(new Paginator('notes/drafts/list', {
|
||||
const tab = ref<'drafts' | 'scheduled'>(props.scheduled ? 'scheduled' : 'drafts');
|
||||
|
||||
const draftsPaginator = markRaw(new Paginator('notes/drafts/list', {
|
||||
limit: 10,
|
||||
params: {
|
||||
scheduled: false,
|
||||
},
|
||||
}));
|
||||
|
||||
const scheduledPaginator = markRaw(new Paginator('notes/drafts/list', {
|
||||
limit: 10,
|
||||
params: {
|
||||
scheduled: true,
|
||||
},
|
||||
}));
|
||||
|
||||
const currentDraftsCount = ref(0);
|
||||
|
@ -171,7 +216,17 @@ async function deleteDraft(draft: Misskey.entities.NoteDraft) {
|
|||
if (canceled) return;
|
||||
|
||||
os.apiWithDialog('notes/drafts/delete', { draftId: draft.id }).then(() => {
|
||||
paginator.reload();
|
||||
draftsPaginator.reload();
|
||||
});
|
||||
}
|
||||
|
||||
async function cancelSchedule(draft: Misskey.entities.NoteDraft) {
|
||||
os.apiWithDialog('notes/drafts/update', {
|
||||
draftId: draft.id,
|
||||
isActuallyScheduled: false,
|
||||
scheduledAt: null,
|
||||
}).then(() => {
|
||||
scheduledPaginator.reload();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
@ -229,4 +284,11 @@ async function deleteDraft(draft: Misskey.entities.NoteDraft) {
|
|||
padding-top: 16px;
|
||||
border-top: solid 1px var(--MI_THEME-divider);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
background: color(from var(--MI_THEME-bg) srgb r g b / 0.75);
|
||||
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
||||
backdrop-filter: var(--MI-blur, blur(15px));
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div :class="$style.root">
|
||||
<div :class="$style.head">
|
||||
<MkAvatar v-if="['pollEnded', 'note'].includes(notification.type) && 'note' in notification" :class="$style.icon" :user="notification.note.user" link preview/>
|
||||
<MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login', 'createToken'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
|
||||
<MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login', 'createToken', 'scheduledNotePosted', 'scheduledNotePostFailed'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
|
||||
<div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ti ti-heart" style="line-height: 1;"></i></div>
|
||||
<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>
|
||||
|
@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
[$style.t_quote]: notification.type === 'quote',
|
||||
[$style.t_pollEnded]: notification.type === 'pollEnded',
|
||||
[$style.t_scheduledNotePosted]: notification.type === 'scheduledNotePosted',
|
||||
[$style.t_scheduledNotePostFailed]: notification.type === 'scheduledNotePostFailed',
|
||||
[$style.t_achievementEarned]: notification.type === 'achievementEarned',
|
||||
[$style.t_exportCompleted]: notification.type === 'exportCompleted',
|
||||
[$style.t_login]: notification.type === 'login',
|
||||
|
@ -41,6 +42,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 === 'pollEnded'" class="ti ti-chart-arrows"></i>
|
||||
<i v-else-if="notification.type === 'scheduledNotePosted'" class="ti ti-send"></i>
|
||||
<i v-else-if="notification.type === 'scheduledNotePostFailed'" class="ti ti-alert-triangle"></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>
|
||||
<i v-else-if="notification.type === 'login'" class="ti ti-login-2"></i>
|
||||
|
@ -63,6 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<header :class="$style.header">
|
||||
<span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span>
|
||||
<span v-else-if="notification.type === 'scheduledNotePosted'">{{ i18n.ts._notification.scheduledNotePosted }}</span>
|
||||
<span v-else-if="notification.type === 'scheduledNotePostFailed'">{{ i18n.ts._notification.scheduledNotePostFailed }}</span>
|
||||
<span v-else-if="notification.type === 'note'">{{ i18n.ts._notification.newNote }}: <MkUserName :user="notification.note.user"/></span>
|
||||
<span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
|
||||
<span v-else-if="notification.type === 'chatRoomInvitationReceived'">{{ i18n.ts._notification.chatRoomInvitationReceived }}</span>
|
||||
|
@ -351,6 +354,11 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_scheduledNotePostFailed {
|
||||
background: var(--eventOther);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_achievementEarned {
|
||||
background: var(--eventAchievement);
|
||||
pointer-events: none;
|
||||
|
|
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<button v-click-anime v-tooltip="i18n.ts.switchAccount" :class="$style.account" class="_button" @click="openAccountMenu">
|
||||
<img :class="$style.avatar" :src="(postAccount ?? $i).avatarUrl" style="border-radius: 100%;"/>
|
||||
</button>
|
||||
<button v-if="$i.policies.noteDraftLimit > 0" v-tooltip="(postAccount != null && postAccount.id !== $i.id) ? null : i18n.ts.draft" class="_button" :class="$style.draftButton" :disabled="postAccount != null && postAccount.id !== $i.id" @click="showDraftMenu"><i class="ti ti-pencil-minus"></i></button>
|
||||
<button v-if="$i.policies.noteDraftLimit > 0" v-tooltip="(postAccount != null && postAccount.id !== $i.id) ? null : i18n.ts.draftsAndScheduledNotes" class="_button" :class="$style.draftButton" :disabled="postAccount != null && postAccount.id !== $i.id" @click="showDraftMenu"><i class="ti ti-list"></i></button>
|
||||
</div>
|
||||
<div :class="$style.headerRight">
|
||||
<template v-if="!(targetChannel != null && fixed)">
|
||||
|
@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template v-if="posted"></template>
|
||||
<template v-else-if="posting"><MkEllipsis/></template>
|
||||
<template v-else>{{ submitText }}</template>
|
||||
<i style="margin-left: 6px;" :class="posted ? 'ti ti-check' : replyTargetNote ? 'ti ti-arrow-back-up' : renoteTargetNote ? 'ti ti-quote' : 'ti ti-send'"></i>
|
||||
<i style="margin-left: 6px;" :class="submitIcon"></i>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -61,6 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<button class="_buttonPrimary" style="padding: 4px; border-radius: 8px;" @click="addVisibleUser"><i class="ti ti-plus ti-fw"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<MkInfo v-if="scheduledAt != null" :class="$style.scheduledAt">{{ i18n.tsx.scheduleToPostOnX({ x: new Date(scheduledAt).toLocaleString() }) }} - <button class="_textButton" @click="cancelSchedule()">{{ i18n.ts.cancel }}</button></MkInfo>
|
||||
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
||||
<div v-show="useCw" :class="$style.cwOuter">
|
||||
<input ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd">
|
||||
|
@ -263,11 +264,17 @@ const placeholder = computed((): string => {
|
|||
});
|
||||
|
||||
const submitText = computed((): string => {
|
||||
return renoteTargetNote.value
|
||||
? i18n.ts.quote
|
||||
: replyTargetNote.value
|
||||
? i18n.ts.reply
|
||||
: i18n.ts.note;
|
||||
return scheduledAt.value != null
|
||||
? i18n.ts.schedule
|
||||
: renoteTargetNote.value
|
||||
? i18n.ts.quote
|
||||
: replyTargetNote.value
|
||||
? i18n.ts.reply
|
||||
: i18n.ts.note;
|
||||
});
|
||||
|
||||
const submitIcon = computed((): string => {
|
||||
return posted.value ? 'ti ti-check' : scheduledAt.value != null ? 'ti ti-calendar-time' : replyTargetNote.value ? 'ti ti-arrow-back-up' : renoteTargetNote.value ? 'ti ti-quote' : 'ti ti-send';
|
||||
});
|
||||
|
||||
const textLength = computed((): number => {
|
||||
|
@ -662,6 +669,7 @@ function clear() {
|
|||
files.value = [];
|
||||
poll.value = null;
|
||||
quoteId.value = null;
|
||||
scheduledAt.value = null;
|
||||
}
|
||||
|
||||
function onKeydown(ev: KeyboardEvent) {
|
||||
|
@ -832,7 +840,9 @@ function deleteDraft() {
|
|||
miLocalStorage.setItem('drafts', JSON.stringify(draftData));
|
||||
}
|
||||
|
||||
async function saveServerDraft(clearLocal = false) {
|
||||
async function saveServerDraft(options: {
|
||||
isActuallyScheduled?: boolean;
|
||||
} = {}) {
|
||||
return await os.apiWithDialog(serverDraftId.value == null ? 'notes/drafts/create' : 'notes/drafts/update', {
|
||||
...(serverDraftId.value == null ? {} : { draftId: serverDraftId.value }),
|
||||
text: text.value,
|
||||
|
@ -840,20 +850,15 @@ async function saveServerDraft(clearLocal = false) {
|
|||
visibility: visibility.value,
|
||||
localOnly: localOnly.value,
|
||||
hashtag: hashtags.value,
|
||||
...(files.value.length > 0 ? { fileIds: files.value.map(f => f.id) } : {}),
|
||||
fileIds: files.value.map(f => f.id),
|
||||
poll: poll.value,
|
||||
...(visibleUsers.value.length > 0 ? { visibleUserIds: visibleUsers.value.map(x => x.id) } : {}),
|
||||
renoteId: renoteTargetNote.value ? renoteTargetNote.value.id : quoteId.value ? quoteId.value : undefined,
|
||||
replyId: replyTargetNote.value ? replyTargetNote.value.id : undefined,
|
||||
channelId: targetChannel.value ? targetChannel.value.id : undefined,
|
||||
visibleUserIds: visibleUsers.value.map(x => x.id),
|
||||
renoteId: renoteTargetNote.value ? renoteTargetNote.value.id : quoteId.value ? quoteId.value : null,
|
||||
replyId: replyTargetNote.value ? replyTargetNote.value.id : null,
|
||||
channelId: targetChannel.value ? targetChannel.value.id : null,
|
||||
reactionAcceptance: reactionAcceptance.value,
|
||||
scheduledAt: scheduledAt.value,
|
||||
}).then(() => {
|
||||
if (clearLocal) {
|
||||
clear();
|
||||
deleteDraft();
|
||||
}
|
||||
}).catch((err) => {
|
||||
isActuallyScheduled: options.isActuallyScheduled ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -888,6 +893,21 @@ async function post(ev?: MouseEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
if (scheduledAt.value != null) {
|
||||
if (uploader.items.value.some(x => x.uploaded == null)) {
|
||||
await uploadFiles();
|
||||
|
||||
// アップロード失敗したものがあったら中止
|
||||
if (uploader.items.value.some(x => x.uploaded == null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await postAsScheduled();
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.mock) return;
|
||||
|
||||
if (visibility.value === 'public' && (
|
||||
|
@ -1059,6 +1079,14 @@ async function post(ev?: MouseEvent) {
|
|||
});
|
||||
}
|
||||
|
||||
async function postAsScheduled() {
|
||||
if (props.mock) return;
|
||||
|
||||
await saveServerDraft({
|
||||
isActuallyScheduled: true,
|
||||
});
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
emit('cancel');
|
||||
}
|
||||
|
@ -1228,14 +1256,28 @@ function showDraftMenu(ev: MouseEvent) {
|
|||
action: () => {
|
||||
showDraftsDialog();
|
||||
},
|
||||
}, { type: 'divider' }, {
|
||||
type: 'button',
|
||||
text: i18n.ts._drafts.listScheduledNotes,
|
||||
icon: 'ti ti-clock-down',
|
||||
action: () => {
|
||||
showDraftsDialog();
|
||||
},
|
||||
}], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
|
||||
}
|
||||
|
||||
async function schedule() {
|
||||
const { canceled, result } = await os.inputDate({
|
||||
const { canceled, result } = await os.inputDatetime({
|
||||
title: i18n.ts.schedulePost,
|
||||
});
|
||||
if (canceled) return;
|
||||
if (result.getTime() <= Date.now()) return;
|
||||
|
||||
scheduledAt.value = result.getTime();
|
||||
}
|
||||
|
||||
function cancelSchedule() {
|
||||
scheduledAt.value = null;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -1538,6 +1580,10 @@ html[data-color-scheme=light] .preview {
|
|||
margin: 0 20px 16px 20px;
|
||||
}
|
||||
|
||||
.scheduledAt {
|
||||
margin: 0 20px 16px 20px;
|
||||
}
|
||||
|
||||
.cw,
|
||||
.hashtags,
|
||||
.text {
|
||||
|
|
|
@ -460,7 +460,7 @@ export function inputNumber(props: {
|
|||
});
|
||||
}
|
||||
|
||||
export function inputDate(props: {
|
||||
export function inputDatetime(props: {
|
||||
title?: string;
|
||||
text?: string;
|
||||
placeholder?: string | null;
|
||||
|
@ -475,7 +475,7 @@ export function inputDate(props: {
|
|||
title: props.title,
|
||||
text: props.text,
|
||||
input: {
|
||||
type: 'date',
|
||||
type: 'datetime-local',
|
||||
placeholder: props.placeholder,
|
||||
default: props.default ?? null,
|
||||
},
|
||||
|
|
|
@ -85,6 +85,8 @@ export function toBase62(n: number): string {
|
|||
}
|
||||
|
||||
export function getConfig(): UserConfig {
|
||||
const localesHash = toBase62(hash(JSON.stringify(locales)));
|
||||
|
||||
return {
|
||||
base: '/vite/',
|
||||
|
||||
|
@ -188,9 +190,9 @@ export function getConfig(): UserConfig {
|
|||
// dependencies of i18n.ts
|
||||
'config': ['@@/js/config.js'],
|
||||
},
|
||||
entryFileNames: 'scripts/[hash:8].js',
|
||||
chunkFileNames: 'scripts/[hash:8].js',
|
||||
assetFileNames: 'assets/[hash:8][extname]',
|
||||
entryFileNames: `scripts/${localesHash}-[hash:8].js`,
|
||||
chunkFileNames: `scripts/${localesHash}-[hash:8].js`,
|
||||
assetFileNames: `assets/${localesHash}-[hash:8][extname]`,
|
||||
paths(id) {
|
||||
for (const p of externalPackages) {
|
||||
if (p.match.test(id)) {
|
||||
|
|
|
@ -3226,7 +3226,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", "pollEnded", "scheduledNotePosted", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "chatRoomInvitationReceived", "achievementEarned", "exportCompleted", "test", "login", "createToken"];
|
||||
export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollEnded", "scheduledNotePosted", "scheduledNotePostFailed", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "chatRoomInvitationReceived", "achievementEarned", "exportCompleted", "test", "login", "createToken"];
|
||||
|
||||
// @public (undocumented)
|
||||
export function nyaize(text: string): string;
|
||||
|
|
|
@ -4168,6 +4168,15 @@ export type components = {
|
|||
/** Format: misskey:id */
|
||||
userListId: string;
|
||||
};
|
||||
scheduledNotePostFailed?: {
|
||||
/** @enum {string} */
|
||||
type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
|
||||
} | {
|
||||
/** @enum {string} */
|
||||
type: 'list';
|
||||
/** Format: misskey:id */
|
||||
userListId: string;
|
||||
};
|
||||
receiveFollowRequest?: {
|
||||
/** @enum {string} */
|
||||
type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
|
||||
|
@ -4463,6 +4472,7 @@ export type components = {
|
|||
/** @enum {string|null} */
|
||||
reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null;
|
||||
scheduledAt: number | null;
|
||||
isActuallyScheduled: boolean;
|
||||
};
|
||||
NoteReaction: {
|
||||
/** Format: id */
|
||||
|
@ -4579,6 +4589,14 @@ export type components = {
|
|||
/** @enum {string} */
|
||||
type: 'scheduledNotePosted';
|
||||
note: components['schemas']['Note'];
|
||||
} | {
|
||||
/** Format: id */
|
||||
id: string;
|
||||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
/** @enum {string} */
|
||||
type: 'scheduledNotePostFailed';
|
||||
noteDraft: components['schemas']['NoteDraft'];
|
||||
} | {
|
||||
/** Format: id */
|
||||
id: string;
|
||||
|
@ -11680,6 +11698,15 @@ export interface operations {
|
|||
/** Format: misskey:id */
|
||||
userListId: string;
|
||||
};
|
||||
scheduledNotePostFailed?: {
|
||||
/** @enum {string} */
|
||||
type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
|
||||
} | {
|
||||
/** @enum {string} */
|
||||
type: 'list';
|
||||
/** Format: misskey:id */
|
||||
userListId: string;
|
||||
};
|
||||
receiveFollowRequest?: {
|
||||
/** @enum {string} */
|
||||
type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
|
||||
|
@ -25981,8 +26008,8 @@ export interface operations {
|
|||
untilDate?: number;
|
||||
/** @default true */
|
||||
markAsRead?: boolean;
|
||||
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'scheduledNotePostFailed' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'scheduledNotePostFailed' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -26066,8 +26093,8 @@ export interface operations {
|
|||
untilDate?: number;
|
||||
/** @default true */
|
||||
markAsRead?: boolean;
|
||||
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'scheduledNotePostFailed' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'scheduledNotePostFailed' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -27350,6 +27377,15 @@ export interface operations {
|
|||
/** Format: misskey:id */
|
||||
userListId: string;
|
||||
};
|
||||
scheduledNotePostFailed?: {
|
||||
/** @enum {string} */
|
||||
type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
|
||||
} | {
|
||||
/** @enum {string} */
|
||||
type: 'list';
|
||||
/** Format: misskey:id */
|
||||
userListId: string;
|
||||
};
|
||||
receiveFollowRequest?: {
|
||||
/** @enum {string} */
|
||||
type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
|
||||
|
@ -29179,32 +29215,34 @@ export interface operations {
|
|||
* @default public
|
||||
* @enum {string}
|
||||
*/
|
||||
visibility?: 'public' | 'home' | 'followers' | 'specified';
|
||||
visibleUserIds?: string[];
|
||||
cw?: string | null;
|
||||
hashtag?: string | null;
|
||||
visibility: 'public' | 'home' | 'followers' | 'specified';
|
||||
visibleUserIds: string[];
|
||||
cw: string | null;
|
||||
hashtag: string | null;
|
||||
/** @default false */
|
||||
localOnly?: boolean;
|
||||
localOnly: boolean;
|
||||
/**
|
||||
* @default null
|
||||
* @enum {string|null}
|
||||
*/
|
||||
reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
|
||||
reactionAcceptance: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
|
||||
/** Format: misskey:id */
|
||||
replyId?: string | null;
|
||||
replyId: string | null;
|
||||
/** Format: misskey:id */
|
||||
renoteId?: string | null;
|
||||
renoteId: string | null;
|
||||
/** Format: misskey:id */
|
||||
channelId?: string | null;
|
||||
text?: string | null;
|
||||
fileIds?: string[];
|
||||
poll?: {
|
||||
channelId: string | null;
|
||||
text: string | null;
|
||||
fileIds: string[];
|
||||
poll: {
|
||||
choices: string[];
|
||||
multiple?: boolean;
|
||||
expiresAt?: number | null;
|
||||
expiredAfter?: number | null;
|
||||
} | null;
|
||||
scheduledAt?: number | null;
|
||||
scheduledAt: number | null;
|
||||
/** @default false */
|
||||
isActuallyScheduled: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -29351,6 +29389,7 @@ export interface operations {
|
|||
untilId?: string;
|
||||
sinceDate?: number;
|
||||
untilDate?: number;
|
||||
scheduled?: boolean | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -29417,20 +29456,13 @@ export interface operations {
|
|||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
draftId: string;
|
||||
/**
|
||||
* @default public
|
||||
* @enum {string}
|
||||
*/
|
||||
/** @enum {string} */
|
||||
visibility?: 'public' | 'home' | 'followers' | 'specified';
|
||||
visibleUserIds?: string[];
|
||||
cw?: string | null;
|
||||
hashtag?: string | null;
|
||||
/** @default false */
|
||||
localOnly?: boolean;
|
||||
/**
|
||||
* @default null
|
||||
* @enum {string|null}
|
||||
*/
|
||||
/** @enum {string|null} */
|
||||
reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
|
||||
/** Format: misskey:id */
|
||||
replyId?: string | null;
|
||||
|
@ -29447,6 +29479,7 @@ export interface operations {
|
|||
expiredAfter?: number | null;
|
||||
} | null;
|
||||
scheduledAt?: number | null;
|
||||
isActuallyScheduled?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -27,6 +27,7 @@ export const notificationTypes = [
|
|||
'reaction',
|
||||
'pollEnded',
|
||||
'scheduledNotePosted',
|
||||
'scheduledNotePostFailed',
|
||||
'receiveFollowRequest',
|
||||
'followRequestAccepted',
|
||||
'groupInvited',
|
||||
|
|
Loading…
Reference in New Issue