1 parent
146b88afce
commit
a4615c7b73
7 files changed
+494
-13
No files matched your search
@@ -0,0 +1,187 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
NewModal,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { type SkinSiteBugIssueType, skinSiteCreateBugIssue } from '@/helpers/skin-site-api'
|
||||
|
||||
const emit = defineEmits<{ (e: 'created', id: number): void }>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { handleError, addNotification } = injectNotificationManager()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'app.skin-site.bug-create.title', defaultMessage: 'New issue' },
|
||||
type: { id: 'app.skin-site.bug-create.type', defaultMessage: 'Type' },
|
||||
titleField: { id: 'app.skin-site.bug-create.title-field', defaultMessage: 'Title' },
|
||||
titlePlaceholder: {
|
||||
id: 'app.skin-site.bug-create.title-placeholder',
|
||||
defaultMessage: 'A short summary of the problem',
|
||||
},
|
||||
description: { id: 'app.skin-site.bug-create.description', defaultMessage: 'Description' },
|
||||
descriptionPlaceholder: {
|
||||
id: 'app.skin-site.bug-create.description-placeholder',
|
||||
defaultMessage: 'What happened, and how can it be reproduced?',
|
||||
},
|
||||
privateLabel: { id: 'app.skin-site.bug-create.private', defaultMessage: 'Private issue' },
|
||||
privateHint: {
|
||||
id: 'app.skin-site.bug-create.private-hint',
|
||||
defaultMessage: 'Only you and the maintainers can see it.',
|
||||
},
|
||||
images: { id: 'app.skin-site.bug-create.images', defaultMessage: 'Images' },
|
||||
imagesHint: {
|
||||
id: 'app.skin-site.bug-create.images-hint',
|
||||
defaultMessage: 'Optional, up to 5 images.',
|
||||
},
|
||||
submit: { id: 'app.skin-site.bug-create.submit', defaultMessage: 'Create issue' },
|
||||
submitting: { id: 'app.skin-site.bug-create.submitting', defaultMessage: 'Creating…' },
|
||||
created: { id: 'app.skin-site.bug-create.created', defaultMessage: 'Issue created.' },
|
||||
cancel: { id: 'app.skin-site.bug-create.cancel', defaultMessage: 'Cancel' },
|
||||
typeBug: { id: 'app.skin-site.bugs.type.bug', defaultMessage: 'Bug' },
|
||||
typeFeature: { id: 'app.skin-site.bugs.type.feature', defaultMessage: 'Feature' },
|
||||
typeImprovement: { id: 'app.skin-site.bugs.type.improvement', defaultMessage: 'Improvement' },
|
||||
})
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>()
|
||||
const type = ref<SkinSiteBugIssueType>('BUG')
|
||||
const title = ref('')
|
||||
const description = ref('')
|
||||
const isPrivate = ref(false)
|
||||
const images = ref<File[]>([])
|
||||
const working = ref(false)
|
||||
|
||||
const TYPE_OPTIONS: SkinSiteBugIssueType[] = ['BUG', 'FEATURE', 'IMPROVEMENT']
|
||||
|
||||
function typeLabel(t: SkinSiteBugIssueType): string {
|
||||
if (t === 'FEATURE') return formatMessage(messages.typeFeature)
|
||||
if (t === 'IMPROVEMENT') return formatMessage(messages.typeImprovement)
|
||||
return formatMessage(messages.typeBug)
|
||||
}
|
||||
|
||||
const canSubmit = computed(
|
||||
() => title.value.trim().length > 0 && description.value.trim().length > 0 && !working.value,
|
||||
)
|
||||
|
||||
function reset() {
|
||||
type.value = 'BUG'
|
||||
title.value = ''
|
||||
description.value = ''
|
||||
isPrivate.value = false
|
||||
images.value = []
|
||||
working.value = false
|
||||
}
|
||||
|
||||
function show() {
|
||||
reset()
|
||||
modal.value?.show()
|
||||
}
|
||||
|
||||
function onFiles(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
images.value = Array.from(input.files ?? []).slice(0, 5)
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (!canSubmit.value) return
|
||||
working.value = true
|
||||
try {
|
||||
const result = await skinSiteCreateBugIssue(
|
||||
{
|
||||
type: type.value,
|
||||
title: title.value.trim(),
|
||||
description: description.value.trim(),
|
||||
private: isPrivate.value,
|
||||
},
|
||||
images.value,
|
||||
)
|
||||
if (!result.success) throw new Error(result.errorMessage)
|
||||
addNotification({ type: 'success', title: formatMessage(messages.created) })
|
||||
modal.value?.hide()
|
||||
emit('created', result.payload.id)
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
} finally {
|
||||
working.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
modal.value?.hide()
|
||||
reset()
|
||||
}
|
||||
|
||||
defineExpose({ show })
|
||||
</script>
|
||||
<template>
|
||||
<NewModal ref="modal" :header="formatMessage(messages.title)" @hide="reset">
|
||||
<div class="flex flex-col gap-4">
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="font-semibold text-contrast">{{ formatMessage(messages.type) }}</span>
|
||||
<select
|
||||
v-model="type"
|
||||
class="rounded-lg border border-solid border-surface-5 bg-bg-raised px-3 py-2 text-sm text-contrast"
|
||||
>
|
||||
<option v-for="t in TYPE_OPTIONS" :key="t" :value="t">{{ typeLabel(t) }}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="font-semibold text-contrast">{{ formatMessage(messages.titleField) }}</span>
|
||||
<StyledInput
|
||||
v-model="title"
|
||||
type="text"
|
||||
:placeholder="formatMessage(messages.titlePlaceholder)"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="font-semibold text-contrast">{{ formatMessage(messages.description) }}</span>
|
||||
<textarea
|
||||
v-model="description"
|
||||
rows="5"
|
||||
class="w-full resize-y rounded-lg border border-solid border-surface-5 bg-bg-raised px-3 py-2 text-sm text-contrast outline-none focus:border-brand"
|
||||
:placeholder="formatMessage(messages.descriptionPlaceholder)"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start gap-2">
|
||||
<input v-model="isPrivate" type="checkbox" class="mt-1" />
|
||||
<span class="flex flex-col">
|
||||
<span class="text-sm font-semibold text-contrast">
|
||||
{{ formatMessage(messages.privateLabel) }}
|
||||
</span>
|
||||
<span class="text-xs text-secondary">{{ formatMessage(messages.privateHint) }}</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="font-semibold text-contrast">{{ formatMessage(messages.images) }}</span>
|
||||
<input type="file" accept="image/*" multiple @change="onFiles" />
|
||||
<span class="text-xs text-secondary">{{ formatMessage(messages.imagesHint) }}</span>
|
||||
</label>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full bg-button-bg px-4 py-2 text-sm font-semibold text-contrast hover:brightness-110"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ formatMessage(messages.cancel) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full bg-brand px-4 py-2 text-sm font-semibold text-contrast disabled:opacity-50"
|
||||
:disabled="!canSubmit"
|
||||
@click="submit"
|
||||
>
|
||||
{{ working ? formatMessage(messages.submitting) : formatMessage(messages.submit) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
</template>
|
||||
@@ -27,6 +27,11 @@ const messages = defineMessages({
|
||||
all: { id: 'app.skin-site.bugs.all', defaultMessage: 'All' },
|
||||
more: { id: 'app.skin-site.bugs.more', defaultMessage: 'Load more' },
|
||||
openOnly: { id: 'app.skin-site.bugs.open-only', defaultMessage: 'Open only' },
|
||||
create: { id: 'app.skin-site.bugs.create', defaultMessage: 'New issue' },
|
||||
notifications: {
|
||||
id: 'app.skin-site.bugs.notifications',
|
||||
defaultMessage: 'Notifications',
|
||||
},
|
||||
comments: { id: 'app.skin-site.bugs.comments', defaultMessage: '{count} comments' },
|
||||
typeBug: { id: 'app.skin-site.bugs.type.bug', defaultMessage: 'Bug' },
|
||||
typeFeature: { id: 'app.skin-site.bugs.type.feature', defaultMessage: 'Feature' },
|
||||
@@ -42,7 +47,10 @@ const messages = defineMessages({
|
||||
priorityNormal: { id: 'app.skin-site.bugs.priority.normal', defaultMessage: 'Normal' },
|
||||
priorityLow: { id: 'app.skin-site.bugs.priority.low', defaultMessage: 'Low' },
|
||||
})
|
||||
const emit = defineEmits<{ (e: 'back'): void; (e: 'open', id: number): void }>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'back' | 'create' | 'notifications'): void
|
||||
(e: 'open', id: number): void
|
||||
}>()
|
||||
|
||||
const items = ref<SkinSiteBugIssue[]>([])
|
||||
const hasMore = ref(false)
|
||||
@@ -164,9 +172,27 @@ onMounted(() => load(true))
|
||||
← {{ formatMessage(messages.back) }}
|
||||
</button>
|
||||
|
||||
<header class="flex flex-col gap-1">
|
||||
<h1 class="m-0 text-xl font-bold text-contrast">{{ formatMessage(messages.title) }}</h1>
|
||||
<p class="m-0 text-sm text-secondary">{{ formatMessage(messages.subtitle) }}</p>
|
||||
<header class="flex flex-wrap items-start gap-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h1 class="m-0 text-xl font-bold text-contrast">{{ formatMessage(messages.title) }}</h1>
|
||||
<p class="m-0 text-sm text-secondary">{{ formatMessage(messages.subtitle) }}</p>
|
||||
</div>
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full bg-button-bg px-3 py-1 text-xs font-semibold text-secondary hover:brightness-110"
|
||||
@click="emit('notifications')"
|
||||
>
|
||||
{{ formatMessage(messages.notifications) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full bg-brand px-4 py-2 text-sm font-semibold text-contrast hover:brightness-110"
|
||||
@click="emit('create')"
|
||||
>
|
||||
{{ formatMessage(messages.create) }}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
<script setup lang="ts">
|
||||
import { SpinnerIcon } from '@modrinth/assets'
|
||||
import { defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
import {
|
||||
type SkinSiteBugNotification,
|
||||
skinSiteBugNotifications,
|
||||
skinSiteMarkBugNotificationRead,
|
||||
} from '@/helpers/skin-site-api'
|
||||
|
||||
const emit = defineEmits<{ (e: 'back'): void; (e: 'open', id: number): void }>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'app.skin-site.bug-notifications.title', defaultMessage: 'Notifications' },
|
||||
back: { id: 'app.skin-site.bug-notifications.back', defaultMessage: 'Back to home' },
|
||||
subtitle: {
|
||||
id: 'app.skin-site.bug-notifications.subtitle',
|
||||
defaultMessage: 'Activity on issues you reported or follow.',
|
||||
},
|
||||
loading: { id: 'app.skin-site.bug-notifications.loading', defaultMessage: 'Loading…' },
|
||||
failed: {
|
||||
id: 'app.skin-site.bug-notifications.failed',
|
||||
defaultMessage: 'Could not load notifications.',
|
||||
},
|
||||
empty: { id: 'app.skin-site.bug-notifications.empty', defaultMessage: 'No new notifications.' },
|
||||
item: {
|
||||
id: 'app.skin-site.bug-notifications.item',
|
||||
defaultMessage: '{name} updated issue #{id}',
|
||||
},
|
||||
markAll: {
|
||||
id: 'app.skin-site.bug-notifications.mark-all',
|
||||
defaultMessage: 'Mark all as read',
|
||||
},
|
||||
markedAll: {
|
||||
id: 'app.skin-site.bug-notifications.marked-all',
|
||||
defaultMessage: 'All notifications marked as read.',
|
||||
},
|
||||
})
|
||||
|
||||
const items = ref<SkinSiteBugNotification[]>([])
|
||||
const loading = ref(true)
|
||||
const failed = ref(false)
|
||||
const marking = ref(false)
|
||||
|
||||
function formatDateTime(ts: number): string {
|
||||
try {
|
||||
return new Date(ts).toLocaleString()
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
failed.value = false
|
||||
try {
|
||||
const result = await skinSiteBugNotifications()
|
||||
if (!result.success) throw new Error(result.errorMessage)
|
||||
items.value = result.payload ?? []
|
||||
} catch (error) {
|
||||
failed.value = true
|
||||
handleError(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function markAllRead() {
|
||||
if (marking.value || !items.value.length) return
|
||||
marking.value = true
|
||||
try {
|
||||
await skinSiteMarkBugNotificationRead()
|
||||
items.value = []
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
} finally {
|
||||
marking.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => void load())
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-fit items-center gap-1 rounded-full bg-button-bg px-3 py-1 text-xs font-semibold text-contrast hover:brightness-110"
|
||||
@click="emit('back')"
|
||||
>
|
||||
← {{ formatMessage(messages.back) }}
|
||||
</button>
|
||||
|
||||
<header class="flex flex-wrap items-center gap-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h1 class="m-0 text-xl font-bold text-contrast">{{ formatMessage(messages.title) }}</h1>
|
||||
<p class="m-0 text-sm text-secondary">{{ formatMessage(messages.subtitle) }}</p>
|
||||
</div>
|
||||
<button
|
||||
v-if="items.length"
|
||||
type="button"
|
||||
class="ml-auto rounded-full bg-button-bg px-3 py-1 text-xs font-semibold text-secondary hover:brightness-110 disabled:opacity-50"
|
||||
:disabled="marking"
|
||||
@click="markAllRead"
|
||||
>
|
||||
{{ formatMessage(messages.markAll) }}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div v-if="loading" class="flex items-center gap-2 text-sm text-secondary">
|
||||
<SpinnerIcon class="size-4 animate-spin" />
|
||||
{{ formatMessage(messages.loading) }}
|
||||
</div>
|
||||
<p v-else-if="failed" class="m-0 text-sm text-secondary">
|
||||
{{ formatMessage(messages.failed) }}
|
||||
</p>
|
||||
<p
|
||||
v-else-if="!items.length"
|
||||
class="m-0 rounded-xl border border-solid border-surface-5 bg-bg-raised p-4 text-sm text-secondary"
|
||||
>
|
||||
{{ formatMessage(messages.empty) }}
|
||||
</p>
|
||||
<div v-else class="flex flex-col gap-2">
|
||||
<div
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
class="flex cursor-pointer flex-wrap items-center gap-3 rounded-xl border border-solid border-surface-5 bg-bg-raised p-3 hover:border-brand"
|
||||
@click="emit('open', item.issueId)"
|
||||
>
|
||||
<span class="min-w-0 flex-1 truncate text-sm text-contrast">
|
||||
{{ formatMessage(messages.item, { name: item.actorName, id: item.issueId }) }}
|
||||
</span>
|
||||
<span class="shrink-0 text-xs text-secondary">{{ formatDateTime(item.createdAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -5,8 +5,10 @@ import { ref } from 'vue'
|
||||
|
||||
import SkinSiteAccountCard from './SkinSiteAccountCard.vue'
|
||||
import SkinSiteAnnouncements from './SkinSiteAnnouncements.vue'
|
||||
import SkinSiteBugCreateModal from './SkinSiteBugCreateModal.vue'
|
||||
import SkinSiteBugDetailView from './SkinSiteBugDetailView.vue'
|
||||
import SkinSiteBugListView from './SkinSiteBugListView.vue'
|
||||
import SkinSiteBugNotificationsView from './SkinSiteBugNotificationsView.vue'
|
||||
import SkinSiteDonationCard from './SkinSiteDonationCard.vue'
|
||||
import SkinSiteDownloadsView from './SkinSiteDownloadsView.vue'
|
||||
import SkinSiteLoginModal from './SkinSiteLoginModal.vue'
|
||||
@@ -32,9 +34,9 @@ const messages = defineMessages({
|
||||
const YGGDRASIL_URL = 'https://skin.starlight.cool/yggdrasil'
|
||||
const loginModal = ref<InstanceType<typeof SkinSiteLoginModal>>()
|
||||
const profileModal = ref<InstanceType<typeof SkinSiteProfileModal>>()
|
||||
const view = ref<'home' | 'players' | 'downloads' | 'map' | 'review' | 'bugs' | 'bug-detail'>(
|
||||
'home',
|
||||
)
|
||||
const view = ref<
|
||||
'home' | 'players' | 'downloads' | 'map' | 'review' | 'bugs' | 'bug-detail' | 'bug-notifications'
|
||||
>('home')
|
||||
const selectedBugId = ref<number | null>(null)
|
||||
|
||||
function openBug(id: number) {
|
||||
@@ -47,6 +49,16 @@ function backToBugList() {
|
||||
view.value = 'bugs'
|
||||
}
|
||||
|
||||
const bugCreateModal = ref<InstanceType<typeof SkinSiteBugCreateModal>>()
|
||||
|
||||
function openBugNotifications() {
|
||||
view.value = 'bug-notifications'
|
||||
}
|
||||
|
||||
function onBugCreated(id: number) {
|
||||
openBug(id)
|
||||
}
|
||||
|
||||
async function copyAddress() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(YGGDRASIL_URL)
|
||||
@@ -64,7 +76,18 @@ async function copyAddress() {
|
||||
<SkinSiteDownloadsView v-else-if="view === 'downloads'" @back="view = 'home'" />
|
||||
<SkinSiteMapView v-else-if="view === 'map'" @back="view = 'home'" />
|
||||
<SkinSiteReviewView v-else-if="view === 'review'" @back="view = 'home'" />
|
||||
<SkinSiteBugListView v-else-if="view === 'bugs'" @back="view = 'home'" @open="openBug" />
|
||||
<SkinSiteBugListView
|
||||
v-else-if="view === 'bugs'"
|
||||
@back="view = 'home'"
|
||||
@open="openBug"
|
||||
@create="bugCreateModal?.show()"
|
||||
@notifications="openBugNotifications"
|
||||
/>
|
||||
<SkinSiteBugNotificationsView
|
||||
v-else-if="view === 'bug-notifications'"
|
||||
@back="view = 'bugs'"
|
||||
@open="openBug"
|
||||
/>
|
||||
<SkinSiteBugDetailView
|
||||
v-else-if="view === 'bug-detail' && selectedBugId !== null"
|
||||
:issue-id="selectedBugId"
|
||||
@@ -111,5 +134,6 @@ async function copyAddress() {
|
||||
|
||||
<SkinSiteLoginModal ref="loginModal" />
|
||||
<SkinSiteProfileModal ref="profileModal" />
|
||||
<SkinSiteBugCreateModal ref="bugCreateModal" @created="onBugCreated" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -133,6 +133,8 @@ interface SendOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||
type?: RequestType
|
||||
body?: unknown
|
||||
/** Send a multipart form body (e.g. issue creation with images). */
|
||||
form?: FormData
|
||||
/** Set to false to skip the Authorization header (e.g. login). */
|
||||
auth?: boolean
|
||||
}
|
||||
@@ -141,13 +143,14 @@ export async function skinSiteRequest<T>(
|
||||
path: string,
|
||||
options: SendOptions = {},
|
||||
): Promise<SkinSiteResponse<T>> {
|
||||
const { method = 'GET', type = 'starlight', body, auth = true } = options
|
||||
const { method = 'GET', type = 'starlight', body, form, auth = true } = options
|
||||
const url = `${SKIN_SITE_ORIGIN}${REQUEST_TYPE_PATH[type]}/${path.replace(/^\/+/, '')}`
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Accept: 'application/json',
|
||||
}
|
||||
if (body !== undefined) headers['Content-Type'] = 'application/json'
|
||||
// FormData sets its own multipart boundary; never set Content-Type for it.
|
||||
if (auth) {
|
||||
const token = getSkinSiteToken()
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`
|
||||
@@ -158,7 +161,7 @@ export async function skinSiteRequest<T>(
|
||||
response = await tauriFetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
body: form ?? (body !== undefined ? JSON.stringify(body) : undefined),
|
||||
})
|
||||
} catch {
|
||||
return handleException(0)
|
||||
@@ -750,10 +753,14 @@ export function skinSiteBugComments(
|
||||
export function skinSiteCreateBugComment(
|
||||
issueId: number,
|
||||
content: string,
|
||||
images: File[] = [],
|
||||
): Promise<SkinSiteResponse<SkinSiteBugComment>> {
|
||||
const form = new FormData()
|
||||
form.append('payload', JSON.stringify({ content }))
|
||||
for (const image of images) form.append('images', image)
|
||||
return skinSiteRequest<SkinSiteBugComment>(`bug/${issueId}/comments`, {
|
||||
method: 'POST',
|
||||
body: { content },
|
||||
form,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -768,3 +775,40 @@ export function skinSiteBugUnreadCount(): Promise<SkinSiteResponse<number>> {
|
||||
export function skinSiteMarkBugNotificationsRead(): Promise<SkinSiteResponse<string>> {
|
||||
return skinSiteRequest<string>('bug/notifications/read', { method: 'POST' })
|
||||
}
|
||||
|
||||
export interface SkinSiteBugCreateRequest {
|
||||
type: SkinSiteBugIssueType
|
||||
title: string
|
||||
description: string
|
||||
private?: boolean
|
||||
}
|
||||
|
||||
export function skinSiteCreateBugIssue(
|
||||
request: SkinSiteBugCreateRequest,
|
||||
images: File[] = [],
|
||||
): Promise<SkinSiteResponse<SkinSiteBugIssue>> {
|
||||
const form = new FormData()
|
||||
form.append('payload', JSON.stringify(request))
|
||||
for (const image of images) form.append('images', image)
|
||||
return skinSiteRequest<SkinSiteBugIssue>('bug', { method: 'POST', form })
|
||||
}
|
||||
|
||||
export interface SkinSiteBugNotification {
|
||||
id: number
|
||||
issueId: number
|
||||
actorName: string
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
export function skinSiteBugNotifications(): Promise<SkinSiteResponse<SkinSiteBugNotification[]>> {
|
||||
return skinSiteRequest<SkinSiteBugNotification[]>('bug/notifications')
|
||||
}
|
||||
|
||||
export function skinSiteMarkBugNotificationRead(
|
||||
issueId?: number,
|
||||
): Promise<SkinSiteResponse<string>> {
|
||||
return skinSiteRequest<string>('bug/notifications/read', {
|
||||
method: 'POST',
|
||||
body: issueId === undefined ? {} : { issueId },
|
||||
})
|
||||
}
|
||||
@@ -11737,5 +11737,35 @@
|
||||
"app.skin-site.bug-detail.comment-deleted": {"message": "Comment deleted."},
|
||||
"app.skin-site.bug-detail.delete": {"message": "Delete"},
|
||||
"app.skin-site.bug-detail.deleting": {"message": "Deleting…"},
|
||||
"app.skin-site.bug-detail.system": {"message": "System"}
|
||||
"app.skin-site.bug-detail.system": {"message": "System"},
|
||||
"app.skin-site.bug-create.title": {"message": "New issue"},
|
||||
"app.skin-site.bug-create.type": {"message": "Type"},
|
||||
"app.skin-site.bug-create.title-field": {"message": "Title"},
|
||||
"app.skin-site.bug-create.title-placeholder": {"message": "A short summary of the problem"},
|
||||
"app.skin-site.bug-create.description": {"message": "Description"},
|
||||
"app.skin-site.bug-create.description-placeholder": {"message": "What happened, and how can it be reproduced?"},
|
||||
"app.skin-site.bug-create.private": {"message": "Private issue"},
|
||||
"app.skin-site.bug-create.private-hint": {"message": "Only you and the maintainers can see it."},
|
||||
"app.skin-site.bug-create.images": {"message": "Images"},
|
||||
"app.skin-site.bug-create.images-hint": {"message": "Optional, up to 5 images."},
|
||||
"app.skin-site.bug-create.submit": {"message": "Create issue"},
|
||||
"app.skin-site.bug-create.submitting": {"message": "Creating…"},
|
||||
"app.skin-site.bug-create.created": {"message": "Issue created."},
|
||||
"app.skin-site.bug-create.cancel": {"message": "Cancel"},
|
||||
"app.skin-site.bug-notifications.title": {"message": "Notifications"},
|
||||
"app.skin-site.bug-notifications.back": {"message": "Back to home"},
|
||||
"app.skin-site.bug-notifications.subtitle": {"message": "Activity on issues you reported or follow."},
|
||||
"app.skin-site.bug-notifications.loading": {"message": "Loading…"},
|
||||
"app.skin-site.bug-notifications.failed": {"message": "Could not load notifications."},
|
||||
"app.skin-site.bug-notifications.empty": {"message": "No new notifications."},
|
||||
"app.skin-site.bug-notifications.item": {"message": "{name} updated issue #{id}"},
|
||||
"app.skin-site.bug-notifications.mark-all": {"message": "Mark all as read"},
|
||||
"app.skin-site.bug-notifications.marked-all": {"message": "All notifications marked as read."},
|
||||
"app.skin-site.bugs.create": {"message": "New issue"},
|
||||
"app.skin-site.bugs.notifications": {"message": "Notifications"},
|
||||
"app.skin-site.home.title": {"message": "StarLight Skin Site"},
|
||||
"app.skin-site.home.subtitle": {"message": "The wardrobe for StarLight Server players."},
|
||||
"app.skin-site.home.address": {"message": "Skin site address"},
|
||||
"app.skin-site.home.copied": {"message": "Address copied."},
|
||||
"app.skin-site.home.copy": {"message": "Copy"}
|
||||
}
|
||||
@@ -11568,5 +11568,35 @@
|
||||
"app.skin-site.bug-detail.comment-deleted": {"message": "评论已删除。"},
|
||||
"app.skin-site.bug-detail.delete": {"message": "删除"},
|
||||
"app.skin-site.bug-detail.deleting": {"message": "删除中…"},
|
||||
"app.skin-site.bug-detail.system": {"message": "系统"}
|
||||
"app.skin-site.bug-detail.system": {"message": "系统"},
|
||||
"app.skin-site.bug-create.title": {"message": "新建问题"},
|
||||
"app.skin-site.bug-create.type": {"message": "类型"},
|
||||
"app.skin-site.bug-create.title-field": {"message": "标题"},
|
||||
"app.skin-site.bug-create.title-placeholder": {"message": "简要描述问题"},
|
||||
"app.skin-site.bug-create.description": {"message": "描述"},
|
||||
"app.skin-site.bug-create.description-placeholder": {"message": "发生了什么?如何复现?"},
|
||||
"app.skin-site.bug-create.private": {"message": "私密问题"},
|
||||
"app.skin-site.bug-create.private-hint": {"message": "仅你和维护者可见。"},
|
||||
"app.skin-site.bug-create.images": {"message": "图片"},
|
||||
"app.skin-site.bug-create.images-hint": {"message": "可选,最多 5 张图片。"},
|
||||
"app.skin-site.bug-create.submit": {"message": "创建问题"},
|
||||
"app.skin-site.bug-create.submitting": {"message": "创建中…"},
|
||||
"app.skin-site.bug-create.created": {"message": "问题已创建。"},
|
||||
"app.skin-site.bug-create.cancel": {"message": "取消"},
|
||||
"app.skin-site.bug-notifications.title": {"message": "通知"},
|
||||
"app.skin-site.bug-notifications.back": {"message": "返回主页"},
|
||||
"app.skin-site.bug-notifications.subtitle": {"message": "你提交或关注的问题动态。"},
|
||||
"app.skin-site.bug-notifications.loading": {"message": "加载中…"},
|
||||
"app.skin-site.bug-notifications.failed": {"message": "无法加载通知。"},
|
||||
"app.skin-site.bug-notifications.empty": {"message": "暂无新通知。"},
|
||||
"app.skin-site.bug-notifications.item": {"message": "{name} 更新了问题 #{id}"},
|
||||
"app.skin-site.bug-notifications.mark-all": {"message": "全部标为已读"},
|
||||
"app.skin-site.bug-notifications.marked-all": {"message": "已将全部通知标为已读。"},
|
||||
"app.skin-site.bugs.create": {"message": "新建问题"},
|
||||
"app.skin-site.bugs.notifications": {"message": "通知"},
|
||||
"app.skin-site.home.title": {"message": "星光皮肤站"},
|
||||
"app.skin-site.home.subtitle": {"message": "星光服务器玩家的衣柜。"},
|
||||
"app.skin-site.home.address": {"message": "皮肤站地址"},
|
||||
"app.skin-site.home.copied": {"message": "地址已复制。"},
|
||||
"app.skin-site.home.copy": {"message": "复制"}
|
||||
}
|
||||
Reference in new issue
Block a user