revert: note editing

This commit is contained in:
syuilo 2023-10-04 12:05:01 +09:00
parent cc4fd6b5c5
commit b40329887f
23 changed files with 19 additions and 174 deletions

View file

@ -15,6 +15,7 @@
## 2023.10.0 ## 2023.10.0
### NOTE ### NOTE
- muted_noteテーブルは使われなくなったため手動で削除を行ってください。 - muted_noteテーブルは使われなくなったため手動で削除を行ってください。
- 2023.9.2で導入されたノート編集機能はクオリティの高い実装が困難であることが判明したため撤回されました
### Changes ### Changes
- API: users/notes, notes/local-timeline で fileType 指定はできなくなりました - API: users/notes, notes/local-timeline で fileType 指定はできなくなりました

1
locales/index.d.ts vendored
View file

@ -1545,7 +1545,6 @@ export interface Locale {
"gtlAvailable": string; "gtlAvailable": string;
"ltlAvailable": string; "ltlAvailable": string;
"canPublicNote": string; "canPublicNote": string;
"canEditNote": string;
"canInvite": string; "canInvite": string;
"inviteLimit": string; "inviteLimit": string;
"inviteLimitCycle": string; "inviteLimitCycle": string;

View file

@ -1466,7 +1466,6 @@ _role:
gtlAvailable: "グローバルタイムラインの閲覧" gtlAvailable: "グローバルタイムラインの閲覧"
ltlAvailable: "ローカルタイムラインの閲覧" ltlAvailable: "ローカルタイムラインの閲覧"
canPublicNote: "パブリック投稿の許可" canPublicNote: "パブリック投稿の許可"
canEditNote: "ノートの編集"
canInvite: "サーバー招待コードの発行" canInvite: "サーバー招待コードの発行"
inviteLimit: "招待コードの作成可能数" inviteLimit: "招待コードの作成可能数"
inviteLimitCycle: "招待コードの発行間隔" inviteLimitCycle: "招待コードの発行間隔"

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class RevertNoteEdit1696388600237 {
name = 'RevertNoteEdit1696388600237'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "updatedAt"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ADD "updatedAt" TIMESTAMP WITH TIME ZONE`);
}
}

View file

@ -26,7 +26,6 @@ export type RolePolicies = {
gtlAvailable: boolean; gtlAvailable: boolean;
ltlAvailable: boolean; ltlAvailable: boolean;
canPublicNote: boolean; canPublicNote: boolean;
canEditNote: boolean;
canInvite: boolean; canInvite: boolean;
inviteLimit: number; inviteLimit: number;
inviteLimitCycle: number; inviteLimitCycle: number;
@ -52,7 +51,6 @@ export const DEFAULT_POLICIES: RolePolicies = {
gtlAvailable: true, gtlAvailable: true,
ltlAvailable: true, ltlAvailable: true,
canPublicNote: true, canPublicNote: true,
canEditNote: true,
canInvite: false, canInvite: false,
inviteLimit: 0, inviteLimit: 0,
inviteLimitCycle: 60 * 24 * 7, inviteLimitCycle: 60 * 24 * 7,
@ -298,7 +296,6 @@ export class RoleService implements OnApplicationShutdown {
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)), gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)), ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)), canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
canInvite: calc('canInvite', vs => vs.some(v => v === true)), canInvite: calc('canInvite', vs => vs.some(v => v === true)),
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)), inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)), inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),

View file

@ -308,7 +308,6 @@ export class NoteEntityService implements OnModuleInit {
const packed: Packed<'Note'> = await awaitAll({ const packed: Packed<'Note'> = await awaitAll({
id: note.id, id: note.id,
createdAt: note.createdAt.toISOString(), createdAt: note.createdAt.toISOString(),
updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
userId: note.userId, userId: note.userId,
user: this.userEntityService.pack(note.user ?? note.userId, me, { user: this.userEntityService.pack(note.user ?? note.userId, me, {
detail: false, detail: false,

View file

@ -24,11 +24,6 @@ export class MiNote {
}) })
public createdAt: Date; public createdAt: Date;
@Column('timestamp with time zone', {
default: null,
})
public updatedAt: Date | null;
@Index() @Index()
@Column({ @Column({
...id(), ...id(),

View file

@ -17,11 +17,6 @@ export const packedNoteSchema = {
optional: false, nullable: false, optional: false, nullable: false,
format: 'date-time', format: 'date-time',
}, },
updatedAt: {
type: 'string',
optional: true, nullable: true,
format: 'date-time',
},
deletedAt: { deletedAt: {
type: 'string', type: 'string',
optional: true, nullable: true, optional: true, nullable: true,

View file

@ -257,7 +257,6 @@ import * as ep___notes_clips from './endpoints/notes/clips.js';
import * as ep___notes_conversation from './endpoints/notes/conversation.js'; import * as ep___notes_conversation from './endpoints/notes/conversation.js';
import * as ep___notes_create from './endpoints/notes/create.js'; import * as ep___notes_create from './endpoints/notes/create.js';
import * as ep___notes_delete from './endpoints/notes/delete.js'; import * as ep___notes_delete from './endpoints/notes/delete.js';
import * as ep___notes_update from './endpoints/notes/update.js';
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js'; import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
import * as ep___notes_featured from './endpoints/notes/featured.js'; import * as ep___notes_featured from './endpoints/notes/featured.js';
@ -607,7 +606,6 @@ const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes
const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default }; const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default };
const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default }; const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default };
const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default }; const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default };
const $notes_update: Provider = { provide: 'ep:notes/update', useClass: ep___notes_update.default };
const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default }; const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default };
const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default }; const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default }; const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
@ -961,7 +959,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$notes_conversation, $notes_conversation,
$notes_create, $notes_create,
$notes_delete, $notes_delete,
$notes_update,
$notes_favorites_create, $notes_favorites_create,
$notes_favorites_delete, $notes_favorites_delete,
$notes_featured, $notes_featured,
@ -1309,7 +1306,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$notes_conversation, $notes_conversation,
$notes_create, $notes_create,
$notes_delete, $notes_delete,
$notes_update,
$notes_favorites_create, $notes_favorites_create,
$notes_favorites_delete, $notes_favorites_delete,
$notes_featured, $notes_featured,

View file

@ -257,7 +257,6 @@ import * as ep___notes_clips from './endpoints/notes/clips.js';
import * as ep___notes_conversation from './endpoints/notes/conversation.js'; import * as ep___notes_conversation from './endpoints/notes/conversation.js';
import * as ep___notes_create from './endpoints/notes/create.js'; import * as ep___notes_create from './endpoints/notes/create.js';
import * as ep___notes_delete from './endpoints/notes/delete.js'; import * as ep___notes_delete from './endpoints/notes/delete.js';
import * as ep___notes_update from './endpoints/notes/update.js';
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js'; import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
import * as ep___notes_featured from './endpoints/notes/featured.js'; import * as ep___notes_featured from './endpoints/notes/featured.js';
@ -605,7 +604,6 @@ const eps = [
['notes/conversation', ep___notes_conversation], ['notes/conversation', ep___notes_conversation],
['notes/create', ep___notes_create], ['notes/create', ep___notes_create],
['notes/delete', ep___notes_delete], ['notes/delete', ep___notes_delete],
['notes/update', ep___notes_update],
['notes/favorites/create', ep___notes_favorites_create], ['notes/favorites/create', ep___notes_favorites_create],
['notes/favorites/delete', ep___notes_favorites_delete], ['notes/favorites/delete', ep___notes_favorites_delete],
['notes/featured', ep___notes_featured], ['notes/featured', ep___notes_featured],

View file

@ -1,89 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import ms from 'ms';
import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository, NotesRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { NoteDeleteService } from '@/core/NoteDeleteService.js';
import { DI } from '@/di-symbols.js';
import { GetterService } from '@/server/api/GetterService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['notes'],
requireCredential: true,
requireRolePolicy: 'canEditNote',
kind: 'write:notes',
limit: {
duration: ms('1hour'),
max: 10,
minInterval: ms('1sec'),
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: 'a6584e14-6e01-4ad3-b566-851e7bf0d474',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
noteId: { type: 'string', format: 'misskey:id' },
text: {
type: 'string',
minLength: 1,
maxLength: MAX_NOTE_TEXT_LENGTH,
nullable: false,
},
cw: { type: 'string', nullable: true, maxLength: 100 },
},
required: ['noteId', 'text', 'cw'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private getterService: GetterService,
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const note = await this.getterService.getNote(ps.noteId).catch(err => {
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw err;
});
if (note.userId !== me.id) {
throw new ApiError(meta.errors.noSuchNote);
}
await this.notesRepository.update({ id: note.id }, {
updatedAt: new Date(),
cw: ps.cw,
text: ps.text,
});
this.globalEventService.publishNoteStream(note.id, 'updated', {
cw: ps.cw,
text: ps.text,
});
});
}
}

View file

@ -93,9 +93,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<footer> <footer>
<div :class="$style.noteFooterInfo"> <div :class="$style.noteFooterInfo">
<div v-if="appearNote.updatedAt">
{{ i18n.ts.edited }}: <MkTime :time="appearNote.updatedAt" mode="detail"/>
</div>
<MkA :to="notePage(appearNote)"> <MkA :to="notePage(appearNote)">
<MkTime :time="appearNote.createdAt" mode="detail"/> <MkTime :time="appearNote.createdAt" mode="detail"/>
</MkA> </MkA>

View file

@ -14,7 +14,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/> <img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
</div> </div>
<div :class="$style.info"> <div :class="$style.info">
<span v-if="note.updatedAt" style="margin-right: 0.5em;" :title="i18n.ts.edited"><i class="ti ti-pencil"></i></span>
<MkA :to="notePage(note)"> <MkA :to="notePage(note)">
<MkTime :time="note.createdAt"/> <MkTime :time="note.createdAt"/>
</MkA> </MkA>

View file

@ -143,7 +143,6 @@ const props = withDefaults(defineProps<{
fixed?: boolean; fixed?: boolean;
autofocus?: boolean; autofocus?: boolean;
freezeAfterPosted?: boolean; freezeAfterPosted?: boolean;
updateMode?: boolean;
}>(), { }>(), {
initialVisibleUsers: () => [], initialVisibleUsers: () => [],
autofocus: true, autofocus: true,
@ -710,7 +709,6 @@ async function post(ev?: MouseEvent) {
visibility: visibility, visibility: visibility,
visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined, visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
reactionAcceptance, reactionAcceptance,
noteId: props.updateMode ? props.initialNote?.id : undefined,
}; };
if (withHashtags && hashtags && hashtags.trim() !== '') { if (withHashtags && hashtags && hashtags.trim() !== '') {
@ -733,7 +731,7 @@ async function post(ev?: MouseEvent) {
} }
posting = true; posting = true;
os.api(props.updateMode ? 'notes/update' : 'notes/create', postData, token).then(() => { os.api('notes/create', postData, token).then(() => {
if (props.freezeAfterPosted) { if (props.freezeAfterPosted) {
posted = true; posted = true;
} else { } else {

View file

@ -30,7 +30,6 @@ const props = defineProps<{
instant?: boolean; instant?: boolean;
fixed?: boolean; fixed?: boolean;
autofocus?: boolean; autofocus?: boolean;
updateMode?: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{

View file

@ -61,7 +61,6 @@ export const ROLE_POLICIES = [
'gtlAvailable', 'gtlAvailable',
'ltlAvailable', 'ltlAvailable',
'canPublicNote', 'canPublicNote',
'canEditNote',
'canInvite', 'canInvite',
'inviteLimit', 'inviteLimit',
'inviteLimitCycle', 'inviteLimitCycle',

View file

@ -160,26 +160,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</MkFolder> </MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canEditNote, 'canEditNote'])">
<template #label>{{ i18n.ts._role._options.canEditNote }}</template>
<template #suffix>
<span v-if="role.policies.canEditNote.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ role.policies.canEditNote.value ? i18n.ts.yes : i18n.ts.no }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canEditNote)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="role.policies.canEditNote.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
</MkSwitch>
<MkSwitch v-model="role.policies.canEditNote.value" :disabled="role.policies.canEditNote.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
<MkRange v-model="role.policies.canEditNote.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.canInvite, 'canInvite'])"> <MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
<template #label>{{ i18n.ts._role._options.canInvite }}</template> <template #label>{{ i18n.ts._role._options.canInvite }}</template>
<template #suffix> <template #suffix>

View file

@ -48,14 +48,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch> </MkSwitch>
</MkFolder> </MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canEditNote, 'canEditNote'])">
<template #label>{{ i18n.ts._role._options.canEditNote }}</template>
<template #suffix>{{ policies.canEditNote ? i18n.ts.yes : i18n.ts.no }}</template>
<MkSwitch v-model="policies.canEditNote">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])"> <MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
<template #label>{{ i18n.ts._role._options.canInvite }}</template> <template #label>{{ i18n.ts._role._options.canInvite }}</template>
<template #suffix>{{ policies.canInvite ? i18n.ts.yes : i18n.ts.no }}</template> <template #suffix>{{ policies.canInvite ? i18n.ts.yes : i18n.ts.no }}</template>

View file

@ -172,10 +172,6 @@ export function getNoteMenu(props: {
}); });
} }
function edit(): void {
os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel, updateMode: true });
}
function toggleFavorite(favorite: boolean): void { function toggleFavorite(favorite: boolean): void {
claimAchievement('noteFavorited1'); claimAchievement('noteFavorited1');
os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', {
@ -356,11 +352,6 @@ export function getNoteMenu(props: {
), ),
...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [ ...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
null, null,
appearNote.userId === $i.id && $i.policies.canEditNote ? {
icon: 'ti ti-edit',
text: i18n.ts.edit,
action: edit,
} : undefined,
appearNote.userId === $i.id ? { appearNote.userId === $i.id ? {
icon: 'ti ti-edit', icon: 'ti ti-edit',
text: i18n.ts.deleteAndEdit, text: i18n.ts.deleteAndEdit,

View file

@ -71,13 +71,6 @@ export function useNoteCapture(props: {
break; break;
} }
case 'updated': {
note.value.updatedAt = new Date().toISOString();
note.value.cw = body.cw;
note.value.text = body.text;
break;
}
case 'deleted': { case 'deleted': {
props.isDeletedRef.value = true; props.isDeletedRef.value = true;
break; break;

View file

@ -2639,7 +2639,6 @@ export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
type Note = { type Note = {
id: ID; id: ID;
createdAt: DateString; createdAt: DateString;
updatedAt?: DateString | null;
text: string | null; text: string | null;
cw: string | null; cw: string | null;
user: User; user: User;
@ -2979,7 +2978,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts // src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
// src/api.types.ts:630:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts // src/api.types.ts:630:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
// src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts // src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
// src/entities.ts:595:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts // src/entities.ts:594:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package) // (No @packageDocumentation comment for this package)

View file

@ -177,7 +177,6 @@ export type GalleryPost = {
export type Note = { export type Note = {
id: ID; id: ID;
createdAt: DateString; createdAt: DateString;
updatedAt?: DateString | null;
text: string | null; text: string | null;
cw: string | null; cw: string | null;
user: User; user: User;

View file

@ -133,13 +133,6 @@ export type NoteUpdatedEvent = {
body: { body: {
deletedAt: string; deletedAt: string;
}; };
} | {
id: Note['id'];
type: 'updated';
body: {
cw: string | null;
text: string;
};
} | { } | {
id: Note['id']; id: Note['id'];
type: 'pollVoted'; type: 'pollVoted';