You've already forked AstralRinth
forked from didirus/AstralRinth
Add private mod notes and message deletion (#1494)
* Really simply private messages impl * Make private notes better * Add thread message deletion * Lint
This commit is contained in:
@@ -14,8 +14,8 @@
|
|||||||
<ConversationThread
|
<ConversationThread
|
||||||
:thread="thread"
|
:thread="thread"
|
||||||
:report="report"
|
:report="report"
|
||||||
:update-thread="updateThread"
|
|
||||||
:auth="auth"
|
:auth="auth"
|
||||||
|
@update-thread="updateThread"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,7 +44,9 @@
|
|||||||
:message="message"
|
:message="message"
|
||||||
:members="members"
|
:members="members"
|
||||||
:report="report"
|
:report="report"
|
||||||
|
:auth="auth"
|
||||||
raised
|
raised
|
||||||
|
@update-thread="() => updateThreadLocal()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="report && report.closed">
|
<span v-if="report && report.closed">
|
||||||
@@ -61,20 +63,23 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<button
|
<button
|
||||||
v-if="sortedMessages.length > 0"
|
v-if="sortedMessages.length > 0"
|
||||||
class="iconified-button brand-button"
|
class="btn btn-primary"
|
||||||
:disabled="!replyBody"
|
:disabled="!replyBody"
|
||||||
@click="sendReply()"
|
@click="sendReply()"
|
||||||
>
|
>
|
||||||
<ReplyIcon /> Reply
|
<ReplyIcon /> Reply
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button v-else class="btn btn-primary" :disabled="!replyBody" @click="sendReply()">
|
||||||
v-else
|
|
||||||
class="iconified-button brand-button"
|
|
||||||
:disabled="!replyBody"
|
|
||||||
@click="sendReply()"
|
|
||||||
>
|
|
||||||
<SendIcon /> Send
|
<SendIcon /> Send
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="isStaff(auth.user)"
|
||||||
|
class="btn"
|
||||||
|
:disabled="!replyBody"
|
||||||
|
@click="sendReply(null, true)"
|
||||||
|
>
|
||||||
|
<ModerationIcon /> Add private note
|
||||||
|
</button>
|
||||||
<template v-if="currentMember && !isStaff(auth.user)">
|
<template v-if="currentMember && !isStaff(auth.user)">
|
||||||
<template v-if="isRejected(project)">
|
<template v-if="isRejected(project)">
|
||||||
<button
|
<button
|
||||||
@@ -113,7 +118,7 @@
|
|||||||
<template v-if="isStaff(auth.user)">
|
<template v-if="isStaff(auth.user)">
|
||||||
<button
|
<button
|
||||||
v-if="replyBody"
|
v-if="replyBody"
|
||||||
class="iconified-button brand-button"
|
class="btn btn-green"
|
||||||
:disabled="isApproved(project)"
|
:disabled="isApproved(project)"
|
||||||
@click="sendReply(requestedStatus)"
|
@click="sendReply(requestedStatus)"
|
||||||
>
|
>
|
||||||
@@ -121,44 +126,64 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="iconified-button brand-button"
|
class="btn btn-green"
|
||||||
:disabled="isApproved(project)"
|
:disabled="isApproved(project)"
|
||||||
@click="setStatus(requestedStatus)"
|
@click="setStatus(requestedStatus)"
|
||||||
>
|
>
|
||||||
<CheckIcon /> Approve project
|
<CheckIcon /> Approve
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="replyBody"
|
|
||||||
class="iconified-button moderation-button"
|
|
||||||
:disabled="project.status === 'withheld'"
|
|
||||||
@click="sendReply('withheld')"
|
|
||||||
>
|
|
||||||
<EyeOffIcon /> Withhold with reply
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-else
|
|
||||||
class="iconified-button moderation-button"
|
|
||||||
:disabled="project.status === 'withheld'"
|
|
||||||
@click="setStatus('withheld')"
|
|
||||||
>
|
|
||||||
<EyeOffIcon /> Withhold project
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="replyBody"
|
|
||||||
class="iconified-button danger-button"
|
|
||||||
:disabled="project.status === 'rejected'"
|
|
||||||
@click="sendReply('rejected')"
|
|
||||||
>
|
|
||||||
<CrossIcon /> Reject with reply
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-else
|
|
||||||
class="iconified-button danger-button"
|
|
||||||
:disabled="project.status === 'rejected'"
|
|
||||||
@click="setStatus('rejected')"
|
|
||||||
>
|
|
||||||
<CrossIcon /> Reject project
|
|
||||||
</button>
|
</button>
|
||||||
|
<div class="joined-buttons">
|
||||||
|
<button
|
||||||
|
v-if="replyBody"
|
||||||
|
class="btn btn-danger"
|
||||||
|
:disabled="project.status === 'rejected'"
|
||||||
|
@click="sendReply('rejected')"
|
||||||
|
>
|
||||||
|
<CrossIcon /> Reject with reply
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="btn btn-danger"
|
||||||
|
:disabled="project.status === 'rejected'"
|
||||||
|
@click="setStatus('rejected')"
|
||||||
|
>
|
||||||
|
<CrossIcon /> Reject
|
||||||
|
</button>
|
||||||
|
<OverflowMenu
|
||||||
|
class="btn btn-danger btn-dropdown-animation icon-only"
|
||||||
|
position="top"
|
||||||
|
direction="left"
|
||||||
|
:options="
|
||||||
|
replyBody
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 'withhold-reply',
|
||||||
|
color: 'danger',
|
||||||
|
action: () => {
|
||||||
|
sendReply('withheld')
|
||||||
|
},
|
||||||
|
hoverFilled: true,
|
||||||
|
disabled: project.status === 'withheld',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
id: 'withhold',
|
||||||
|
color: 'danger',
|
||||||
|
action: () => {
|
||||||
|
setStatus('withheld')
|
||||||
|
},
|
||||||
|
hoverFilled: true,
|
||||||
|
disabled: project.status === 'withheld',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<DropdownIcon style="rotate: 180deg" />
|
||||||
|
<template #withhold-reply> <EyeOffIcon /> Withhold with reply </template>
|
||||||
|
<template #withhold> <EyeOffIcon /> Withhold </template>
|
||||||
|
</OverflowMenu>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,7 +193,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { MarkdownEditor } from 'omorphia'
|
import { OverflowMenu, MarkdownEditor, DropdownIcon } from 'omorphia'
|
||||||
import { useImageUpload } from '~/composables/image-upload.ts'
|
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||||
import CopyCode from '~/components/ui/CopyCode.vue'
|
import CopyCode from '~/components/ui/CopyCode.vue'
|
||||||
import ReplyIcon from '~/assets/images/utils/reply.svg'
|
import ReplyIcon from '~/assets/images/utils/reply.svg'
|
||||||
@@ -199,11 +224,6 @@ const props = defineProps({
|
|||||||
required: false,
|
required: false,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
updateThread: {
|
|
||||||
type: Function,
|
|
||||||
required: false,
|
|
||||||
default: () => {},
|
|
||||||
},
|
|
||||||
setStatus: {
|
setStatus: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -220,6 +240,9 @@ const props = defineProps({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update-thread'])
|
||||||
|
|
||||||
const app = useNuxtApp()
|
const app = useNuxtApp()
|
||||||
const cosmetics = useCosmetics()
|
const cosmetics = useCosmetics()
|
||||||
|
|
||||||
@@ -255,7 +278,7 @@ async function updateThreadLocal() {
|
|||||||
if (threadId) {
|
if (threadId) {
|
||||||
thread = await useBaseFetch(`thread/${threadId}`)
|
thread = await useBaseFetch(`thread/${threadId}`)
|
||||||
}
|
}
|
||||||
props.updateThread(thread)
|
emit('update-thread', thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageIDs = ref([])
|
const imageIDs = ref([])
|
||||||
@@ -270,12 +293,13 @@ async function onUploadImage(file) {
|
|||||||
return response.url
|
return response.url
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendReply(status = null) {
|
async function sendReply(status = null, privateMessage = false) {
|
||||||
try {
|
try {
|
||||||
const body = {
|
const body = {
|
||||||
body: {
|
body: {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
body: replyBody.value,
|
body: replyBody.value,
|
||||||
|
private: privateMessage,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
/>
|
/>
|
||||||
</ConditionalNuxtLink>
|
</ConditionalNuxtLink>
|
||||||
<span :class="`message__author role-${members[message.author_id].role}`">
|
<span :class="`message__author role-${members[message.author_id].role}`">
|
||||||
<PrivateIcon
|
<LockIcon
|
||||||
v-if="message.body.private"
|
v-if="message.body.private"
|
||||||
v-tooltip="'Only visible by moderators'"
|
v-tooltip="'Only visible to moderators'"
|
||||||
class="private-icon"
|
class="private-icon"
|
||||||
/>
|
/>
|
||||||
<ConditionalNuxtLink
|
<ConditionalNuxtLink
|
||||||
@@ -34,15 +34,12 @@
|
|||||||
>
|
>
|
||||||
{{ members[message.author_id].username }}
|
{{ members[message.author_id].username }}
|
||||||
</ConditionalNuxtLink>
|
</ConditionalNuxtLink>
|
||||||
<ModeratorIcon
|
<ScaleIcon v-if="members[message.author_id].role === 'moderator'" v-tooltip="'Moderator'" />
|
||||||
v-if="members[message.author_id].role === 'moderator'"
|
|
||||||
v-tooltip="'Moderator'"
|
|
||||||
/>
|
|
||||||
<ModrinthIcon
|
<ModrinthIcon
|
||||||
v-else-if="members[message.author_id].role === 'admin'"
|
v-else-if="members[message.author_id].role === 'admin'"
|
||||||
v-tooltip="'Modrinth Team'"
|
v-tooltip="'Modrinth Team'"
|
||||||
/>
|
/>
|
||||||
<MicIcon
|
<MicrophoneIcon
|
||||||
v-if="report && message.author_id === report.reporterUser.id"
|
v-if="report && message.author_id === report.reporterUser.id"
|
||||||
v-tooltip="'Reporter'"
|
v-tooltip="'Reporter'"
|
||||||
class="reporter-icon"
|
class="reporter-icon"
|
||||||
@@ -51,11 +48,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="message__icon backed-svg circle moderation-color" :class="{ raised: raised }">
|
<div class="message__icon backed-svg circle moderation-color" :class="{ raised: raised }">
|
||||||
<ModeratorIcon />
|
<ScaleIcon />
|
||||||
</div>
|
</div>
|
||||||
<span class="message__author moderation-color">
|
<span class="message__author moderation-color">
|
||||||
Moderator
|
Moderator
|
||||||
<ModeratorIcon v-tooltip="'Moderator'" />
|
<ScaleIcon v-tooltip="'Moderator'" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
@@ -81,21 +78,40 @@
|
|||||||
{{ timeSincePosted }}
|
{{ timeSincePosted }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<!-- <div class="message__actions">-->
|
<div v-if="isStaff(auth.user) && message.author_id === auth.user.id" class="message__actions">
|
||||||
<!-- <Button icon-only><MoreIcon /></Button>-->
|
<OverflowMenu
|
||||||
<!-- </div>-->
|
class="btn btn-transparent icon-only"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
id: 'delete',
|
||||||
|
action: () => deleteMessage(),
|
||||||
|
color: 'red',
|
||||||
|
hoverFilled: true,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<MoreHorizontalIcon />
|
||||||
|
<template #delete> <TrashIcon /> Delete </template>
|
||||||
|
</OverflowMenu>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import {
|
||||||
|
OverflowMenu,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
TrashIcon,
|
||||||
|
ConditionalNuxtLink,
|
||||||
|
MicrophoneIcon,
|
||||||
|
LockIcon,
|
||||||
|
ModrinthIcon,
|
||||||
|
ScaleIcon,
|
||||||
|
} from 'omorphia'
|
||||||
import Avatar from '~/components/ui/Avatar.vue'
|
import Avatar from '~/components/ui/Avatar.vue'
|
||||||
import Badge from '~/components/ui/Badge.vue'
|
import Badge from '~/components/ui/Badge.vue'
|
||||||
import ModeratorIcon from '~/assets/images/sidebar/admin.svg'
|
|
||||||
import ModrinthIcon from '~/assets/images/utils/modrinth.svg'
|
|
||||||
import MicIcon from '~/assets/images/utils/mic.svg'
|
|
||||||
import PrivateIcon from '~/assets/images/utils/lock.svg'
|
|
||||||
import { renderString } from '~/helpers/parse.js'
|
import { renderString } from '~/helpers/parse.js'
|
||||||
import ConditionalNuxtLink from '~/components/ui/ConditionalNuxtLink.vue'
|
import { isStaff } from '~/helpers/users.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
message: {
|
message: {
|
||||||
@@ -122,8 +138,14 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
auth: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update-thread'])
|
||||||
|
|
||||||
const formattedMessage = computed(() => {
|
const formattedMessage = computed(() => {
|
||||||
const body = renderString(props.message.body.body)
|
const body = renderString(props.message.body.body)
|
||||||
if (props.forceCompact) {
|
if (props.forceCompact) {
|
||||||
@@ -142,6 +164,13 @@ const formattedMessage = computed(() => {
|
|||||||
|
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
const timeSincePosted = ref(formatRelativeTime(props.message.created))
|
const timeSincePosted = ref(formatRelativeTime(props.message.created))
|
||||||
|
|
||||||
|
async function deleteMessage() {
|
||||||
|
await useBaseFetch(`message/${props.message.id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
emit('update-thread')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -222,7 +251,10 @@ const timeSincePosted = ref(formatRelativeTime(props.message.created))
|
|||||||
.message__actions {
|
.message__actions {
|
||||||
grid-area: actions;
|
grid-area: actions;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
opacity: 0;
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message__body {
|
.message__body {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
:message="message"
|
:message="message"
|
||||||
:report="report"
|
:report="report"
|
||||||
:members="members"
|
:members="members"
|
||||||
|
:auth="auth"
|
||||||
force-compact
|
force-compact
|
||||||
no-links
|
no-links
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -70,11 +70,11 @@
|
|||||||
<ConversationThread
|
<ConversationThread
|
||||||
v-if="thread"
|
v-if="thread"
|
||||||
:thread="thread"
|
:thread="thread"
|
||||||
:update-thread="(newThread) => (thread = newThread)"
|
|
||||||
:project="project"
|
:project="project"
|
||||||
:set-status="setStatus"
|
:set-status="setStatus"
|
||||||
:current-member="currentMember"
|
:current-member="currentMember"
|
||||||
:auth="auth"
|
:auth="auth"
|
||||||
|
@update-thread="(newThread) => (thread = newThread)"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user