Compare commits
16 Commits
c6ed8e0e36
...
a6bd5a1903
Author | SHA1 | Date |
---|---|---|
|
a6bd5a1903 | |
|
b97f4a9407 | |
|
f0c297c483 | |
|
9bd4dbc906 | |
|
4ac1dc8740 | |
|
6471863ba5 | |
|
45074de293 | |
|
5e766ae230 | |
|
5d2bc61ede | |
|
1fe2264f41 | |
|
2b24dd6075 | |
|
43f82593ab | |
|
41336d880f | |
|
2faa39f5ae | |
|
1df36d5fc7 | |
|
914eb0bd35 |
|
@ -5,6 +5,7 @@
|
|||
|
||||
### General
|
||||
- Feat: 予約投稿ができるようになりました
|
||||
- デフォルトで作成可能数は1になっています。適宜ロールのポリシーで設定を行ってください。
|
||||
- Enhance: 広告ごとにセンシティブフラグを設定できるようになりました
|
||||
|
||||
### Client
|
||||
|
|
|
@ -7957,6 +7957,10 @@ export interface Locale extends ILocale {
|
|||
* サーバーサイドのノートの下書きの作成可能数
|
||||
*/
|
||||
"noteDraftLimit": string;
|
||||
/**
|
||||
* 予約投稿の同時作成可能数
|
||||
*/
|
||||
"scheduledNoteLimit": string;
|
||||
/**
|
||||
* ウォーターマーク機能の使用可否
|
||||
*/
|
||||
|
|
|
@ -2062,6 +2062,7 @@ _role:
|
|||
uploadableFileTypes_caption: "MIMEタイプを指定します。改行で区切って複数指定できるほか、アスタリスク(*)でワイルドカード指定できます。(例: image/*)"
|
||||
uploadableFileTypes_caption2: "ファイルによっては種別を判定できないことがあります。そのようなファイルを許可する場合は {x} を指定に追加してください。"
|
||||
noteDraftLimit: "サーバーサイドのノートの下書きの作成可能数"
|
||||
scheduledNoteLimit: "予約投稿の同時作成可能数"
|
||||
watermarkAvailable: "ウォーターマーク機能の使用可否"
|
||||
_condition:
|
||||
roleAssignedTo: "マニュアルロールにアサイン済み"
|
||||
|
|
|
@ -236,24 +236,25 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
isBot: MiUser['isBot'];
|
||||
isCat: MiUser['isCat'];
|
||||
}, data: {
|
||||
createdAt: Date;
|
||||
replyId: MiNote['id'] | null;
|
||||
renoteId: MiNote['id'] | null;
|
||||
fileIds: MiDriveFile['id'][];
|
||||
text: string | null;
|
||||
cw: string | null;
|
||||
visibility: string | null;
|
||||
visibility: string;
|
||||
visibleUserIds: MiUser['id'][];
|
||||
channelId: MiChannel['id'] | null;
|
||||
localOnly: boolean | null;
|
||||
localOnly: boolean;
|
||||
reactionAcceptance: MiNote['reactionAcceptance'];
|
||||
|
||||
poll: IPoll | null;
|
||||
apMentions?: MinimumUser[] | null;
|
||||
apHashtags?: string[] | null;
|
||||
apEmojis?: string[] | null;
|
||||
}): Promise<MiNote> {
|
||||
let visibleUsers: MiUser[] = [];
|
||||
if (data.visibleUserIds) {
|
||||
visibleUsers = await this.usersRepository.findBy({
|
||||
id: In(data.visibleUserIds),
|
||||
});
|
||||
}
|
||||
const visibleUsers = data.visibleUserIds.length > 0 ? await this.usersRepository.findBy({
|
||||
id: In(data.visibleUserIds),
|
||||
}) : [];
|
||||
|
||||
let files: MiDriveFile[] = [];
|
||||
if (data.fileIds.length > 0) {
|
||||
|
@ -352,13 +353,11 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
if (ps.poll) {
|
||||
if (typeof ps.poll.expiresAt === 'number') {
|
||||
if (ps.poll.expiresAt < Date.now()) {
|
||||
if (data.poll) {
|
||||
if (data.poll.expiresAt != null) {
|
||||
if (data.poll.expiresAt.getTime() < Date.now()) {
|
||||
throw new IdentifiableError('0c11c11e-0c8d-48e7-822c-76ccef660068', 'Poll expiration must be future time');
|
||||
}
|
||||
} else if (typeof ps.poll.expiredAfter === 'number') {
|
||||
ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -372,14 +371,10 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
return this.create(user, {
|
||||
createdAt: new Date(),
|
||||
createdAt: data.createdAt,
|
||||
files: files,
|
||||
poll: ps.poll ? {
|
||||
choices: ps.poll.choices,
|
||||
multiple: ps.poll.multiple ?? false,
|
||||
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
|
||||
} : undefined,
|
||||
text: data.text ?? undefined,
|
||||
poll: data.poll,
|
||||
text: data.text,
|
||||
reply,
|
||||
renote,
|
||||
cw: data.cw,
|
||||
|
@ -388,9 +383,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
visibility: data.visibility,
|
||||
visibleUsers,
|
||||
channel,
|
||||
apMentions: data.noExtractMentions ? [] : undefined,
|
||||
apHashtags: data.noExtractHashtags ? [] : undefined,
|
||||
apEmojis: data.noExtractEmojis ? [] : undefined,
|
||||
apMentions: data.apMentions,
|
||||
apHashtags: data.apHashtags,
|
||||
apEmojis: data.apEmojis,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -5,14 +5,12 @@
|
|||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import type { noteVisibilities, noteReactionAcceptances } from '@/types.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiNoteDraft, NoteDraftsRepository, MiNote, MiDriveFile, MiChannel, UsersRepository, DriveFilesRepository, NotesRepository, BlockingsRepository, ChannelsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||
import { IPoll } from '@/models/Poll.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { isRenote, isQuote } from '@/misc/is-renote.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
|
@ -61,12 +59,24 @@ export class NoteDraftService {
|
|||
@bindThis
|
||||
public async create(me: MiLocalUser, data: NoteDraftOptions): Promise<MiNoteDraft> {
|
||||
//#region check draft limit
|
||||
const policies = await this.roleService.getUserPolicies(me.id);
|
||||
|
||||
const currentCount = await this.noteDraftsRepository.countBy({
|
||||
userId: me.id,
|
||||
});
|
||||
if (currentCount >= (await this.roleService.getUserPolicies(me.id)).noteDraftLimit) {
|
||||
if (currentCount >= policies.noteDraftLimit) {
|
||||
throw new IdentifiableError('9ee33bbe-fde3-4c71-9b51-e50492c6b9c8', 'Too many drafts');
|
||||
}
|
||||
|
||||
if (data.isActuallyScheduled) {
|
||||
const currentScheduledCount = await this.noteDraftsRepository.countBy({
|
||||
userId: me.id,
|
||||
isActuallyScheduled: true,
|
||||
});
|
||||
if (currentScheduledCount >= policies.scheduledNoteLimit) {
|
||||
throw new IdentifiableError('c3275f19-4558-4c59-83e1-4f684b5fab66', 'Too many scheduled notes');
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
await this.validate(me, data);
|
||||
|
@ -95,6 +105,20 @@ export class NoteDraftService {
|
|||
throw new IdentifiableError('49cd6b9d-848e-41ee-b0b9-adaca711a6b1', 'No such note draft');
|
||||
}
|
||||
|
||||
//#region check draft limit
|
||||
const policies = await this.roleService.getUserPolicies(me.id);
|
||||
|
||||
if (!draft.isActuallyScheduled && data.isActuallyScheduled) {
|
||||
const currentScheduledCount = await this.noteDraftsRepository.countBy({
|
||||
userId: me.id,
|
||||
isActuallyScheduled: true,
|
||||
});
|
||||
if (currentScheduledCount >= policies.scheduledNoteLimit) {
|
||||
throw new IdentifiableError('bacdf856-5c51-4159-b88a-804fa5103be5', 'Too many scheduled notes');
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
await this.validate(me, data);
|
||||
|
||||
const updatedDraft = await this.noteDraftsRepository.createQueryBuilder().update()
|
||||
|
|
|
@ -69,6 +69,7 @@ export type RolePolicies = {
|
|||
chatAvailability: 'available' | 'readonly' | 'unavailable';
|
||||
uploadableFileTypes: string[];
|
||||
noteDraftLimit: number;
|
||||
scheduledNoteLimit: number;
|
||||
watermarkAvailable: boolean;
|
||||
};
|
||||
|
||||
|
@ -116,6 +117,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
|||
'audio/*',
|
||||
],
|
||||
noteDraftLimit: 10,
|
||||
scheduledNoteLimit: 1,
|
||||
watermarkAvailable: true,
|
||||
};
|
||||
|
||||
|
@ -440,6 +442,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
return [...set];
|
||||
}),
|
||||
noteDraftLimit: calc('noteDraftLimit', vs => Math.max(...vs)),
|
||||
scheduledNoteLimit: calc('scheduledNoteLimit', vs => Math.max(...vs)),
|
||||
watermarkAvailable: calc('watermarkAvailable', vs => vs.some(v => v === true)),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -129,6 +129,12 @@ export class NoteDraftEntityService implements OnModuleInit {
|
|||
allowRenoteToExternal: channel.allowRenoteToExternal,
|
||||
userId: channel.userId,
|
||||
} : undefined,
|
||||
poll: noteDraft.hasPoll ? {
|
||||
choices: noteDraft.pollChoices,
|
||||
multiple: noteDraft.pollMultiple,
|
||||
expiresAt: noteDraft.pollExpiresAt?.toISOString(),
|
||||
expiredAfter: noteDraft.pollExpiredAfter,
|
||||
} : null,
|
||||
|
||||
...(opts.detail ? {
|
||||
reply: noteDraft.replyId ? nullIfEntityNotFound(this.noteEntityService.pack(noteDraft.replyId, me, {
|
||||
|
@ -140,13 +146,6 @@ export class NoteDraftEntityService implements OnModuleInit {
|
|||
detail: true,
|
||||
skipHide: opts.skipHide,
|
||||
})) : undefined,
|
||||
|
||||
poll: noteDraft.hasPoll ? {
|
||||
choices: noteDraft.pollChoices,
|
||||
multiple: noteDraft.pollMultiple,
|
||||
expiresAt: noteDraft.pollExpiresAt?.toISOString(),
|
||||
expiredAfter: noteDraft.pollExpiredAfter,
|
||||
} : undefined,
|
||||
} : {} ),
|
||||
});
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ export const packedNoteDraftSchema = {
|
|||
},
|
||||
cw: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
|
@ -37,27 +37,23 @@ export const packedNoteDraftSchema = {
|
|||
},
|
||||
replyId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
optional: false, nullable: true,
|
||||
format: 'id',
|
||||
example: 'xxxxxxxxxx',
|
||||
},
|
||||
renoteId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
optional: false, nullable: true,
|
||||
format: 'id',
|
||||
example: 'xxxxxxxxxx',
|
||||
},
|
||||
reply: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
ref: 'Note',
|
||||
description: 'The reply target note contents if exists. If the reply target has been deleted since the draft was created, this will be null while replyId is not null.',
|
||||
},
|
||||
renote: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
ref: 'Note',
|
||||
description: 'The renote target note contents if exists. If the renote target has been deleted since the draft was created, this will be null while renoteId is not null.',
|
||||
},
|
||||
visibility: {
|
||||
type: 'string',
|
||||
|
@ -66,7 +62,7 @@ export const packedNoteDraftSchema = {
|
|||
},
|
||||
visibleUserIds: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
|
@ -75,7 +71,7 @@ export const packedNoteDraftSchema = {
|
|||
},
|
||||
fileIds: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
|
@ -93,11 +89,11 @@ export const packedNoteDraftSchema = {
|
|||
},
|
||||
hashtag: {
|
||||
type: 'string',
|
||||
optional: true, nullable: false,
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
poll: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
optional: false, nullable: true,
|
||||
properties: {
|
||||
expiresAt: {
|
||||
type: 'string',
|
||||
|
@ -124,9 +120,8 @@ export const packedNoteDraftSchema = {
|
|||
},
|
||||
channelId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
optional: false, nullable: true,
|
||||
format: 'id',
|
||||
example: 'xxxxxxxxxx',
|
||||
},
|
||||
channel: {
|
||||
type: 'object',
|
||||
|
@ -160,7 +155,7 @@ export const packedNoteDraftSchema = {
|
|||
},
|
||||
localOnly: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
reactionAcceptance: {
|
||||
type: 'string',
|
||||
|
|
|
@ -317,6 +317,10 @@ export const packedRolePoliciesSchema = {
|
|||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
scheduledNoteLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
watermarkAvailable: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
|
|
@ -40,11 +40,11 @@ export class PostScheduledNoteProcessorService {
|
|||
const note = await this.noteCreateService.fetchAndCreate(draft.user, {
|
||||
createdAt: new Date(),
|
||||
fileIds: draft.fileIds,
|
||||
poll: ps.poll ? {
|
||||
choices: ps.poll.choices,
|
||||
multiple: ps.poll.multiple ?? false,
|
||||
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
|
||||
} : undefined,
|
||||
poll: draft.hasPoll ? {
|
||||
choices: draft.pollChoices,
|
||||
multiple: draft.pollMultiple,
|
||||
expiresAt: draft.pollExpiredAfter ? new Date(Date.now() + draft.pollExpiredAfter) : draft.pollExpiresAt ? new Date(draft.pollExpiresAt) : null,
|
||||
} : null,
|
||||
text: draft.text ?? null,
|
||||
replyId: draft.replyId,
|
||||
renoteId: draft.renoteId,
|
||||
|
@ -54,9 +54,6 @@ export class PostScheduledNoteProcessorService {
|
|||
visibility: draft.visibility,
|
||||
visibleUserIds: draft.visibleUserIds,
|
||||
channelId: draft.channelId,
|
||||
apMentions: draft.noExtractMentions ? [] : undefined,
|
||||
apHashtags: draft.noExtractHashtags ? [] : undefined,
|
||||
apEmojis: draft.noExtractEmojis ? [] : undefined,
|
||||
});
|
||||
|
||||
// await不要
|
||||
|
|
|
@ -227,8 +227,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
poll: ps.poll ? {
|
||||
choices: ps.poll.choices,
|
||||
multiple: ps.poll.multiple ?? false,
|
||||
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
|
||||
} : undefined,
|
||||
expiresAt: ps.poll.expiredAfter ? new Date(Date.now() + ps.poll.expiredAfter) : ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
|
||||
} : null,
|
||||
text: ps.text ?? null,
|
||||
replyId: ps.replyId ?? null,
|
||||
renoteId: ps.renoteId ?? null,
|
||||
|
@ -246,16 +246,46 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
return {
|
||||
createdNote: await this.noteEntityService.pack(note, me),
|
||||
};
|
||||
} catch (e) {
|
||||
} catch (err) {
|
||||
// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
|
||||
if (e instanceof IdentifiableError) {
|
||||
if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
|
||||
if (err instanceof IdentifiableError) {
|
||||
if (err.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
|
||||
throw new ApiError(meta.errors.containsProhibitedWords);
|
||||
} else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') {
|
||||
} else if (err.id === '9f466dab-c856-48cd-9e65-ff90ff750580') {
|
||||
throw new ApiError(meta.errors.containsTooManyMentions);
|
||||
} else if (err.id === '801c046c-5bf5-4234-ad2b-e78fc20a2ac7') {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
} else if (err.id === '53983c56-e163-45a6-942f-4ddc485d4290') {
|
||||
throw new ApiError(meta.errors.noSuchRenoteTarget);
|
||||
} else if (err.id === 'bde24c37-121f-4e7d-980d-cec52f599f02') {
|
||||
throw new ApiError(meta.errors.cannotReRenote);
|
||||
} else if (err.id === '2b4fe776-4414-4a2d-ae39-f3418b8fd4d3') {
|
||||
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
} else if (err.id === '90b9d6f0-893a-4fef-b0f1-e9a33989f71a') {
|
||||
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
|
||||
} else if (err.id === '48d7a997-da5c-4716-b3c3-92db3f37bf7d') {
|
||||
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
|
||||
} else if (err.id === 'b060f9a6-8909-4080-9e0b-94d9fa6f6a77') {
|
||||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
} else if (err.id === '7e435f4a-780d-4cfc-a15a-42519bd6fb67') {
|
||||
throw new ApiError(meta.errors.cannotRenoteOutsideOfChannel);
|
||||
} else if (err.id === '60142edb-1519-408e-926d-4f108d27bee0') {
|
||||
throw new ApiError(meta.errors.noSuchReplyTarget);
|
||||
} else if (err.id === 'f089e4e2-c0e7-4f60-8a23-e5a6bf786b36') {
|
||||
throw new ApiError(meta.errors.cannotReplyToPureRenote);
|
||||
} else if (err.id === '11cd37b3-a411-4f77-8633-c580ce6a8dce') {
|
||||
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
|
||||
} else if (err.id === 'ced780a1-2012-4caf-bc7e-a95a291294cb') {
|
||||
throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
|
||||
} else if (err.id === 'b0df6025-f2e8-44b4-a26a-17ad99104612') {
|
||||
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
} else if (err.id === '0c11c11e-0c8d-48e7-822c-76ccef660068') {
|
||||
throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll);
|
||||
} else if (err.id === 'bfa3905b-25f5-4894-b430-da331a490e4b') {
|
||||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -124,6 +124,12 @@ export const meta = {
|
|||
id: '9ee33bbe-fde3-4c71-9b51-e50492c6b9c8',
|
||||
},
|
||||
|
||||
tooManyScheduledNotes: {
|
||||
message: 'You cannot create scheduled notes any more.',
|
||||
code: 'TOO_MANY_SCHEDULED_NOTES',
|
||||
id: '22ae69eb-09e3-4541-a850-773cfa45e693',
|
||||
},
|
||||
|
||||
cannotRenoteToExternal: {
|
||||
message: 'Cannot Renote to External.',
|
||||
code: 'CANNOT_RENOTE_TO_EXTERNAL',
|
||||
|
@ -244,6 +250,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
|
||||
case '215dbc76-336c-4d2a-9605-95766ba7dab0':
|
||||
throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
|
||||
case 'c3275f19-4558-4c59-83e1-4f684b5fab66':
|
||||
throw new ApiError(meta.errors.tooManyScheduledNotes);
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
|
|
|
@ -159,6 +159,12 @@ export const meta = {
|
|||
code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY',
|
||||
id: '215dbc76-336c-4d2a-9605-95766ba7dab0',
|
||||
},
|
||||
|
||||
tooManyScheduledNotes: {
|
||||
message: 'You cannot create scheduled notes any more.',
|
||||
code: 'TOO_MANY_SCHEDULED_NOTES',
|
||||
id: '02f5df79-08ae-4a33-8524-f1503c8f6212',
|
||||
},
|
||||
},
|
||||
|
||||
limit: {
|
||||
|
@ -287,6 +293,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.containsProhibitedWords);
|
||||
case '4de0363a-3046-481b-9b0f-feff3e211025':
|
||||
throw new ApiError(meta.errors.containsTooManyMentions);
|
||||
case 'bacdf856-5c51-4159-b88a-804fa5103be5':
|
||||
throw new ApiError(meta.errors.tooManyScheduledNotes);
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
|
|
|
@ -614,13 +614,13 @@ function showOtherSettings() {
|
|||
action: () => {
|
||||
toggleReactionAcceptance();
|
||||
},
|
||||
}, {
|
||||
}, ...($i.policies.scheduledNoteLimit > 0 ? [{
|
||||
icon: 'ti ti-calendar-time',
|
||||
text: i18n.ts.schedulePost + '...',
|
||||
action: () => {
|
||||
schedule();
|
||||
},
|
||||
}, { type: 'divider' }, {
|
||||
}] : []), { type: 'divider' }, {
|
||||
type: 'switch',
|
||||
icon: 'ti ti-eye',
|
||||
text: i18n.ts.preview,
|
||||
|
@ -1181,8 +1181,10 @@ function showPerUploadItemMenuViaContextmenu(item: UploaderItem, ev: MouseEvent)
|
|||
}
|
||||
|
||||
function showDraftMenu(ev: MouseEvent) {
|
||||
function showDraftsDialog() {
|
||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNoteDraftsDialog.vue')), {}, {
|
||||
function showDraftsDialog(scheduled: boolean) {
|
||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNoteDraftsDialog.vue')), {
|
||||
scheduled,
|
||||
}, {
|
||||
restore: async (draft: Misskey.entities.NoteDraft) => {
|
||||
text.value = draft.text ?? '';
|
||||
useCw.value = draft.cw != null;
|
||||
|
@ -1254,14 +1256,14 @@ function showDraftMenu(ev: MouseEvent) {
|
|||
text: i18n.ts._drafts.listDrafts,
|
||||
icon: 'ti ti-cloud-download',
|
||||
action: () => {
|
||||
showDraftsDialog();
|
||||
showDraftsDialog(false);
|
||||
},
|
||||
}, { type: 'divider' }, {
|
||||
type: 'button',
|
||||
text: i18n.ts._drafts.listScheduledNotes,
|
||||
icon: 'ti ti-clock-down',
|
||||
action: () => {
|
||||
showDraftsDialog();
|
||||
showDraftsDialog(true);
|
||||
},
|
||||
}], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints>(
|
|||
} else if (err.code === 'ROLE_PERMISSION_DENIED') {
|
||||
title = i18n.ts.permissionDeniedError;
|
||||
text = i18n.ts.permissionDeniedErrorDescription;
|
||||
} else if (err.code.startsWith('TOO_MANY')) {
|
||||
} else if (err.code.startsWith('TOO_MANY')) { // TODO: バックエンドに kind: client/contentsLimitExceeded みたいな感じで送るように統一してもらってそれで判定する
|
||||
title = i18n.ts.youCannotCreateAnymore;
|
||||
text = `${i18n.ts.error}: ${err.id}`;
|
||||
} else if (err.message.startsWith('Unexpected token')) {
|
||||
|
|
|
@ -802,6 +802,25 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.scheduledNoteLimit, 'scheduledNoteLimit'])">
|
||||
<template #label>{{ i18n.ts._role._options.scheduledNoteLimit }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="role.policies.scheduledNoteLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||
<span v-else>{{ role.policies.scheduledNoteLimit.value }}</span>
|
||||
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.scheduledNoteLimit)"></i></span>
|
||||
</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="role.policies.scheduledNoteLimit.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="role.policies.scheduledNoteLimit.value" :disabled="role.policies.scheduledNoteLimit.useDefault" type="number" :readonly="readonly">
|
||||
</MkInput>
|
||||
<MkRange v-model="role.policies.scheduledNoteLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.watermarkAvailable, 'watermarkAvailable'])">
|
||||
<template #label>{{ i18n.ts._role._options.watermarkAvailable }}</template>
|
||||
<template #suffix>
|
||||
|
@ -831,6 +850,7 @@ import { watch, ref, computed } from 'vue';
|
|||
import { throttle } from 'throttle-debounce';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import RolesEditorFormula from './RolesEditorFormula.vue';
|
||||
import type { MkSelectItem, GetMkSelectValueTypesFromDef } from '@/components/MkSelect.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkColorInput from '@/components/MkColorInput.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
|
@ -842,7 +862,6 @@ import FormSlot from '@/components/form/slot.vue';
|
|||
import { i18n } from '@/i18n.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
import type { MkSelectItem, GetMkSelectValueTypesFromDef } from '@/components/MkSelect.vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'update:modelValue', v: any): void;
|
||||
|
|
|
@ -304,6 +304,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.scheduledNoteLimit, 'scheduledNoteLimit'])">
|
||||
<template #label>{{ i18n.ts._role._options.scheduledNoteLimit }}</template>
|
||||
<template #suffix>{{ policies.scheduledNoteLimit }}</template>
|
||||
<MkInput v-model="policies.scheduledNoteLimit" type="number" :min="0">
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.watermarkAvailable, 'watermarkAvailable'])">
|
||||
<template #label>{{ i18n.ts._role._options.watermarkAvailable }}</template>
|
||||
<template #suffix>{{ policies.watermarkAvailable ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
|
|
|
@ -3441,7 +3441,7 @@ type RoleLite = components['schemas']['RoleLite'];
|
|||
type RolePolicies = components['schemas']['RolePolicies'];
|
||||
|
||||
// @public (undocumented)
|
||||
export const rolePolicies: readonly ["gtlAvailable", "ltlAvailable", "canPublicNote", "mentionLimit", "canInvite", "inviteLimit", "inviteLimitCycle", "inviteExpirationTime", "canManageCustomEmojis", "canManageAvatarDecorations", "canSearchNotes", "canSearchUsers", "canUseTranslator", "canHideAds", "driveCapacityMb", "maxFileSizeMb", "alwaysMarkNsfw", "canUpdateBioMedia", "pinLimit", "antennaLimit", "wordMuteLimit", "webhookLimit", "clipLimit", "noteEachClipsLimit", "userListLimit", "userEachUserListsLimit", "rateLimitFactor", "avatarDecorationLimit", "canImportAntennas", "canImportBlocking", "canImportFollowing", "canImportMuting", "canImportUserLists", "chatAvailability", "uploadableFileTypes", "noteDraftLimit", "watermarkAvailable"];
|
||||
export const rolePolicies: readonly ["gtlAvailable", "ltlAvailable", "canPublicNote", "mentionLimit", "canInvite", "inviteLimit", "inviteLimitCycle", "inviteExpirationTime", "canManageCustomEmojis", "canManageAvatarDecorations", "canSearchNotes", "canSearchUsers", "canUseTranslator", "canHideAds", "driveCapacityMb", "maxFileSizeMb", "alwaysMarkNsfw", "canUpdateBioMedia", "pinLimit", "antennaLimit", "wordMuteLimit", "webhookLimit", "clipLimit", "noteEachClipsLimit", "userListLimit", "userEachUserListsLimit", "rateLimitFactor", "avatarDecorationLimit", "canImportAntennas", "canImportBlocking", "canImportFollowing", "canImportMuting", "canImportUserLists", "chatAvailability", "uploadableFileTypes", "noteDraftLimit", "scheduledNoteLimit", "watermarkAvailable"];
|
||||
|
||||
// @public (undocumented)
|
||||
type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json'];
|
||||
|
|
|
@ -4424,42 +4424,31 @@ export type components = {
|
|||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
text: string | null;
|
||||
cw?: string | null;
|
||||
cw: string | null;
|
||||
/** Format: id */
|
||||
userId: string;
|
||||
user: components['schemas']['UserLite'];
|
||||
/**
|
||||
* Format: id
|
||||
* @example xxxxxxxxxx
|
||||
*/
|
||||
replyId?: string | null;
|
||||
/**
|
||||
* Format: id
|
||||
* @example xxxxxxxxxx
|
||||
*/
|
||||
renoteId?: string | null;
|
||||
/** @description The reply target note contents if exists. If the reply target has been deleted since the draft was created, this will be null while replyId is not null. */
|
||||
/** Format: id */
|
||||
replyId: string | null;
|
||||
/** Format: id */
|
||||
renoteId: string | null;
|
||||
reply?: components['schemas']['Note'] | null;
|
||||
/** @description The renote target note contents if exists. If the renote target has been deleted since the draft was created, this will be null while renoteId is not null. */
|
||||
renote?: components['schemas']['Note'] | null;
|
||||
/** @enum {string} */
|
||||
visibility: 'public' | 'home' | 'followers' | 'specified';
|
||||
visibleUserIds?: string[];
|
||||
fileIds?: string[];
|
||||
visibleUserIds: string[];
|
||||
fileIds: string[];
|
||||
files?: components['schemas']['DriveFile'][];
|
||||
hashtag?: string;
|
||||
poll?: {
|
||||
hashtag: string | null;
|
||||
poll: {
|
||||
/** Format: date-time */
|
||||
expiresAt?: string | null;
|
||||
expiredAfter?: number | null;
|
||||
multiple: boolean;
|
||||
choices: string[];
|
||||
} | null;
|
||||
/**
|
||||
* Format: id
|
||||
* @example xxxxxxxxxx
|
||||
*/
|
||||
channelId?: string | null;
|
||||
/** Format: id */
|
||||
channelId: string | null;
|
||||
channel?: {
|
||||
id: string;
|
||||
name: string;
|
||||
|
@ -4468,7 +4457,7 @@ export type components = {
|
|||
allowRenoteToExternal: boolean;
|
||||
userId: string | null;
|
||||
} | null;
|
||||
localOnly?: boolean;
|
||||
localOnly: boolean;
|
||||
/** @enum {string|null} */
|
||||
reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null;
|
||||
scheduledAt: number | null;
|
||||
|
@ -5289,6 +5278,7 @@ export type components = {
|
|||
/** @enum {string} */
|
||||
chatAvailability: 'available' | 'readonly' | 'unavailable';
|
||||
noteDraftLimit: number;
|
||||
scheduledNoteLimit: number;
|
||||
watermarkAvailable: boolean;
|
||||
};
|
||||
ReversiGameLite: {
|
||||
|
|
|
@ -229,6 +229,7 @@ export const rolePolicies = [
|
|||
'chatAvailability',
|
||||
'uploadableFileTypes',
|
||||
'noteDraftLimit',
|
||||
'scheduledNoteLimit',
|
||||
'watermarkAvailable',
|
||||
] as const;
|
||||
|
||||
|
|
Loading…
Reference in New Issue