summaryrefslogtreecommitdiff
path: root/packages/frontend
diff options
context:
space:
mode:
authorHazelnoot <acomputerdog@gmail.com>2025-03-02 18:56:44 +0000
committerHazelnoot <acomputerdog@gmail.com>2025-03-02 18:56:44 +0000
commit141bce2be7a5b8756d58e2d4e92d1f6180629da4 (patch)
treec73d3940938e4fd8cc515377f9334c4a48679c7b /packages/frontend
parentmerge: Remove assertActivityMatchesUrls in favor of three-way same-authority ... (diff)
parentremove `fileId` from `importCustomEmojis` log (diff)
downloadsharkey-141bce2be7a5b8756d58e2d4e92d1f6180629da4.tar.gz
sharkey-141bce2be7a5b8756d58e2d4e92d1f6180629da4.tar.bz2
sharkey-141bce2be7a5b8756d58e2d4e92d1f6180629da4.zip
merge: Add/fix moderation logs for many endpoints (resolves #911 and #969) (!925)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/925 Closes #911 and #969 Approved-by: dakkar <dakkar@thenautilus.net> Approved-by: Marie <github@yuugi.dev>
Diffstat (limited to 'packages/frontend')
-rw-r--r--packages/frontend/src/components/DynamicNote.vue49
-rw-r--r--packages/frontend/src/components/SkFetchNote.vue69
-rw-r--r--packages/frontend/src/components/global/MkLazy.vue10
-rw-r--r--packages/frontend/src/pages/admin/modlog.ModLog.vue70
4 files changed, 198 insertions, 0 deletions
diff --git a/packages/frontend/src/components/DynamicNote.vue b/packages/frontend/src/components/DynamicNote.vue
new file mode 100644
index 0000000000..6703099591
--- /dev/null
+++ b/packages/frontend/src/components/DynamicNote.vue
@@ -0,0 +1,49 @@
+<!--
+SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<XNote
+ ref="rootEl"
+ :note="note"
+ :pinned="pinned"
+ :mock="mock"
+ :withHardMute="withHardMute"
+ @reaction="emoji => emit('reaction', emoji)"
+ @removeReaction="emoji => emit('removeReaction', emoji)"
+/>
+</template>
+
+<script setup lang="ts">
+import * as Misskey from 'misskey-js';
+import { computed, defineAsyncComponent, shallowRef } from 'vue';
+import type { ComponentExposed } from 'vue-component-type-helpers';
+import type MkNote from '@/components/MkNote.vue';
+import type SkNote from '@/components/SkNote.vue';
+import { defaultStore } from '@/store';
+
+const XNote = computed(() =>
+ defineAsyncComponent(() =>
+ defaultStore.reactiveState.noteDesign.value === 'misskey'
+ ? import('@/components/MkNote.vue')
+ : import('@/components/SkNote.vue'),
+ ),
+);
+
+const rootEl = shallowRef<ComponentExposed<typeof MkNote | typeof SkNote>>();
+
+defineExpose({ rootEl });
+
+defineProps<{
+ note: Misskey.entities.Note;
+ pinned?: boolean;
+ mock?: boolean;
+ withHardMute?: boolean;
+}>();
+
+const emit = defineEmits<{
+ (ev: 'reaction', emoji: string): void;
+ (ev: 'removeReaction', emoji: string): void;
+}>();
+</script>
diff --git a/packages/frontend/src/components/SkFetchNote.vue b/packages/frontend/src/components/SkFetchNote.vue
new file mode 100644
index 0000000000..ab702c28f8
--- /dev/null
+++ b/packages/frontend/src/components/SkFetchNote.vue
@@ -0,0 +1,69 @@
+<!--
+SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkLazy @show="showing = true">
+ <MkLoading v-if="state === 'loading'"/>
+
+ <div v-if="state === 'error'">{{ i18n.ts.cannotLoadNote }}</div>
+
+ <DynamicNote v-if="state === 'done' && note" :note="note"/>
+</MkLazy>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+import * as Misskey from 'misskey-js';
+import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/scripts/misskey-api';
+import DynamicNote from '@/components/DynamicNote.vue';
+
+const props = withDefaults(defineProps<{
+ noteId: string,
+ lazy?: boolean,
+}>(), {
+ lazy: true,
+});
+
+// Lazy-load, unless props.lazy is false.
+// eslint-disable-next-line vue/no-setup-props-reactivity-loss
+const showing = ref(!props.lazy);
+const state = ref<'loading' | 'error' | 'done'>('loading');
+const note = ref<Misskey.entities.Note | null>(null);
+
+watch(
+ [
+ () => props.noteId,
+ () => showing.value,
+ ],
+ async ([noteId, show]) => {
+ // Wait until the note is visible to avoid bombarding the API with requests.
+ if (!show) return;
+
+ // Unload the old note
+ note.value = null;
+ state.value = 'loading';
+
+ // Fetch the new note
+ const newNote = await misskeyApi('notes/show', { noteId }).catch(() => null);
+
+ // Check for race conditions (ex. the note changed again while the first request was still running)
+ if (noteId !== props.noteId) return;
+
+ // Check for errors
+ if (!newNote) {
+ state.value = 'error';
+ return;
+ }
+
+ // Display the new note
+ note.value = newNote;
+ state.value = 'done';
+ },
+ {
+ immediate: true,
+ },
+);
+</script>
diff --git a/packages/frontend/src/components/global/MkLazy.vue b/packages/frontend/src/components/global/MkLazy.vue
index f35932ae77..29908f303d 100644
--- a/packages/frontend/src/components/global/MkLazy.vue
+++ b/packages/frontend/src/components/global/MkLazy.vue
@@ -16,10 +16,20 @@ import { nextTick, onMounted, onActivated, onBeforeUnmount, ref, shallowRef } fr
const rootEl = shallowRef<HTMLDivElement>();
const showing = ref(false);
+const emit = defineEmits<{
+ (ev: 'show'): void,
+}>();
+
const observer = new IntersectionObserver(
(entries) => {
if (entries.some((entry) => entry.isIntersecting)) {
showing.value = true;
+
+ // Disconnect to avoid observer soft-leaks
+ observer.disconnect();
+
+ // Notify containing element to trigger edge logic
+ emit('show');
}
},
);
diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue
index 306a873173..9ce6499e2d 100644
--- a/packages/frontend/src/pages/admin/modlog.ModLog.vue
+++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue
@@ -18,6 +18,10 @@ SPDX-License-Identifier: AGPL-3.0-only
'createAvatarDecoration',
'createSystemWebhook',
'createAbuseReportNotificationRecipient',
+ 'createAccount',
+ 'importCustomEmojis',
+ 'createPromo',
+ 'addRelay',
].includes(log.type),
[$style.logYellow]: [
'markSensitiveDriveFile',
@@ -30,6 +34,11 @@ SPDX-License-Identifier: AGPL-3.0-only
'acceptRemoteInstanceReports',
'rejectQuotesUser',
'acceptQuotesUser',
+ 'nsfwUser',
+ 'unNsfwUser',
+ 'silenceUser',
+ 'unSilenceUser',
+ 'updateCustomEmojis',
].includes(log.type),
[$style.logRed]: [
'suspend',
@@ -49,6 +58,12 @@ SPDX-License-Identifier: AGPL-3.0-only
'deletePage',
'deleteFlash',
'deleteGalleryPost',
+ 'clearUserFiles',
+ 'clearRemoteFiles',
+ 'clearOwnerlessFiles',
+ 'clearInstanceFiles',
+ 'severFollowRelations',
+ 'removeRelay',
].includes(log.type)
}"
>{{ i18n.ts._moderationLogTypes[log.type] }}</b>
@@ -100,6 +115,19 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="log.type === 'deletePage'">: @{{ log.info.pageUserUsername }}</span>
<span v-else-if="log.type === 'deleteFlash'">: @{{ log.info.flashUserUsername }}</span>
<span v-else-if="log.type === 'deleteGalleryPost'">: @{{ log.info.postUserUsername }}</span>
+ <span v-else-if="log.type === 'clearUserFiles'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+ <span v-else-if="log.type === 'nsfwUser'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+ <span v-else-if="log.type === 'unNsfwUser'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+ <span v-else-if="log.type === 'silenceUser'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+ <span v-else-if="log.type === 'unSilenceUser'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+ <span v-else-if="log.type === 'createAccount'">: @{{ log.info.userUsername }}</span>
+ <span v-else-if="log.type === 'clearOwnerlessFiles'">: {{ log.info.count }}</span>
+ <span v-else-if="log.type === 'importCustomEmojis'">: {{ log.info.fileName }}</span>
+ <span v-else-if="log.type === 'clearInstanceFiles'">: {{ log.info.host }}</span>
+ <span v-else-if="log.type === 'severFollowRelations'">: {{ log.info.host }}</span>
+ <span v-else-if="log.type === 'createPromo'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
+ <span v-else-if="log.type === 'addRelay'">: {{ log.info.inbox }}</span>
+ <span v-else-if="log.type === 'removeRelay'">: {{ log.info.inbox }}</span>
</template>
<template v-if="log.user" #icon>
<MkAvatar :user="log.user" :class="$style.avatar"/>
@@ -205,6 +233,47 @@ SPDX-License-Identifier: AGPL-3.0-only
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
</div>
</template>
+ <template v-else-if="log.type === 'clearUserFiles'">
+ <div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
+ <div>{{ i18n.ts.filesRemoved }}: {{ log.info.count }}</div>
+ </template>
+ <template v-else-if="log.type === 'nsfwUser'">
+ <div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
+ </template>
+ <template v-else-if="log.type === 'unNsfwUser'">
+ <div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
+ </template>
+ <template v-else-if="log.type === 'silenceUser'">
+ <div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
+ </template>
+ <template v-else-if="log.type === 'unSilenceUser'">
+ <div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
+ </template>
+ <template v-else-if="log.type === 'createAccount'">
+ <div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}</MkA></div>
+ </template>
+ <template v-else-if="log.type === 'clearOwnerlessFiles'">
+ <div>{{ i18n.ts.filesRemoved }}: {{ log.info.count }}</div>
+ </template>
+ <template v-else-if="log.type === 'importCustomEmojis'">
+ <div>{{ i18n.ts.fileImported }}: {{ log.info.fileName }}</div>
+ </template>
+ <template v-else-if="log.type === 'clearInstanceFiles'">
+ <div>{{ i18n.ts.host }}: <MkA :to="`/instance-info/${log.info.host}`" class="_link">{{ log.info.host }}</MkA></div>
+ <div>{{ i18n.ts.filesRemoved }}: {{ log.info.count }}</div>
+ </template>
+ <template v-else-if="log.type === 'severFollowRelations'">
+ <div>{{ i18n.ts.host }}: <MkA :to="`/instance-info/${log.info.host}`" class="_link">{{ log.info.host }}</MkA></div>
+ </template>
+ <template v-else-if="log.type === 'createPromo'">
+ <SkFetchNote :noteId="log.info.noteId"/>
+ </template>
+ <template v-else-if="log.type === 'addRelay'">
+ <div>{{ i18n.ts.inboxUrl }}: {{ log.info.inbox }}</div>
+ </template>
+ <template v-else-if="log.type === 'removeRelay'">
+ <div>{{ i18n.ts.inboxUrl }}: {{ log.info.inbox }}</div>
+ </template>
<details>
<summary>raw</summary>
@@ -220,6 +289,7 @@ import { CodeDiff } from 'v-code-diff';
import JSON5 from 'json5';
import { i18n } from '@/i18n.js';
import MkFolder from '@/components/MkFolder.vue';
+import SkFetchNote from '@/components/SkFetchNote.vue';
const props = defineProps<{
log: Misskey.entities.ModerationLog;