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
|
- `full`: `mod` permission + delete existing custom emojis
|
||||||
- Emoji moderators are able to access to the endpoints under `admin/emoji/`
|
- 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`.
|
- Removed `lang` from the response of `i` and the request parameter of `i/update`.
|
||||||
|
- Added `notes/make-private` endpoint.
|
||||||
|
|
||||||
## v20240217
|
## 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
|
- Fix a bug that made impossible to update user profiles under some conditions
|
||||||
- Add "private" (only me) post visibility
|
- Add "private" (only me) post visibility
|
||||||
- It's just a paraphrase of DMs without recipients
|
- It's just a paraphrase of DMs without recipients
|
||||||
|
- You can also convert your existing public posts to private posts
|
||||||
|
|
||||||
## :warning: v20240217-1
|
## :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."
|
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"
|
private: "Private"
|
||||||
privateDescription: "Make visible for you only"
|
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:
|
_emojiModPerm:
|
||||||
unauthorized: "None"
|
unauthorized: "None"
|
||||||
|
|
|
@ -2028,3 +2028,5 @@ _emojiModPerm:
|
||||||
full: "全て許可"
|
full: "全て許可"
|
||||||
private: "秘密"
|
private: "秘密"
|
||||||
privateDescription: "あなた以外には非公開"
|
privateDescription: "あなた以外には非公開"
|
||||||
|
makePrivate: "秘密にする"
|
||||||
|
makePrivateConfirm: "リモートサーバーに削除リクエストを送信し、投稿の公開範囲を「秘密」にして他の人から見られないようにします。実行しますか?"
|
||||||
|
|
|
@ -2014,3 +2014,5 @@ preventMisclick: "預防誤觸"
|
||||||
hideFollowButtons: "隱藏會誤觸的追隨按鈕"
|
hideFollowButtons: "隱藏會誤觸的追隨按鈕"
|
||||||
private: "祕密"
|
private: "祕密"
|
||||||
privateDescription: "僅你可見"
|
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_hybridTimeline from "./endpoints/notes/hybrid-timeline.js";
|
||||||
import * as ep___notes_localTimeline from "./endpoints/notes/local-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_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_mentions from "./endpoints/notes/mentions.js";
|
||||||
import * as ep___notes_polls_recommendation from "./endpoints/notes/polls/recommendation.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";
|
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/hybrid-timeline", ep___notes_hybridTimeline],
|
||||||
["notes/local-timeline", ep___notes_localTimeline],
|
["notes/local-timeline", ep___notes_localTimeline],
|
||||||
["notes/recommended-timeline", ep___notes_recommendedTimeline],
|
["notes/recommended-timeline", ep___notes_recommendedTimeline],
|
||||||
|
["notes/make-private", ep___notes_makePrivate],
|
||||||
["notes/mentions", ep___notes_mentions],
|
["notes/mentions", ep___notes_mentions],
|
||||||
["notes/polls/recommendation", ep___notes_polls_recommendation],
|
["notes/polls/recommendation", ep___notes_polls_recommendation],
|
||||||
["notes/polls/vote", ep___notes_polls_vote],
|
["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"] },
|
user: { id: User["id"]; uri: User["uri"]; host: User["host"] },
|
||||||
note: Note,
|
note: Note,
|
||||||
quiet = false,
|
quiet = false,
|
||||||
|
deleteFromDb = true,
|
||||||
) {
|
) {
|
||||||
const deletedAt = new Date();
|
const deletedAt = new Date();
|
||||||
|
|
||||||
// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
|
// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
|
||||||
if (
|
if (
|
||||||
note.renoteId &&
|
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 }, "renoteCount", 1);
|
||||||
Notes.decrement({ id: note.renoteId }, "score", 1);
|
Notes.decrement({ id: note.renoteId }, "score", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.replyId) {
|
if (note.replyId && deleteFromDb) {
|
||||||
await Notes.decrement({ id: note.replyId }, "repliesCount", 1);
|
await Notes.decrement({ id: note.replyId }, "repliesCount", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!quiet) {
|
if (!quiet) {
|
||||||
publishNoteStream(note.id, "deleted", {
|
// Only broadcast "deleted" to local if the note is deleted from db
|
||||||
deletedAt: deletedAt,
|
if (deleteFromDb) {
|
||||||
});
|
publishNoteStream(note.id, "deleted", {
|
||||||
|
deletedAt: deletedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//#region ローカルの投稿なら削除アクティビティを配送
|
//#region ローカルの投稿なら削除アクティビティを配送
|
||||||
if (Users.isLocalUser(user) && !note.localOnly) {
|
if (Users.isLocalUser(user) && !note.localOnly) {
|
||||||
|
@ -116,10 +121,12 @@ export default async function (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Notes.delete({
|
if (deleteFromDb) {
|
||||||
id: note.id,
|
await Notes.delete({
|
||||||
userId: user.id,
|
id: note.id,
|
||||||
});
|
userId: user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (meilisearch) {
|
if (meilisearch) {
|
||||||
await meilisearch.deleteNotes(note.id);
|
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 {
|
function toggleFavorite(favorite: boolean): void {
|
||||||
os.apiWithDialog(
|
os.apiWithDialog(
|
||||||
favorite ? "notes/favorites/create" : "notes/favorites/delete",
|
favorite ? "notes/favorites/create" : "notes/favorites/delete",
|
||||||
|
@ -437,6 +450,18 @@ export function getNoteMenu(props: {
|
||||||
action: edit,
|
action: edit,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
isAppearAuthor &&
|
||||||
|
!(
|
||||||
|
appearNote.visibility === "specified" &&
|
||||||
|
appearNote.visibleUserIds.length === 0
|
||||||
|
)
|
||||||
|
? {
|
||||||
|
icon: `${icon("ph-eye-slash")}`,
|
||||||
|
text: i18n.ts.makePrivate,
|
||||||
|
danger: true,
|
||||||
|
action: makePrivate,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
isAppearAuthor
|
isAppearAuthor
|
||||||
? {
|
? {
|
||||||
icon: `${icon("ph-eraser")}`,
|
icon: `${icon("ph-eraser")}`,
|
||||||
|
|
|
@ -80,17 +80,22 @@ export function useNoteCapture(props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
case "updated": {
|
case "updated": {
|
||||||
const editedNote = await os.api("notes/show", {
|
try {
|
||||||
noteId: id,
|
const editedNote = await os.api("notes/show", {
|
||||||
});
|
noteId: id,
|
||||||
|
});
|
||||||
|
|
||||||
const keys = new Set<string>();
|
const keys = new Set<string>();
|
||||||
Object.keys(editedNote)
|
Object.keys(editedNote)
|
||||||
.concat(Object.keys(note.value))
|
.concat(Object.keys(note.value))
|
||||||
.forEach((key) => keys.add(key));
|
.forEach((key) => keys.add(key));
|
||||||
keys.forEach((key) => {
|
keys.forEach((key) => {
|
||||||
note.value[key] = editedNote[key];
|
note.value[key] = editedNote[key];
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
// delete the note if failing to get the edited note
|
||||||
|
props.isDeletedRef.value = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue