Implement Editor MOD-349 (#1427)

* Implement Editor

* content oveflow fix for description

* Description card fix

* make everything fix in report modal

* seperate report page with image upload

* Bump Omorphia

* Update pages/report.vue

Co-authored-by: Emma Alexia <emma@modrinth.com>

* suggested changes and cleanup

* fix button spacing

* clean up and replace report implementations

* corepack fix

* Remove ModalReport

* image uploads for conversations

* image uploading context for versions and threads

* adjust information about thread messages

* Update pages/report.vue

Co-authored-by: Emma Alexia <emma@modrinth.com>

* Adjust image upload imports

* fix api changes for useImageUpload

* correct report redirection uri

* report button feedback

* omorphia ver bump

---------

Co-authored-by: Emma Alexia <emma@modrinth.com>
This commit is contained in:
Carter
2023-11-29 10:56:17 -08:00
committed by GitHub
parent e4cb8b71dd
commit accc53c5dd
14 changed files with 458 additions and 272 deletions

View File

@@ -221,8 +221,6 @@
:where(input) {
box-sizing: border-box;
max-height: 40px;
width: 24rem;
flex-basis: 24rem;
&:not(.stylized-toggle) {
max-width: 100%;

View File

@@ -96,5 +96,6 @@
.normal-page__content {
max-width: calc(60rem - 0.75rem);
overflow-x: hidden;
}
}

View File

@@ -1,175 +0,0 @@
<template>
<Modal ref="modal" :header="`Report ${itemType}`">
<div class="modal-report universal-labels">
<div class="markdown-body">
<p>
Modding should be safe for everyone, so we take abuse and malicious intent seriously at
Modrinth. We want to hear about harmful content on the site that violates our
<nuxt-link class="text-link" to="/legal/terms">ToS</nuxt-link> and
<nuxt-link class="text-link" to="/legal/rules">Rules</nuxt-link>. Rest assured, well keep
your identifying information private.
</p>
<p v-if="itemType === 'project' || itemType === 'version'">
Please <strong>do not</strong> use this to report bugs with the project itself. This form
is only for submitting a report to Modrinth staff. If the project has an Issues link or a
Discord invite, consider reporting it there.
</p>
</div>
<label for="report-type">
<span class="label__title">Reason</span>
</label>
<Multiselect
id="report-type"
v-model="reportType"
:options="tags.reportTypes"
:custom-label="(value) => value.charAt(0).toUpperCase() + value.slice(1)"
:multiple="false"
:searchable="false"
:show-no-results="false"
:show-labels="false"
placeholder="Choose report type"
/>
<label for="report-body">
<span class="label__title">Additional information</span>
<span class="label__description add-line-height">
Please provide additional context about your report. Include links and images if possible.
<strong>Empty reports will be closed.</strong> This editor supports
<a
class="text-link"
href="https://docs.modrinth.com/docs/tutorials/markdown/"
target="_blank"
>Markdown formatting</a
>.
</span>
</label>
<div class="textarea-wrapper">
<Chips v-model="bodyViewType" class="separator" :items="['source', 'preview']" />
<div v-if="bodyViewType === 'source'" class="textarea-wrapper">
<textarea id="body" v-model="body" spellcheck="true" />
</div>
<div v-else class="preview" v-html="renderString(body)" />
</div>
<div class="button-group">
<button class="iconified-button" @click="cancel">
<CrossIcon />
Cancel
</button>
<button class="iconified-button brand-button" @click="submitReport">
<CheckIcon />
Report
</button>
</div>
</div>
</Modal>
</template>
<script>
import { Multiselect } from 'vue-multiselect'
import CrossIcon from '~/assets/images/utils/x.svg'
import CheckIcon from '~/assets/images/utils/check.svg'
import Modal from '~/components/ui/Modal.vue'
import Chips from '~/components/ui/Chips.vue'
import { renderString } from '~/helpers/parse.js'
export default {
components: {
Chips,
CrossIcon,
CheckIcon,
Modal,
Multiselect,
},
props: {
itemType: {
type: String,
default: '',
},
itemId: {
type: String,
default: '',
},
},
setup() {
const tags = useTags()
return { tags }
},
data() {
return {
reportType: '',
body: '',
bodyViewType: 'source',
}
},
methods: {
renderString,
cancel() {
this.reportType = ''
this.body = ''
this.bodyViewType = 'source'
this.$refs.modal.hide()
},
async submitReport() {
startLoading()
try {
const data = {
report_type: this.reportType,
item_id: this.itemId,
item_type: this.itemType,
body: this.body,
}
await useBaseFetch('report', {
method: 'POST',
body: data,
})
this.$refs.modal.hide()
await this.$router.push('/dashboard/reports')
} catch (err) {
this.$notify({
group: 'main',
title: 'An error occurred',
text: err.data.description,
type: 'error',
})
}
stopLoading()
},
show() {
this.$refs.modal.show()
},
},
}
</script>
<style scoped lang="scss">
.modal-report {
padding: var(--spacing-card-bg);
display: flex;
flex-direction: column;
.add-line-height {
line-height: 1.5;
margin-bottom: 0;
}
.multiselect {
max-width: 20rem;
}
.textarea-wrapper {
margin-top: 1rem;
height: 12rem;
textarea {
// here due to a bug in safari
max-height: 9rem;
}
.preview {
overflow-y: auto;
}
}
}
</style>

View File

@@ -51,14 +51,12 @@
This thread is closed and new messages cannot be sent to it.
</span>
<template v-else-if="!report || !report.closed">
<div class="resizable-textarea-wrapper">
<Chips v-model="replyViewMode" class="chips" :items="['source', 'preview']" />
<textarea
v-if="replyViewMode === 'source'"
<div class="markdown-editor-spacing">
<MarkdownEditor
v-model="replyBody"
:placeholder="sortedMessages.length > 0 ? 'Reply to thread...' : 'Send a message...'"
:on-image-upload="onUploadImage"
/>
<div v-else class="markdown-body preview" v-html="renderString(replyBody)" />
</div>
<div class="input-group">
<button
@@ -170,7 +168,8 @@
</template>
<script setup>
import Chips from '~/components/ui/Chips.vue'
import { MarkdownEditor } from 'omorphia'
import { useImageUpload } from '~/composables/image-upload.ts'
import CopyCode from '~/components/ui/CopyCode.vue'
import ReplyIcon from '~/assets/images/utils/reply.svg'
import SendIcon from '~/assets/images/utils/send.svg'
@@ -179,7 +178,6 @@ import CrossIcon from '~/assets/images/utils/x.svg'
import EyeOffIcon from '~/assets/images/utils/eye-off.svg'
import CheckIcon from '~/assets/images/utils/check.svg'
import ModerationIcon from '~/assets/images/sidebar/admin.svg'
import { renderString } from '~/helpers/parse.js'
import ThreadMessage from '~/components/ui/thread/ThreadMessage.vue'
import { isStaff } from '~/helpers/users.js'
import { isApproved, isRejected } from '~/helpers/projects.js'
@@ -233,7 +231,6 @@ const members = computed(() => {
return members
})
const replyViewMode = ref('source')
const replyBody = ref('')
const sortedMessages = computed(() => {
@@ -261,18 +258,41 @@ async function updateThreadLocal() {
props.updateThread(thread)
}
const imageIDs = ref([])
async function onUploadImage(file) {
const response = await useImageUpload(file, { context: 'thread_message' })
imageIDs.value.push(response.id)
// Keep the last 10 entries of image IDs
imageIDs.value = imageIDs.value.slice(-10)
return response.url
}
async function sendReply(status = null) {
try {
const body = {
body: {
type: 'text',
body: replyBody.value,
},
}
if (imageIDs.value.length > 0) {
body.body = {
...body.body,
uploaded_images: imageIDs.value,
}
}
await useBaseFetch(`thread/${props.thread.id}`, {
method: 'POST',
body: {
body: {
type: 'text',
body: replyBody.value,
},
},
body,
})
replyBody.value = ''
await updateThreadLocal()
if (status !== null) {
props.setStatus(status)
@@ -332,6 +352,10 @@ const requestedStatus = computed(() => props.project.requested_status ?? 'approv
</script>
<style lang="scss" scoped>
.markdown-editor-spacing {
margin-bottom: var(--gap-md);
}
.messages {
display: flex;
flex-direction: column;

View File

@@ -0,0 +1,45 @@
type ImageUploadContext = {
projectID?: string
context: 'project' | 'version' | 'thread_message' | 'report'
}
interface ImageUploadResponse {
id: string
url: string
}
export const useImageUpload = async (file: File, ctx: ImageUploadContext) => {
// Make sure file is of type image/png, image/jpeg, image/gif, or image/webp
if (
!file.type.startsWith('image/') ||
!['png', 'jpeg', 'gif', 'webp'].includes(file.type.split('/')[1])
) {
throw new Error('File is not an accepted image type')
}
// Make sure file is less than 1MB
if (file.size > 1024 * 1024) {
throw new Error('File is too large')
}
const qs = new URLSearchParams()
if (ctx.projectID) qs.set('project_id', ctx.projectID)
qs.set('context', ctx.context)
qs.set('ext', file.type.split('/')[1])
const url = `image?${qs.toString()}`
const response = (await useBaseFetch(url, {
method: 'POST',
body: file,
})) as ImageUploadResponse // TODO: zod or object validation
// Type check to see if response has a url property and an id property
if (!response?.id || typeof response.id !== 'string') {
throw new Error('Unexpected response from server')
}
if (!response?.url || typeof response.url !== 'string') {
throw new Error('Unexpected response from server')
}
return response
}

View File

@@ -46,7 +46,7 @@
"js-yaml": "^4.1.0",
"jszip": "^3.10.1",
"markdown-it": "^13.0.1",
"omorphia": "^0.6.7",
"omorphia": "^0.7.1",
"qrcode.vue": "^3.4.0",
"semver": "^7.5.4",
"vue-multiselect": "^3.0.0-alpha.2",

View File

@@ -130,12 +130,6 @@
<div class="markdown-body" v-html="renderString(licenseText)" />
</div>
</Modal>
<ModalReport
v-if="auth.user"
ref="modal_project_report"
:item-id="project.id"
item-type="project"
/>
<div
:class="{
'normal-page': true,
@@ -258,7 +252,7 @@
<hr class="card-divider" />
<div class="input-group">
<template v-if="auth.user">
<button class="iconified-button" @click="$refs.modal_project_report.show()">
<button class="iconified-button" @click="() => reportProject(project.id)">
<ReportIcon aria-hidden="true" />
Report
</button>
@@ -689,7 +683,6 @@ import Badge from '~/components/ui/Badge.vue'
import Categories from '~/components/ui/search/Categories.vue'
import EnvironmentIndicator from '~/components/ui/EnvironmentIndicator.vue'
import Modal from '~/components/ui/Modal.vue'
import ModalReport from '~/components/ui/ModalReport.vue'
import NavRow from '~/components/ui/NavRow.vue'
import CopyCode from '~/components/ui/CopyCode.vue'
import Avatar from '~/components/ui/Avatar.vue'
@@ -706,6 +699,7 @@ import LicenseIcon from '~/assets/images/utils/copyright.svg'
import GalleryIcon from '~/assets/images/utils/image.svg'
import VersionIcon from '~/assets/images/utils/version.svg'
import { renderString } from '~/helpers/parse.js'
import { reportProject } from '~/utils/report-helpers.ts'
import Breadcrumbs from '~/components/ui/Breadcrumbs.vue'
const data = useNuxtApp()

View File

@@ -63,9 +63,9 @@
<section id="messages" class="universal-card">
<h2>Messages</h2>
<p>
This is a private conversation thread with the Modrinth moderators. They will message you
for issues concerning your project on Modrinth, and you are welcome to message them about
things concerning your project.
This is a private conversation thread with the Modrinth moderators. They may message you
with issues concerning this project. Additionally, you are welcome to start a discussion
here regarding this project and its status.
</p>
<ConversationThread
v-if="thread"

View File

@@ -1,17 +1,10 @@
<template>
<div>
<section class="universal-card">
<label for="project-description">
<span class="label__title size-card-header">Description</span>
<Card>
<div class="markdown-disclaimer">
<h2>Description</h2>
<span class="label__description">
You can type an extended description of your mod here. This editor supports
<a
class="text-link"
href="https://docs.modrinth.com/docs/tutorials/markdown/"
target="_blank"
>Markdown formatting</a
>. HTML can also be used inside your description, not including styles, scripts, and
iframes (though YouTube iframes are allowed).
You can type an extended description of your mod here.
<span class="label__subdescription">
The description must clearly and honestly describe the purpose and function of the
project. See section 2.1 of the
@@ -19,21 +12,13 @@
for the full requirements.
</span>
</span>
</label>
<Chips v-model="bodyViewMode" :items="['source', 'preview']" />
<div v-if="bodyViewMode === 'source'" class="resizable-textarea-wrapper">
<textarea
id="project-description"
v-model="description"
:disabled="(currentMember.permissions & EDIT_BODY) !== EDIT_BODY"
/>
</div>
<div
v-else-if="bodyViewMode === 'preview'"
class="markdown-body"
v-html="description ? renderHighlightedString(description) : 'No body specified.'"
<MarkdownEditor
v-model="description"
:on-image-upload="onUploadHandler"
:disabled="(currentMember.permissions & EDIT_BODY) !== EDIT_BODY"
/>
<div class="input-group">
<div class="input-group markdown-disclaimer">
<button
type="button"
class="iconified-button brand-button"
@@ -44,19 +29,23 @@
Save changes
</button>
</div>
</section>
</Card>
</div>
</template>
<script>
import { MarkdownEditor, Card } from 'omorphia'
import Chips from '~/components/ui/Chips.vue'
import SaveIcon from '~/assets/images/utils/save.svg'
import { renderHighlightedString } from '~/helpers/highlight.js'
import { useImageUpload } from '~/composables/image-upload.ts'
export default defineNuxtComponent({
components: {
Card,
Chips,
SaveIcon,
MarkdownEditor,
},
props: {
project: {
@@ -121,15 +110,23 @@ export default defineNuxtComponent({
this.patchProject(this.patchData)
}
},
async onUploadHandler(file) {
const response = await useImageUpload(file, {
context: 'project',
projectID: this.project.id,
})
return response.url
},
},
})
</script>
<style lang="scss" scoped>
.resizable-textarea-wrapper textarea {
min-height: 40rem;
<style scoped>
.markdown-disclaimer {
margin-block: 1rem;
}
.markdown-body {
margin-bottom: var(--spacing-card-md);
.universal-card {
margin-top: 1rem;
}
</style>

View File

@@ -9,12 +9,6 @@
proceed-label="Delete"
@proceed="deleteVersion()"
/>
<ModalReport
v-if="auth.user"
ref="modal_version_report"
:item-id="version.id"
item-type="version"
/>
<Modal v-if="auth.user && currentMember" ref="modal_package_mod" header="Package data pack">
<div class="modal-package-mod universal-labels">
<div class="markdown-body">
@@ -157,7 +151,7 @@
<ReportIcon aria-hidden="true" />
Report
</nuxt-link>
<button v-else class="iconified-button" @click="$refs.modal_version_report.show()">
<button v-else class="iconified-button" @click="() => reportVersion(version.id)">
<ReportIcon aria-hidden="true" />
Report
</button>
@@ -195,29 +189,9 @@
<div class="version-page__changelog universal-card">
<h3>Changelog</h3>
<template v-if="isEditing">
<span
>This editor supports
<a
class="text-link"
href="https://docs.modrinth.com/docs/tutorials/markdown/"
target="_blank"
>Markdown formatting</a
>. HTML can also be used inside your changelog, not including styles, scripts, and
iframes.
</span>
<Chips v-model="changelogViewMode" class="separator" :items="['source', 'preview']" />
<div v-if="changelogViewMode === 'source'" class="resizable-textarea-wrapper">
<textarea id="body" v-model="version.changelog" maxlength="65536" />
<div class="changelog-editor-spacing">
<MarkdownEditor v-model="version.changelog" :on-image-upload="onImageUpload" />
</div>
<div
v-if="changelogViewMode === 'preview'"
class="markdown-body"
v-html="
version.changelog
? renderHighlightedString(version.changelog)
: 'No changelog specified.'
"
/>
</template>
<div
v-else
@@ -656,11 +630,14 @@
</div>
</template>
<script>
import { MarkdownEditor } from 'omorphia'
import { Multiselect } from 'vue-multiselect'
import { acceptFileFromProjectType } from '~/helpers/fileUtils.js'
import { inferVersionInfo } from '~/helpers/infer.js'
import { createDataPackVersion } from '~/helpers/package.js'
import { renderHighlightedString } from '~/helpers/highlight.js'
import { reportVersion } from '~/utils/report-helpers.ts'
import { useImageUpload } from '~/composables/image-upload.ts'
import Avatar from '~/components/ui/Avatar.vue'
import Badge from '~/components/ui/Badge.vue'
@@ -668,7 +645,6 @@ import Breadcrumbs from '~/components/ui/Breadcrumbs.vue'
import CopyCode from '~/components/ui/CopyCode.vue'
import Categories from '~/components/ui/search/Categories.vue'
import ModalConfirm from '~/components/ui/ModalConfirm.vue'
import ModalReport from '~/components/ui/ModalReport.vue'
import Chips from '~/components/ui/Chips.vue'
import Checkbox from '~/components/ui/Checkbox.vue'
import FileInput from '~/components/ui/FileInput.vue'
@@ -693,6 +669,7 @@ import ChevronRightIcon from '~/assets/images/utils/chevron-right.svg'
export default defineNuxtComponent({
components: {
MarkdownEditor,
Modal,
FileInput,
Checkbox,
@@ -717,7 +694,6 @@ export default defineNuxtComponent({
Breadcrumbs,
CopyCode,
ModalConfirm,
ModalReport,
Multiselect,
BoxIcon,
RightArrowIcon,
@@ -919,6 +895,7 @@ export default defineNuxtComponent({
primaryFile: ref(primaryFile),
alternateFile: ref(alternateFile),
replaceFile: ref(replaceFile),
uploadedImageIds: ref([]),
}
},
data() {
@@ -927,7 +904,6 @@ export default defineNuxtComponent({
newDependencyType: 'required',
newDependencyId: '',
changelogViewMode: 'source',
showSnapshots: false,
newFiles: [],
@@ -967,6 +943,14 @@ export default defineNuxtComponent({
},
},
methods: {
async onImageUpload(file) {
const response = await useImageUpload(file, { context: 'version' })
this.uploadedImageIds.push(response.id)
this.uploadedImageIds = this.uploadedImageIds.slice(-10)
return response.url
},
getPreviousLink() {
if (this.$router.options.history.state.back) {
if (
@@ -1171,6 +1155,7 @@ export default defineNuxtComponent({
}
stopLoading()
},
reportVersion,
async createVersion() {
this.shouldPreventActions = true
startLoading()
@@ -1349,6 +1334,10 @@ export default defineNuxtComponent({
</script>
<style lang="scss" scoped>
.changelog-editor-spacing {
padding-block: var(--gap-md);
}
.version-page {
display: grid;

293
pages/report.vue Normal file
View File

@@ -0,0 +1,293 @@
<template>
<div class="page">
<Card>
<div class="content">
<div>
<h1 class="card-title-adjustments">Submit a Report</h1>
<div>
<p>
Modding should be safe for everyone, so we take abuse and malicious intent seriously
at Modrinth. If you encounter content that violates our
<nuxt-link class="text-link" to="/legal/terms">Terms of Service</nuxt-link> or our
<nuxt-link class="text-link" to="/legal/rules">Rules</nuxt-link>, please report it to
us here.
</p>
<p>
This form is intended exclusively for reporting abuse or harmful content to Modrinth
staff. For bugs related to specific projects, please use the project's designated
Issues link or Discord channel.
</p>
<p>
Your privacy is important to us; rest assured that your identifying information will
be kept confidential.
</p>
</div>
</div>
<div class="report-info-section">
<div class="report-info-item">
<label for="report-item">Item type to report</label>
<DropdownSelect
id="report-item"
v-model="reportItem"
name="report-item"
:options="reportItems"
:display-name="capitalizeString"
:multiple="false"
:searchable="false"
:show-no-results="false"
:show-labels="false"
placeholder="Choose report item"
/>
</div>
<div class="report-info-item">
<label for="report-item-id">Item ID</label>
<input
id="report-item-id"
v-model="reportItemID"
type="text"
placeholder="ex. project ID"
autocomplete="off"
:disabled="reportItem === ''"
/>
</div>
<div class="report-info-item">
<label for="report-type">Reason for report</label>
<DropdownSelect
id="report-type"
v-model="reportType"
name="report-type"
:options="reportTypes"
:multiple="false"
:searchable="false"
:show-no-results="false"
:show-labels="false"
:display-name="capitalizeString"
placeholder="Choose report type"
/>
</div>
</div>
<div class="report-submission-section">
<div>
<p>
Please provide additional context about your report. Include links and images if
possible. <strong>Empty reports will be closed.</strong>
</p>
</div>
<MarkdownEditor v-model="reportBody" placeholder="" :on-image-upload="onImageUpload" />
</div>
<div class="submit-button">
<Button
id="submit-button"
color="primary"
:disabled="submitLoading || !canSubmit"
@click="submitReport"
>
<SaveIcon />
Submit
</Button>
</div>
</div>
</Card>
</div>
</template>
<script setup lang="ts">
import { Card, Button, MarkdownEditor, DropdownSelect, SaveIcon } from 'omorphia'
import { useImageUpload } from '~/composables/image-upload.ts'
const tags = useTags()
const route = useRoute()
const accessQuery = (id: string): string => {
return route.query?.[id]?.toString() || ''
}
const submitLoading = ref<boolean>(false)
const uploadedImageIDs = ref<string[]>([])
const reportBody = ref<string>(accessQuery('body'))
const reportItem = ref<string>(accessQuery('item'))
const reportItemID = ref<string>(accessQuery('itemID'))
const reportType = ref<string>('')
const reportItems = ['project', 'version', 'user']
const reportTypes = computed(() => tags.value.reportTypes)
const canSubmit = computed(() => {
return (
reportItem.value !== '' &&
reportItemID.value !== '' &&
reportType.value !== '' &&
reportBody.value !== ''
)
})
const submissionValidation = () => {
if (!canSubmit.value) {
throw new Error('Please fill out all required fields')
}
if (reportItem.value === '') {
throw new Error('Please select a report item')
}
if (reportItemID.value === '') {
throw new Error('Please enter a report item ID')
}
if (reportType.value === '') {
throw new Error('Please select a report type')
}
if (reportBody.value === '') {
throw new Error('Please enter a report body')
}
return true
}
const capitalizeString = (value?: string) => {
if (!value) return ''
return value?.charAt(0).toUpperCase() + value?.slice(1)
}
const submitReport = async () => {
submitLoading.value = true
let data: {
[key: string]: unknown
} = {
report_type: reportType.value,
item_type: reportItem.value,
item_id: reportItemID.value,
body: reportBody.value,
}
function takeNLast<T>(arr: T[], n: number): T[] {
return arr.slice(Math.max(arr.length - n, 0))
}
if (uploadedImageIDs.value.length > 0) {
data = {
...data,
uploaded_images: takeNLast(uploadedImageIDs.value, 10),
}
}
try {
submissionValidation()
} catch (error) {
submitLoading.value = false
if (error instanceof Error) {
addNotification({
group: 'main',
title: 'An error occurred',
text: error.message,
type: 'error',
})
}
return
}
try {
const response = (await useBaseFetch('report', {
method: 'POST',
body: data,
})) as { id: string }
submitLoading.value = false
if (response?.id) {
navigateTo(`/dashboard/report/${response.id}`)
}
} catch (error) {
submitLoading.value = false
if (error instanceof Error) {
addNotification({
group: 'main',
title: 'An error occurred',
text: error.message,
type: 'error',
})
}
throw error
}
}
const onImageUpload = async (file: File) => {
const item = await useImageUpload(file, { context: 'report' })
uploadedImageIDs.value.push(item.id)
return item.url
}
</script>
<style scoped lang="scss">
.submit-button {
display: flex;
justify-content: flex-end;
width: 100%;
margin-top: var(--spacing-card-md);
}
.card-title-adjustments {
margin-block: var(--spacing-card-md) var(--spacing-card-sm);
}
.page {
padding: 0.5rem;
margin-left: auto;
margin-right: auto;
max-width: 56rem;
}
.content {
// TODO: Get rid of this hack when removing global styles from the website.
// Overflow decides the behavior of md editor but also clips the border.
// In the future, we should use ring instead of block-shadow for the
// green ring around the md editor
padding-inline: var(--gap-md);
padding-bottom: var(--gap-md);
margin-inline: calc(var(--gap-md) * -1);
display: grid;
// Disable horizontal stretch
grid-template-columns: minmax(0, 1fr);
overflow: hidden;
}
.report-info-section {
display: block;
width: 100%;
gap: var(--gap-md);
:global(.animated-dropdown) {
& > .selected {
height: 40px;
}
}
.report-info-item {
display: block;
width: 100%;
max-width: 100%;
label {
display: block;
margin-bottom: var(--gap-sm);
color: var(--color-text-dark);
font-size: var(--font-size-md);
font-weight: var(--font-weight-bold);
margin-block: var(--spacing-card-md) var(--spacing-card-sm);
}
}
}
</style>

View File

@@ -1,7 +1,6 @@
<template>
<div v-if="user">
<ModalCreation ref="modal_creation" />
<ModalReport ref="modal_report" :item-id="user.id" item-type="user" />
<div class="user-header-wrapper">
<div class="user-header">
<Avatar
@@ -44,7 +43,7 @@
<button
v-else-if="auth.user"
class="iconified-button"
@click="$refs.modal_report.show()"
@click="() => reportUser(user.id)"
>
<ReportIcon aria-hidden="true" />
{{ formatMessage(messages.profileReportButton) }}
@@ -264,6 +263,7 @@
import { Promotion } from 'omorphia'
import ProjectCard from '~/components/ui/ProjectCard.vue'
import Badge from '~/components/ui/Badge.vue'
import { reportUser } from '~/utils/report-helpers.ts'
import ReportIcon from '~/assets/images/utils/report.svg'
import SunriseIcon from '~/assets/images/utils/sunrise.svg'
@@ -280,7 +280,6 @@ import ListIcon from '~/assets/images/utils/list.svg'
import ImageIcon from '~/assets/images/utils/image.svg'
import UploadIcon from '~/assets/images/utils/upload.svg'
import FileInput from '~/components/ui/FileInput.vue'
import ModalReport from '~/components/ui/ModalReport.vue'
import ModalCreation from '~/components/ui/ModalCreation.vue'
import NavRow from '~/components/ui/NavRow.vue'
import CopyCode from '~/components/ui/CopyCode.vue'

8
pnpm-lock.yaml generated
View File

@@ -35,8 +35,8 @@ dependencies:
specifier: ^13.0.1
version: 13.0.1(patch_hash=3vlxaukqep4gvqytxeznhg6wbq)
omorphia:
specifier: ^0.6.7
version: 0.6.7(vue@3.3.4)
specifier: ^0.7.1
version: 0.7.1(vue@3.3.4)
qrcode.vue:
specifier: ^3.4.0
version: 3.4.0(vue@3.3.4)
@@ -6965,8 +6965,8 @@ packages:
resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==}
dev: true
/omorphia@0.6.7(vue@3.3.4):
resolution: {integrity: sha512-ExAgYnNjaUsS3TxG9mipzEszxHzoQyx291lH8iPGhAja3ovseq/bVWgNvTSFlByANaqdotGcyQ+29Ndq7oLgjw==}
/omorphia@0.7.1(vue@3.3.4):
resolution: {integrity: sha512-YQ+u+V52LxeWaEGjEfQk6h+cF//Q9UNSiQLcpDyacTqmrsPYEQstWriBpapANG3ZsPTyAAdkGZePj5uBdYcuIg==}
peerDependencies:
vue: ^3.3.4
dependencies:

21
utils/report-helpers.ts Normal file
View File

@@ -0,0 +1,21 @@
const startReport = (type: string, id: string) => {
const prefill = new URLSearchParams()
// type
prefill.set('item', type)
prefill.set('itemID', id)
navigateTo('/report?' + prefill.toString())
}
export const reportProject = (id: string) => {
return startReport('project', id)
}
export const reportVersion = (id: string) => {
return startReport('version', id)
}
export const reportUser = (id: string) => {
return startReport('user', id)
}