feat: ability to make existing public posts private
Co-authored-by: sup39 <dev@sup39.dev>
This commit is contained in:
parent
fa0e65cc1b
commit
fb74a5eeda
10 changed files with 133 additions and 19 deletions
|
@ -10,6 +10,7 @@ Breaking changes are indicated by the :warning: icon.
|
|||
- `full`: `mod` permission + delete existing custom emojis
|
||||
- Emoji moderators are able to access to the endpoints under `admin/emoji/`
|
||||
- Removed `lang` from the response of `i` and the request parameter of `i/update`.
|
||||
- Added `notes/make-private` endpoint.
|
||||
|
||||
## v20240217
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ Critical security updates are indicated by the :warning: icon.
|
|||
- Fix a bug that made impossible to update user profiles under some conditions
|
||||
- Add "private" (only me) post visibility
|
||||
- It's just a paraphrase of DMs without recipients
|
||||
- You can also convert your existing public posts to private posts
|
||||
|
||||
## :warning: v20240217-1
|
||||
|
||||
|
|
|
@ -1174,6 +1174,8 @@ emojiModPerm: "Custom emoji management permission"
|
|||
emojiModPermDescription: "Add: Allow this user to add new custom emojis and to set tag/category/license to newly added custom emojis.\nAdd and Edit: \"Add\" Permission + Allow this user to edit the name/category/tag/license of the existing custom emojis.\nAllow All: \"Add and Edit\" Permission + Allow this user to delete existing custom emojis."
|
||||
private: "Private"
|
||||
privateDescription: "Make visible for you only"
|
||||
makePrivate: "Make private"
|
||||
makePrivateConfirm: "This operation will send a deletion request to remote servers and change the visibility to private. Proceed?"
|
||||
|
||||
_emojiModPerm:
|
||||
unauthorized: "None"
|
||||
|
|
|
@ -2028,3 +2028,5 @@ _emojiModPerm:
|
|||
full: "全て許可"
|
||||
private: "秘密"
|
||||
privateDescription: "あなた以外には非公開"
|
||||
makePrivate: "秘密にする"
|
||||
makePrivateConfirm: "リモートサーバーに削除リクエストを送信し、投稿の公開範囲を「秘密」にして他の人から見られないようにします。実行しますか?"
|
||||
|
|
|
@ -2014,3 +2014,5 @@ preventMisclick: "預防誤觸"
|
|||
hideFollowButtons: "隱藏會誤觸的追隨按鈕"
|
||||
private: "祕密"
|
||||
privateDescription: "僅你可見"
|
||||
makePrivate: "設為祕密"
|
||||
makePrivateConfirm: "此操作將向遠端伺服器發送刪除請求,並將貼文的公開範圍設為「祕密」。是否繼續?"
|
||||
|
|
|
@ -256,6 +256,7 @@ import * as ep___notes_globalTimeline from "./endpoints/notes/global-timeline.js
|
|||
import * as ep___notes_hybridTimeline from "./endpoints/notes/hybrid-timeline.js";
|
||||
import * as ep___notes_localTimeline from "./endpoints/notes/local-timeline.js";
|
||||
import * as ep___notes_recommendedTimeline from "./endpoints/notes/recommended-timeline.js";
|
||||
import * as ep___notes_makePrivate from "./endpoints/notes/make-private.js";
|
||||
import * as ep___notes_mentions from "./endpoints/notes/mentions.js";
|
||||
import * as ep___notes_polls_recommendation from "./endpoints/notes/polls/recommendation.js";
|
||||
import * as ep___notes_polls_vote from "./endpoints/notes/polls/vote.js";
|
||||
|
@ -611,6 +612,7 @@ const eps = [
|
|||
["notes/hybrid-timeline", ep___notes_hybridTimeline],
|
||||
["notes/local-timeline", ep___notes_localTimeline],
|
||||
["notes/recommended-timeline", ep___notes_recommendedTimeline],
|
||||
["notes/make-private", ep___notes_makePrivate],
|
||||
["notes/mentions", ep___notes_mentions],
|
||||
["notes/polls/recommendation", ep___notes_polls_recommendation],
|
||||
["notes/polls/vote", ep___notes_polls_vote],
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import deleteNote from "@/services/note/delete.js";
|
||||
import { Notes } from "@/models/index.js";
|
||||
import define from "@/server/api/define.js";
|
||||
import { getNote } from "@/server/api/common/getters.js";
|
||||
import { ApiError } from "@/server/api/error.js";
|
||||
import { SECOND, HOUR } from "@/const.js";
|
||||
import { publishNoteStream } from "@/services/stream.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["notes"],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: "write:notes",
|
||||
|
||||
limit: {
|
||||
duration: HOUR,
|
||||
max: 300,
|
||||
minInterval: SECOND,
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchNote: {
|
||||
message: "No such note.",
|
||||
code: "NO_SUCH_NOTE",
|
||||
id: "490be23f-8c1f-4796-819f-94cb4f9d1630",
|
||||
},
|
||||
|
||||
accessDenied: {
|
||||
message: "Access denied.",
|
||||
code: "ACCESS_DENIED",
|
||||
id: "fe8d7103-0ea8-4ec3-814d-f8b401dc69e9",
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
noteId: { type: "string", format: "misskey:id" },
|
||||
},
|
||||
required: ["noteId"],
|
||||
} as const;
|
||||
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
const note = await getNote(ps.noteId, user).catch((err) => {
|
||||
if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24")
|
||||
throw new ApiError(meta.errors.noSuchNote);
|
||||
throw err;
|
||||
});
|
||||
|
||||
if (note.userId !== user.id) {
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
await deleteNote(user, note, false, false);
|
||||
await Notes.update(note.id, {
|
||||
visibility: "specified",
|
||||
visibleUserIds: [],
|
||||
});
|
||||
|
||||
// Publish update event for the updated note details
|
||||
// TODO: Send "deleted" to other users?
|
||||
publishNoteStream(note.id, "updated", {
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
});
|
|
@ -32,26 +32,31 @@ export default async function (
|
|||
user: { id: User["id"]; uri: User["uri"]; host: User["host"] },
|
||||
note: Note,
|
||||
quiet = false,
|
||||
deleteFromDb = true,
|
||||
) {
|
||||
const deletedAt = new Date();
|
||||
|
||||
// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
|
||||
if (
|
||||
note.renoteId &&
|
||||
(await countSameRenotes(user.id, note.renoteId, note.id)) === 0
|
||||
(await countSameRenotes(user.id, note.renoteId, note.id)) === 0 &&
|
||||
deleteFromDb
|
||||
) {
|
||||
Notes.decrement({ id: note.renoteId }, "renoteCount", 1);
|
||||
Notes.decrement({ id: note.renoteId }, "score", 1);
|
||||
}
|
||||
|
||||
if (note.replyId) {
|
||||
if (note.replyId && deleteFromDb) {
|
||||
await Notes.decrement({ id: note.replyId }, "repliesCount", 1);
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
// Only broadcast "deleted" to local if the note is deleted from db
|
||||
if (deleteFromDb) {
|
||||
publishNoteStream(note.id, "deleted", {
|
||||
deletedAt: deletedAt,
|
||||
});
|
||||
}
|
||||
|
||||
//#region ローカルの投稿なら削除アクティビティを配送
|
||||
if (Users.isLocalUser(user) && !note.localOnly) {
|
||||
|
@ -116,10 +121,12 @@ export default async function (
|
|||
}
|
||||
}
|
||||
|
||||
if (deleteFromDb) {
|
||||
await Notes.delete({
|
||||
id: note.id,
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (meilisearch) {
|
||||
await meilisearch.deleteNotes(note.id);
|
||||
|
|
|
@ -73,6 +73,19 @@ export function getNoteMenu(props: {
|
|||
});
|
||||
}
|
||||
|
||||
function makePrivate(): void {
|
||||
os.confirm({
|
||||
type: "warning",
|
||||
text: i18n.ts.makePrivateConfirm,
|
||||
}).then(async ({ canceled }) => {
|
||||
if (canceled) return;
|
||||
|
||||
await os.api("notes/make-private", {
|
||||
noteId: appearNote.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toggleFavorite(favorite: boolean): void {
|
||||
os.apiWithDialog(
|
||||
favorite ? "notes/favorites/create" : "notes/favorites/delete",
|
||||
|
@ -437,6 +450,18 @@ export function getNoteMenu(props: {
|
|||
action: edit,
|
||||
}
|
||||
: undefined,
|
||||
isAppearAuthor &&
|
||||
!(
|
||||
appearNote.visibility === "specified" &&
|
||||
appearNote.visibleUserIds.length === 0
|
||||
)
|
||||
? {
|
||||
icon: `${icon("ph-eye-slash")}`,
|
||||
text: i18n.ts.makePrivate,
|
||||
danger: true,
|
||||
action: makePrivate,
|
||||
}
|
||||
: undefined,
|
||||
isAppearAuthor
|
||||
? {
|
||||
icon: `${icon("ph-eraser")}`,
|
||||
|
|
|
@ -80,6 +80,7 @@ export function useNoteCapture(props: {
|
|||
}
|
||||
|
||||
case "updated": {
|
||||
try {
|
||||
const editedNote = await os.api("notes/show", {
|
||||
noteId: id,
|
||||
});
|
||||
|
@ -91,6 +92,10 @@ export function useNoteCapture(props: {
|
|||
keys.forEach((key) => {
|
||||
note.value[key] = editedNote[key];
|
||||
});
|
||||
} catch {
|
||||
// delete the note if failing to get the edited note
|
||||
props.isDeletedRef.value = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue