summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMar0xy <marie@kaifa.ch>2023-11-30 02:11:47 +0100
committerMar0xy <marie@kaifa.ch>2023-11-30 02:11:47 +0100
commit65a2ea6a74fbcb56216d667a9feb8259f744d30d (patch)
treef87bdf251bdabd0de93ed3fbf51f485d18e24a0d
parentchore: remove `rmquote` locale (diff)
downloadsharkey-65a2ea6a74fbcb56216d667a9feb8259f744d30d.tar.gz
sharkey-65a2ea6a74fbcb56216d667a9feb8259f744d30d.tar.bz2
sharkey-65a2ea6a74fbcb56216d667a9feb8259f744d30d.zip
upd: improve post editing on polls
Fixes not being able to edit post if poll expiry was set and now checks properly if poll was edited or not
-rw-r--r--packages/backend/src/core/NoteEditService.ts24
-rw-r--r--packages/backend/src/server/api/endpoints/notes/edit.ts163
-rw-r--r--packages/frontend/src/components/MkPostForm.vue4
-rw-r--r--packages/misskey-js/src/entities.ts1
4 files changed, 95 insertions, 97 deletions
diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts
index dfcff0e5b1..e5918e6c2f 100644
--- a/packages/backend/src/core/NoteEditService.ts
+++ b/packages/backend/src/core/NoteEditService.ts
@@ -14,18 +14,15 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf
import { extractHashtags } from '@/misc/extract-hashtags.js';
import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { MiNote } from '@/models/Note.js';
-import type { NoteEditRepository, ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { NoteEditRepository, ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository, PollsRepository } from '@/models/_.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiApp } from '@/models/App.js';
import { concat } from '@/misc/prelude/array.js';
import { IdService } from '@/core/IdService.js';
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { MiPoll, type IPoll } from '@/models/Poll.js';
-import { checkWordMute } from '@/misc/check-word-mute.js';
import type { MiChannel } from '@/models/Channel.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
-import { MemorySingleCache } from '@/misc/cache.js';
-import type { MiUserProfile } from '@/models/UserProfile.js';
import { RelayService } from '@/core/RelayService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { DI } from '@/di-symbols.js';
@@ -35,7 +32,6 @@ import ActiveUsersChart from '@/core/chart/charts/active-users.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { WebhookService } from '@/core/WebhookService.js';
-import { HashtagService } from '@/core/HashtagService.js';
import { QueueService } from '@/core/QueueService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -48,11 +44,7 @@ import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js';
-import { FeaturedService } from '@/core/FeaturedService.js';
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
-import { AntennaService } from './AntennaService.js';
-import NotesChart from './chart/charts/notes.js';
-import PerUserNotesChart from './chart/charts/per-user-notes.js';
import { UtilityService } from '@/core/UtilityService.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@@ -191,6 +183,9 @@ export class NoteEditService implements OnApplicationShutdown {
@Inject(DI.noteEditRepository)
private noteEditRepository: NoteEditRepository,
+ @Inject(DI.pollsRepository)
+ private pollsRepository: PollsRepository,
+
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
private idService: IdService,
@@ -201,18 +196,13 @@ export class NoteEditService implements OnApplicationShutdown {
private notificationService: NotificationService,
private relayService: RelayService,
private federatedInstanceService: FederatedInstanceService,
- private hashtagService: HashtagService,
- private antennaService: AntennaService,
private webhookService: WebhookService,
- private featuredService: FeaturedService,
private remoteUserResolveService: RemoteUserResolveService,
private apDeliverManagerService: ApDeliverManagerService,
private apRendererService: ApRendererService,
private roleService: RoleService,
private metaService: MetaService,
private searchService: SearchService,
- private notesChart: NotesChart,
- private perUserNotesChart: PerUserNotesChart,
private activeUsersChart: ActiveUsersChart,
private instanceChart: InstanceChart,
private utilityService: UtilityService,
@@ -385,6 +375,10 @@ export class NoteEditService implements OnApplicationShutdown {
update.hasPoll = !!data.poll;
}
+ const poll = await this.pollsRepository.findOneBy({ noteId: oldnote.id });
+
+ const oldPoll = poll ? { choices: poll.choices, multiple: poll.multiple, expiresAt: poll.expiresAt } : null;
+
if (Object.keys(update).length > 0) {
const exists = await this.noteEditRepository.findOneBy({ noteId: oldnote.id });
@@ -456,7 +450,7 @@ export class NoteEditService implements OnApplicationShutdown {
}));
}
- if (data.poll != null) {
+ if (data.poll != null && JSON.stringify(data.poll) !== JSON.stringify(oldPoll)) {
// Start transaction
await this.db.transaction(async transactionalEntityManager => {
await transactionalEntityManager.update(MiNote, oldnote.id, note);
diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts
index 6140c80a5d..49fa4c3bd6 100644
--- a/packages/backend/src/server/api/endpoints/notes/edit.ts
+++ b/packages/backend/src/server/api/endpoints/notes/edit.ts
@@ -14,7 +14,7 @@ import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
export const meta = {
- tags: ["notes"],
+ tags: ['notes'],
requireCredential: true,
@@ -23,99 +23,99 @@ export const meta = {
max: 300,
},
- kind: "write:notes",
+ kind: 'write:notes',
res: {
- type: "object",
+ type: 'object',
optional: false,
nullable: false,
properties: {
createdNote: {
- type: "object",
+ type: 'object',
optional: false,
nullable: false,
- ref: "Note",
+ ref: 'Note',
},
},
},
errors: {
noSuchRenoteTarget: {
- message: "No such renote target.",
- code: "NO_SUCH_RENOTE_TARGET",
- id: "b5c90186-4ab0-49c8-9bba-a1f76c282ba4",
+ message: 'No such renote target.',
+ code: 'NO_SUCH_RENOTE_TARGET',
+ id: 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4',
},
cannotReRenote: {
- message: "You can not Renote a pure Renote.",
- code: "CANNOT_RENOTE_TO_A_PURE_RENOTE",
- id: "fd4cc33e-2a37-48dd-99cc-9b806eb2031a",
+ message: 'You can not Renote a pure Renote.',
+ code: 'CANNOT_RENOTE_TO_A_PURE_RENOTE',
+ id: 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a',
},
noSuchReplyTarget: {
- message: "No such reply target.",
- code: "NO_SUCH_REPLY_TARGET",
- id: "749ee0f6-d3da-459a-bf02-282e2da4292c",
+ message: 'No such reply target.',
+ code: 'NO_SUCH_REPLY_TARGET',
+ id: '749ee0f6-d3da-459a-bf02-282e2da4292c',
},
cannotReplyToPureRenote: {
- message: "You can not reply to a pure Renote.",
- code: "CANNOT_REPLY_TO_A_PURE_RENOTE",
- id: "3ac74a84-8fd5-4bb0-870f-01804f82ce15",
+ message: 'You can not reply to a pure Renote.',
+ code: 'CANNOT_REPLY_TO_A_PURE_RENOTE',
+ id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15',
},
cannotCreateAlreadyExpiredPoll: {
- message: "Poll is already expired.",
- code: "CANNOT_CREATE_ALREADY_EXPIRED_POLL",
- id: "04da457d-b083-4055-9082-955525eda5a5",
+ message: 'Poll is already expired.',
+ code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL',
+ id: '04da457d-b083-4055-9082-955525eda5a5',
},
noSuchChannel: {
- message: "No such channel.",
- code: "NO_SUCH_CHANNEL",
- id: "b1653923-5453-4edc-b786-7c4f39bb0bbb",
+ message: 'No such channel.',
+ code: 'NO_SUCH_CHANNEL',
+ id: 'b1653923-5453-4edc-b786-7c4f39bb0bbb',
},
youHaveBeenBlocked: {
- message: "You have been blocked by this user.",
- code: "YOU_HAVE_BEEN_BLOCKED",
- id: "b390d7e1-8a5e-46ed-b625-06271cafd3d3",
+ message: 'You have been blocked by this user.',
+ code: 'YOU_HAVE_BEEN_BLOCKED',
+ id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3',
},
accountLocked: {
- message: "You migrated. Your account is now locked.",
- code: "ACCOUNT_LOCKED",
- id: "d390d7e1-8a5e-46ed-b625-06271cafd3d3",
+ message: 'You migrated. Your account is now locked.',
+ code: 'ACCOUNT_LOCKED',
+ id: 'd390d7e1-8a5e-46ed-b625-06271cafd3d3',
},
needsEditId: {
- message: "You need to specify `editId`.",
- code: "NEEDS_EDIT_ID",
- id: "d697edc8-8c73-4de8-bded-35fd198b79e5",
+ message: 'You need to specify `editId`.',
+ code: 'NEEDS_EDIT_ID',
+ id: 'd697edc8-8c73-4de8-bded-35fd198b79e5',
},
noSuchNote: {
- message: "No such note.",
- code: "NO_SUCH_NOTE",
- id: "eef6c173-3010-4a23-8674-7c4fcaeba719",
+ message: 'No such note.',
+ code: 'NO_SUCH_NOTE',
+ id: 'eef6c173-3010-4a23-8674-7c4fcaeba719',
},
youAreNotTheAuthor: {
- message: "You are not the author of this note.",
- code: "YOU_ARE_NOT_THE_AUTHOR",
- id: "c6e61685-411d-43d0-b90a-a448d2539001",
+ message: 'You are not the author of this note.',
+ code: 'YOU_ARE_NOT_THE_AUTHOR',
+ id: 'c6e61685-411d-43d0-b90a-a448d2539001',
},
cannotPrivateRenote: {
- message: "You can not perform a private renote.",
- code: "CANNOT_PRIVATE_RENOTE",
- id: "19a50f1c-84fa-4e33-81d3-17834ccc0ad8",
+ message: 'You can not perform a private renote.',
+ code: 'CANNOT_PRIVATE_RENOTE',
+ id: '19a50f1c-84fa-4e33-81d3-17834ccc0ad8',
},
notLocalUser: {
- message: "You are not a local user.",
- code: "NOT_LOCAL_USER",
- id: "b907f407-2aa0-4283-800b-a2c56290b822",
+ message: 'You are not a local user.',
+ code: 'NOT_LOCAL_USER',
+ id: 'b907f407-2aa0-4283-800b-a2c56290b822',
},
cannotRenoteOutsideOfChannel: {
@@ -127,60 +127,63 @@ export const meta = {
} as const;
export const paramDef = {
- type: "object",
+ type: 'object',
properties: {
- editId: { type: "string", format: "misskey:id" },
- visibility: { type: "string", enum: ['public', 'home', 'followers', 'specified'], default: "public" },
+ editId: { type: 'string', format: 'misskey:id' },
+ visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' },
visibleUserIds: {
- type: "array",
+ type: 'array',
uniqueItems: true,
items: {
- type: "string",
- format: "misskey:id",
+ type: 'string',
+ format: 'misskey:id',
},
},
- text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true },
- cw: { type: "string", nullable: true, minLength: 1, maxLength: 250 },
- localOnly: { type: "boolean", default: false },
- noExtractMentions: { type: "boolean", default: false },
- noExtractHashtags: { type: "boolean", default: false },
- noExtractEmojis: { type: "boolean", default: false },
+ cw: { type: 'string', nullable: true, minLength: 1, maxLength: 250 },
+ localOnly: { type: 'boolean', default: false },
+ reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
+ noExtractMentions: { type: 'boolean', default: false },
+ noExtractHashtags: { type: 'boolean', default: false },
+ noExtractEmojis: { type: 'boolean', default: false },
+ replyId: { type: 'string', format: 'misskey:id', nullable: true },
+ renoteId: { type: 'string', format: 'misskey:id', nullable: true },
+ channelId: { type: 'string', format: 'misskey:id', nullable: true },
+ text: {
+ type: 'string',
+ minLength: 1,
+ maxLength: MAX_NOTE_TEXT_LENGTH,
+ nullable: true,
+ },
fileIds: {
- type: "array",
+ type: 'array',
uniqueItems: true,
minItems: 1,
maxItems: 16,
- items: { type: "string", format: "misskey:id" },
+ items: { type: 'string', format: 'misskey:id' },
},
mediaIds: {
- deprecated: true,
- description:
- "Use `fileIds` instead. If both are specified, this property is discarded.",
- type: "array",
+ type: 'array',
uniqueItems: true,
minItems: 1,
maxItems: 16,
- items: { type: "string", format: "misskey:id" },
+ items: { type: 'string', format: 'misskey:id' },
},
- replyId: { type: "string", format: "misskey:id", nullable: true },
- renoteId: { type: "string", format: "misskey:id", nullable: true },
- channelId: { type: "string", format: "misskey:id", nullable: true },
poll: {
- type: "object",
+ type: 'object',
nullable: true,
properties: {
choices: {
- type: "array",
+ type: 'array',
uniqueItems: true,
minItems: 2,
maxItems: 10,
- items: { type: "string", minLength: 1, maxLength: 50 },
+ items: { type: 'string', minLength: 1, maxLength: 50 },
},
- multiple: { type: "boolean", default: false },
- expiresAt: { type: "integer", nullable: true },
- expiredAfter: { type: "integer", nullable: true, minimum: 1 },
+ multiple: { type: 'boolean' },
+ expiresAt: { type: 'integer', nullable: true },
+ expiredAfter: { type: 'integer', nullable: true, minimum: 1 },
},
- required: ["choices"],
+ required: ['choices'],
},
},
anyOf: [
@@ -188,32 +191,32 @@ export const paramDef = {
// (re)note with text, files and poll are optional
properties: {
text: {
- type: "string",
+ type: 'string',
minLength: 1,
maxLength: MAX_NOTE_TEXT_LENGTH,
nullable: false,
},
},
- required: ["text"],
+ required: ['text'],
},
{
// (re)note with files, text and poll are optional
- required: ["fileIds"],
+ required: ['fileIds'],
},
{
// (re)note with files, text and poll are optional
- required: ["mediaIds"],
+ required: ['mediaIds'],
},
{
// (re)note with poll, text and files are optional
properties: {
- poll: { type: "object", nullable: false },
+ poll: { type: 'object', nullable: false },
},
- required: ["poll"],
+ required: ['poll'],
},
{
// pure renote
- required: ["renoteId"],
+ required: ['renoteId'],
},
],
} as const;
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index f72e4ddfc3..74038cd62c 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -931,8 +931,8 @@ onMounted(() => {
poll = {
choices: init.poll.choices.map(x => x.text),
multiple: init.poll.multiple,
- expiresAt: init.poll.expiresAt,
- expiredAfter: init.poll.expiredAfter,
+ expiresAt: init.poll.expiresAt ? new Date(init.poll.expiresAt).getTime().toString() : null,
+ expiredAfter: init.poll.expiredAfter ? new Date(init.poll.expiredAfter).getTime().toString() : null,
};
}
visibility = init.visibility;
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index ec9a3d45c3..10b9dd5eb4 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -217,6 +217,7 @@ export type Note = {
clippedCount?: number;
poll?: {
expiresAt: DateString | null;
+ expiredAfter: DateString | null;
multiple: boolean;
choices: {
isVoted: boolean;