mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f11c210f0 | ||
|
|
47103ce3e9 | ||
|
|
da775bd1c1 | ||
|
|
f5072c917a | ||
|
|
8680e61883 | ||
|
|
ea86db450b | ||
|
|
bbdaa171e3 | ||
|
|
af8daca059 | ||
|
|
2434f0c45b | ||
|
|
332dfe1074 |
@@ -884,7 +884,7 @@ async function search(requestParams: string) {
|
||||
const rawResults = await queryClient.fetchQuery({
|
||||
queryKey: ['search', 'v3', requestParams],
|
||||
queryFn: () =>
|
||||
get_search_results_v3(requestParams) as Promise<{
|
||||
get_search_results_v3(requestParams, 'must_revalidate') as Promise<{
|
||||
result: Labrinth.Search.v3.SearchResults & {
|
||||
hits: (Labrinth.Search.v3.ResultSearchProject & { installed?: boolean })[]
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ function installAdsConsentOverlayStyle() {
|
||||
html.modrinth-ads-consent-overlay #modrinth-rail-1 {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.qc-cmp2-close-icon {
|
||||
background: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M.5.5l23 23m0-23l-23 23' fill='none' stroke='%23b0bac5' stroke-width='3' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10'/%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3C/svg%3E") 0% 0% / 66% auto no-repeat !important;
|
||||
}
|
||||
`
|
||||
document.documentElement.appendChild(style)
|
||||
}
|
||||
@@ -186,6 +190,7 @@ function muteVideos() {
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
installAdsConsentOverlayStyle()
|
||||
muteVideos()
|
||||
muteAudioContext()
|
||||
installTcfConsentListener()
|
||||
|
||||
@@ -1969,11 +1969,8 @@ paths:
|
||||
- `project_id`
|
||||
- `license`
|
||||
- `downloads`
|
||||
- `color`
|
||||
- `created_timestamp` (uses Unix timestamp)
|
||||
- `modified_timestamp` (uses Unix timestamp)
|
||||
- `date_created` (uses ISO-8601 timestamp)
|
||||
- `date_modified` (uses ISO-8601 timestamp)
|
||||
|
||||
In order to then use these facets, you need a value to filter by, as well as an operation to perform on this value.
|
||||
The most common operation is `:` (same as `=`), though you can also use `!=`, `>=`, `>`, `<=`, and `<`.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:description="
|
||||
formatMessage(messages.removePasskeyConfirmDescription, { name: passkeyToRemove?.name })
|
||||
"
|
||||
:proceed-label="formatMessage(commonMessages.removeButton)"
|
||||
:proceed-label="formatMessage(messages.deletePasskeyButton)"
|
||||
@proceed="removePasskey()"
|
||||
/>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(commonMessages.removeButton)"
|
||||
v-tooltip="formatMessage(messages.deletePasskeyButton)"
|
||||
@click="
|
||||
() => {
|
||||
passkeyToRemove = passkey
|
||||
@@ -88,7 +88,7 @@
|
||||
<ButtonStyled>
|
||||
<button @click="registerPasskey()">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(messages.managePasskeyAddPasskey) }}
|
||||
{{ formatMessage(messages.managePasskeyAddPasskeyButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
@@ -104,7 +104,7 @@
|
||||
<NewModal
|
||||
ref="addPasskeyModal"
|
||||
width="500px"
|
||||
:header="formatMessage(messages.managePasskeyAddPasskey)"
|
||||
:header="formatMessage(messages.passkeyAddModalTitle)"
|
||||
>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col gap-2.5">
|
||||
@@ -133,7 +133,7 @@
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="!pendingPasskeyName" @click="finishRegisterPasskey()">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(messages.managePasskeyAddPasskey) }}
|
||||
{{ formatMessage(messages.managePasskeyAddPasskeyButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -189,7 +189,7 @@
|
||||
<div>
|
||||
<ButtonStyled>
|
||||
<button id="manage-passkeys" @click="showPasskeyModal">
|
||||
<UserKeyIcon /> {{ formatMessage(messages.managePasskeyTitle) }}
|
||||
<UserKeyIcon /> {{ formatMessage(messages.managePasskeyButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -247,12 +247,16 @@ const messages = defineMessages({
|
||||
id: 'settings.account.security.passkey.title',
|
||||
defaultMessage: 'Manage passkeys',
|
||||
},
|
||||
managePasskeyButton: {
|
||||
id: 'settings.account.security.passkey.button',
|
||||
defaultMessage: 'Manage passkeys',
|
||||
},
|
||||
managePasskeyDescription: {
|
||||
id: 'settings.account.security.passkey.description',
|
||||
defaultMessage: 'Manage your registered passkeys, or add a new one.',
|
||||
},
|
||||
managePasskeyAddPasskey: {
|
||||
id: 'settings.account.security.passkey.add',
|
||||
managePasskeyAddPasskeyButton: {
|
||||
id: 'settings.account.security.passkey.add.button',
|
||||
defaultMessage: 'Add passkey',
|
||||
},
|
||||
managePasskeyModalLoading: {
|
||||
@@ -275,6 +279,10 @@ const messages = defineMessages({
|
||||
id: 'settings.account.security.passkey.modal.never-used',
|
||||
defaultMessage: 'Never used',
|
||||
},
|
||||
passkeyAddModalTitle: {
|
||||
id: 'settings.account.security.passkey.add-modal.title',
|
||||
defaultMessage: 'Add passkey',
|
||||
},
|
||||
passkeyNameLabel: {
|
||||
id: 'settings.account.security.passkey.add-modal.name.label',
|
||||
defaultMessage: 'Name',
|
||||
@@ -292,6 +300,10 @@ const messages = defineMessages({
|
||||
id: 'settings.account.security.passkey.rename-modal.header',
|
||||
defaultMessage: 'Rename passkey',
|
||||
},
|
||||
deletePasskeyButton: {
|
||||
id: 'settings.account.security.passkey.remove.button',
|
||||
defaultMessage: 'Delete passkey',
|
||||
},
|
||||
removePasskeyConfirmTitle: {
|
||||
id: 'settings.account.security.passkey.remove.title',
|
||||
defaultMessage: 'Are you sure you want to remove this passkey?',
|
||||
|
||||
@@ -37,21 +37,22 @@
|
||||
>
|
||||
{{ formatRelativeTime(report.created) }}
|
||||
</span>
|
||||
<ButtonStyled circular>
|
||||
<OverflowMenu :options="quickActions">
|
||||
<template #default>
|
||||
<EllipsisVerticalIcon class="size-4" />
|
||||
</template>
|
||||
<template #copy-id>
|
||||
<div class="flex items-center gap-2">
|
||||
<ButtonStyled circular>
|
||||
<button v-tooltip="'Copy ID'" @click="copyId">
|
||||
<ClipboardCopyIcon />
|
||||
<span class="hidden sm:inline">Copy ID</span>
|
||||
</template>
|
||||
<template #copy-link>
|
||||
<LinkIcon />
|
||||
<span class="hidden sm:inline">Copy link</span>
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<a
|
||||
v-tooltip="'Open in new tab'"
|
||||
:href="`/moderation/reports/${props.report.id}`"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalIcon />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -183,12 +184,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ClipboardCopyIcon,
|
||||
EllipsisVerticalIcon,
|
||||
LinkIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { CheckCircleIcon, ClipboardCopyIcon, ExternalIcon } from '@modrinth/assets'
|
||||
import { type ExtendedReport, reportQuickReplies } from '@modrinth/moderation'
|
||||
import {
|
||||
Avatar,
|
||||
@@ -196,8 +192,6 @@ import {
|
||||
CollapsibleRegion,
|
||||
getProjectTypeIcon,
|
||||
injectNotificationManager,
|
||||
OverflowMenu,
|
||||
type OverflowMenuOption,
|
||||
useFormatDateTime,
|
||||
useRelativeTime,
|
||||
} from '@modrinth/ui'
|
||||
@@ -328,35 +322,6 @@ function updateThread(newThread: any) {
|
||||
}
|
||||
}
|
||||
|
||||
const quickActions: OverflowMenuOption[] = [
|
||||
{
|
||||
id: 'copy-link',
|
||||
action: () => {
|
||||
const base = window.location.origin
|
||||
const reportUrl = `${base}/moderation/reports/${props.report.id}`
|
||||
navigator.clipboard.writeText(reportUrl).then(() => {
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: 'Report link copied',
|
||||
text: 'The link to this report has been copied to your clipboard.',
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'copy-id',
|
||||
action: () => {
|
||||
navigator.clipboard.writeText(props.report.id).then(() => {
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: 'Report ID copied',
|
||||
text: 'The ID of this report has been copied to your clipboard.',
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const reportItemAvatarUrl = computed(() => {
|
||||
switch (props.report.item_type) {
|
||||
case 'project':
|
||||
@@ -395,4 +360,14 @@ const formattedReportType = computed(() => {
|
||||
const words = reportType.includes('-') ? reportType.split('-') : reportType.split(' ')
|
||||
return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')
|
||||
})
|
||||
|
||||
function copyId() {
|
||||
navigator.clipboard.writeText(props.report.id).then(() => {
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: 'Report ID copied',
|
||||
text: 'The ID of this report has been copied to your clipboard.',
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -123,30 +123,36 @@ const projectStatusActions = computed<OverflowMenuOption[]>(() => [
|
||||
color: 'green',
|
||||
action: () => setStatus('approved'),
|
||||
hoverFilled: true,
|
||||
disabled: isProjectApproved.value || isLoadingStatusAction.value,
|
||||
disabled: isStatusActionDisabled('approved'),
|
||||
},
|
||||
{
|
||||
id: 'withhold',
|
||||
color: 'orange',
|
||||
action: () => setStatus('withheld'),
|
||||
hoverFilled: true,
|
||||
disabled: projectStatus.value === 'withheld' || isLoadingStatusAction.value,
|
||||
disabled: isStatusActionDisabled('withheld'),
|
||||
},
|
||||
{
|
||||
id: 'send-to-review',
|
||||
action: () => setStatus('processing'),
|
||||
hoverFilled: true,
|
||||
disabled: projectStatus.value === 'processing' || isLoadingStatusAction.value,
|
||||
disabled: isStatusActionDisabled('processing'),
|
||||
},
|
||||
{
|
||||
id: 'reject',
|
||||
color: 'red',
|
||||
action: () => setStatus('rejected'),
|
||||
hoverFilled: true,
|
||||
disabled: projectStatus.value === 'rejected' || isLoadingStatusAction.value,
|
||||
disabled: isStatusActionDisabled('rejected'),
|
||||
},
|
||||
])
|
||||
|
||||
function isStatusActionDisabled(status: Labrinth.Projects.v2.ProjectStatus): boolean {
|
||||
const currentStatus = projectStatus.value
|
||||
const isLoading = isLoadingStatusAction.value
|
||||
return currentStatus === status || isLoading
|
||||
}
|
||||
|
||||
async function setStatus(status: Labrinth.Projects.v2.ProjectStatus) {
|
||||
isLoadingStatusAction.value = true
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<kbd
|
||||
v-for="(definition, index) in definitions"
|
||||
:key="`keybind-${index}`"
|
||||
ref="keybinding"
|
||||
class="cursor-pointer border-2 !text-lg font-bold"
|
||||
:class="{
|
||||
editing: editing === index,
|
||||
}"
|
||||
@click="startEditing(index)"
|
||||
>
|
||||
{{ toDisplay(definition) }}
|
||||
</kbd>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type KeybindDefinition, toKeybindDefinition } from '@modrinth/moderation'
|
||||
import { onUnmounted } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
definitions: KeybindDefinition[]
|
||||
onChange: (definitions: KeybindDefinition[]) => void
|
||||
}>()
|
||||
|
||||
const keybinding = useTemplateRef('keybinding')
|
||||
const definitions = ref(structuredClone(props.definitions))
|
||||
const editing = ref(-1)
|
||||
|
||||
function startEditing(index: number) {
|
||||
if (editing.value === index) {
|
||||
stopEditing()
|
||||
} else {
|
||||
editing.value = index
|
||||
window.addEventListener('keyup', handleKeybinds)
|
||||
window.addEventListener('click', handleMouse)
|
||||
}
|
||||
}
|
||||
|
||||
function stopEditing() {
|
||||
console.log('stop editing')
|
||||
|
||||
editing.value = -1
|
||||
window.removeEventListener('keyup', handleKeybinds)
|
||||
window.removeEventListener('click', handleMouse)
|
||||
}
|
||||
|
||||
function handleMouse(event: MouseEvent) {
|
||||
if (keybinding.value && event.target && editing.value != -1) {
|
||||
const editingRef = keybinding.value[editing.value]
|
||||
if (editingRef === event.target || editingRef.contains(event.target)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
stopEditing()
|
||||
}
|
||||
|
||||
function handleKeybinds(event: KeyboardEvent) {
|
||||
definitions.value[editing.value] = toKeybindDefinition(event)
|
||||
props.onChange(definitions.value)
|
||||
stopEditing()
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
function toDisplay(definition: KeybindDefinition): string {
|
||||
const keys = []
|
||||
|
||||
if (definition.ctrl || definition.meta) {
|
||||
keys.push(isMac() ? 'CMD' : 'CTRL')
|
||||
}
|
||||
if (definition.shift) keys.push('SHIFT')
|
||||
if (definition.alt) keys.push('ALT')
|
||||
|
||||
const mainKey = definition.key
|
||||
.toUpperCase()
|
||||
.replace('ARROWLEFT', '←')
|
||||
.replace('ARROWRIGHT', '→')
|
||||
.replace('ARROWUP', '↑')
|
||||
.replace('ARROWDOWN', '↓')
|
||||
.replace('ENTER', '↵')
|
||||
.replace('ESCAPE', 'ESC')
|
||||
|
||||
keys.push(mainKey)
|
||||
|
||||
return keys.join(' + ')
|
||||
}
|
||||
|
||||
function isMac() {
|
||||
return navigator.platform.toUpperCase().includes('MAC')
|
||||
}
|
||||
|
||||
onUnmounted(stopEditing)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.editing {
|
||||
animation: blink 1s step-end infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%,
|
||||
100% {
|
||||
border-color: var(--color-red);
|
||||
box-shadow: 0 0 10px 1px var(--color-red);
|
||||
}
|
||||
|
||||
50% {
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,26 +1,32 @@
|
||||
<template>
|
||||
<NewModal ref="modal" header="Moderation shortcuts" :closable="true">
|
||||
<div>
|
||||
<div id="moderation-checklist-keybinds-modal">
|
||||
<div class="keybinds-sections">
|
||||
<div class="grid grid-cols-2 gap-x-12 gap-y-3">
|
||||
<div
|
||||
v-for="keybind in keybinds"
|
||||
:key="keybind.id"
|
||||
class="keybind-item flex items-center justify-between gap-4"
|
||||
v-for="[id, keybind] in Object.entries(keybinds)"
|
||||
:key="id"
|
||||
class="keybind-item flex flex-wrap items-center justify-between gap-4"
|
||||
:class="{
|
||||
'col-span-2': keybinds.length % 2 === 1 && keybinds[keybinds.length - 1] === keybind,
|
||||
'col-span-2':
|
||||
Object.keys(keybinds).length % 2 === 1 &&
|
||||
Object.keys(keybinds)[Object.keys(keybinds).length - 1] === id,
|
||||
}"
|
||||
>
|
||||
<span class="text-sm text-secondary">{{ keybind.description }}</span>
|
||||
<div class="flex items-center gap-1">
|
||||
<kbd
|
||||
v-for="(key, index) in parseKeybindDisplay(keybind.keybind)"
|
||||
:key="`${keybind.id}-key-${index}`"
|
||||
class="keybind-key"
|
||||
>
|
||||
{{ key }}
|
||||
</kbd>
|
||||
</div>
|
||||
<ChecklistKeybind
|
||||
:definitions="
|
||||
(!Array.isArray(keybind.keybind) ? [keybind.keybind] : keybind.keybind).map(
|
||||
normalizeKeybind,
|
||||
)
|
||||
"
|
||||
:on-change="
|
||||
(definitions) => {
|
||||
keybinds[id].keybind = definitions
|
||||
saveModerationKeybinds()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,43 +35,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type KeybindListener, keybinds, normalizeKeybind } from '@modrinth/moderation'
|
||||
import { normalizeKeybind } from '@modrinth/moderation'
|
||||
import NewModal from '@modrinth/ui/src/components/modal/NewModal.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { saveModerationKeybinds } from '#imports'
|
||||
import ChecklistKeybind from '~/components/ui/moderation/checklist/ChecklistKeybind.vue'
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>()
|
||||
|
||||
function parseKeybindDisplay(keybind: KeybindListener['keybind']): string[] {
|
||||
const keybinds = Array.isArray(keybind) ? keybind : [keybind]
|
||||
const normalized = keybinds[0]
|
||||
const def = normalizeKeybind(normalized)
|
||||
|
||||
const keys = []
|
||||
|
||||
if (def.ctrl || def.meta) {
|
||||
keys.push(isMac() ? 'CMD' : 'CTRL')
|
||||
}
|
||||
if (def.shift) keys.push('SHIFT')
|
||||
if (def.alt) keys.push('ALT')
|
||||
|
||||
const mainKey = def.key
|
||||
.replace('ArrowLeft', '←')
|
||||
.replace('ArrowRight', '→')
|
||||
.replace('ArrowUp', '↑')
|
||||
.replace('ArrowDown', '↓')
|
||||
.replace('Enter', '↵')
|
||||
.replace('Space', 'SPACE')
|
||||
.replace('Escape', 'ESC')
|
||||
.toUpperCase()
|
||||
|
||||
keys.push(mainKey)
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
function isMac() {
|
||||
return navigator.platform.toUpperCase().includes('MAC')
|
||||
}
|
||||
const keybinds = useModerationKeybinds()
|
||||
|
||||
function show(event?: MouseEvent) {
|
||||
modal.value?.show(event)
|
||||
@@ -82,29 +60,6 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.keybind-key {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-divider);
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-contrast);
|
||||
|
||||
+ .keybind-key {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.keybind-item {
|
||||
min-height: 2rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.keybinds-sections {
|
||||
.grid {
|
||||
|
||||
@@ -478,7 +478,6 @@ import {
|
||||
handleKeybind,
|
||||
initializeActionState,
|
||||
kebabToTitleCase,
|
||||
keybinds,
|
||||
type MultiSelectChipsAction,
|
||||
processMessage,
|
||||
type Stage,
|
||||
@@ -533,6 +532,7 @@ import ModpackPermissionsFlow from './ModpackPermissionsFlow.vue'
|
||||
const notifications = injectNotificationManager()
|
||||
const { addNotification } = notifications
|
||||
const debug = useDebugLogger('ModerationChecklist')
|
||||
const keybinds = useModerationKeybinds()
|
||||
|
||||
const keybindsModal = ref<InstanceType<typeof KeybindsModal>>()
|
||||
const takeOverModal = ref<InstanceType<typeof ConfirmModal>>()
|
||||
@@ -1266,7 +1266,7 @@ function handleKeybinds(event: KeyboardEvent) {
|
||||
},
|
||||
},
|
||||
},
|
||||
keybinds,
|
||||
Object.values(keybinds.value),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
type KeybindDefinition,
|
||||
type KeybindListener,
|
||||
keybinds,
|
||||
normalizeKeybind,
|
||||
} from '@modrinth/moderation'
|
||||
|
||||
import type { CookieOptions } from '#app'
|
||||
|
||||
const moderationKeybindsId = 'moderation-keybinds'
|
||||
|
||||
type StoredKeybinds = { [id: string]: KeybindDefinition[] }
|
||||
type PartialStoredKeybinds = Partial<StoredKeybinds>
|
||||
|
||||
const getCookieOptions = () =>
|
||||
({
|
||||
maxAge: 60 * 60 * 24 * 365 * 10,
|
||||
sameSite: 'lax',
|
||||
secure: useRuntimeConfig().public.cookieSecure,
|
||||
httpOnly: false,
|
||||
path: '/',
|
||||
}) satisfies CookieOptions<PartialStoredKeybinds>
|
||||
|
||||
export const useModerationKeybinds = () =>
|
||||
useState<{ [id: string]: KeybindListener }>(moderationKeybindsId, () => {
|
||||
const storedKeybinds = useCookie<PartialStoredKeybinds>(
|
||||
moderationKeybindsId,
|
||||
getCookieOptions(),
|
||||
)
|
||||
|
||||
if (!storedKeybinds.value) {
|
||||
storedKeybinds.value = {}
|
||||
}
|
||||
|
||||
const output: { [id: string]: KeybindListener } = {}
|
||||
|
||||
for (const [id, keybind] of Object.entries(keybinds)) {
|
||||
const definitions = storedKeybinds.value[id]
|
||||
output[id] = {
|
||||
keybind: definitions !== undefined ? definitions : keybind.keybind,
|
||||
description: keybind.description,
|
||||
enabled: keybind.enabled,
|
||||
action: keybind.action,
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
})
|
||||
|
||||
export const saveModerationKeybinds = () => {
|
||||
const keybinds = useModerationKeybinds()
|
||||
const cookie = useCookie<PartialStoredKeybinds>(moderationKeybindsId, getCookieOptions())
|
||||
|
||||
const storedKeybinds: PartialStoredKeybinds = {}
|
||||
for (const [id, keybind] of Object.entries(keybinds.value)) {
|
||||
storedKeybinds[id] = (Array.isArray(keybind.keybind) ? keybind.keybind : [keybind.keybind]).map(
|
||||
normalizeKeybind,
|
||||
)
|
||||
}
|
||||
cookie.value = storedKeybinds
|
||||
}
|
||||
@@ -4052,9 +4052,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "E-Mail"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "Passkey hinzufügen"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "Stell sicher, dass du etwas nimmst, was du dir gut merken kannst, damit du den Passkey später identifizieren kannst."
|
||||
},
|
||||
|
||||
@@ -4052,9 +4052,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "E-Mail"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "Passkey hinzufügen"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "Stell sicher, dass du etwas nimmst, was du dir gut merken kannst, damit du den Passkey später identifizieren kannst."
|
||||
},
|
||||
|
||||
@@ -4208,9 +4208,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "Email"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "Add passkey"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "Make sure to pick something memorable, so you can identify this passkey later."
|
||||
},
|
||||
@@ -4220,6 +4217,15 @@
|
||||
"settings.account.security.passkey.add-modal.name.placeholder": {
|
||||
"message": "My passkey"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.title": {
|
||||
"message": "Add passkey"
|
||||
},
|
||||
"settings.account.security.passkey.add.button": {
|
||||
"message": "Add passkey"
|
||||
},
|
||||
"settings.account.security.passkey.button": {
|
||||
"message": "Manage passkeys"
|
||||
},
|
||||
"settings.account.security.passkey.description": {
|
||||
"message": "Manage your registered passkeys, or add a new one."
|
||||
},
|
||||
@@ -4238,6 +4244,9 @@
|
||||
"settings.account.security.passkey.modal.no-passkeys": {
|
||||
"message": "You do not have any passkeys registered."
|
||||
},
|
||||
"settings.account.security.passkey.remove.button": {
|
||||
"message": "Delete passkey"
|
||||
},
|
||||
"settings.account.security.passkey.remove.description": {
|
||||
"message": "This will permanently remove the passkey \"{name}\". You will no longer be able to sign in with it."
|
||||
},
|
||||
|
||||
@@ -4052,9 +4052,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "Correo electrónico"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "Agregar clave de acceso"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "Asegúrate de elegir algo fácil de recordar, para que puedas identificar esta contraseña más adelante."
|
||||
},
|
||||
|
||||
@@ -4052,9 +4052,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "E-mail"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "Ajouter une clé d'accès"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "Veillez à choisir un nom facile à retenir, afin de pouvoir identifier tę klucz d'accès plus tard."
|
||||
},
|
||||
|
||||
@@ -4025,9 +4025,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "Email"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "Aggiungi passkey"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "Assicurati di scegliere un nome facile da ricordare, così da poter identificare la passkey più tardi."
|
||||
},
|
||||
|
||||
@@ -4046,9 +4046,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "E-mail"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "Dodaj klucz logowania"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "Wybierz coś, co zapamiętasz, by móć później rozpoznać ten klucz logowania."
|
||||
},
|
||||
|
||||
@@ -4043,9 +4043,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "E-mail"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "Adicionar chave de acesso"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "Certifique-se de adicionar algo memorável, para poder identificar mais tarde."
|
||||
},
|
||||
|
||||
@@ -4022,9 +4022,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "Почта"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "Добавить ключ доступа"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "Выберите что-то запоминающееся, чтобы потом опознать этот ключ доступа."
|
||||
},
|
||||
|
||||
@@ -3938,9 +3938,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "Eposta"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "Giriş anahtarı ekle"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "Giriş anahtarınızı hatırlamanız için akılda kalıcı bir şey seçin."
|
||||
},
|
||||
|
||||
@@ -4046,9 +4046,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "电子邮箱"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "添加通行密钥"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "请确保你选择的名称易于记忆,方便你日后辨认出该通行密钥。"
|
||||
},
|
||||
|
||||
@@ -4052,9 +4052,6 @@
|
||||
"settings.account.security.email.title": {
|
||||
"message": "電子郵件"
|
||||
},
|
||||
"settings.account.security.passkey.add": {
|
||||
"message": "新增通行金鑰"
|
||||
},
|
||||
"settings.account.security.passkey.add-modal.name.description": {
|
||||
"message": "請務必選擇容易記住的名稱,以便日後能辨識出這個通行金鑰。"
|
||||
},
|
||||
|
||||
@@ -246,7 +246,9 @@ const { data: allReports } = await useLazyAsyncData('new-moderation-reports', as
|
||||
const enrichmentPromise = enrichReportBatch(reports)
|
||||
enrichmentPromises.push(enrichmentPromise)
|
||||
|
||||
currentOffset += reports.length
|
||||
// this is explicitly not the length of the reports array, because the API may return fewer reports due to a report in the middle not being
|
||||
// serializable if the offset is set to the reports array you can get the same report from the end multiple times.
|
||||
currentOffset += REPORT_ENDPOINT_COUNT
|
||||
|
||||
if (enrichmentPromises.length >= 3) {
|
||||
const completed = await Promise.all(enrichmentPromises.splice(0, 2))
|
||||
|
||||
@@ -101,6 +101,9 @@ impl CacheValueType {
|
||||
// ModpackFiles never expire - version_id is immutable so hashes never change
|
||||
// TODO: There has to be a way to exclude this from the "Purge cache" stuff?
|
||||
CacheValueType::ModpackFiles => 100 * 365 * 24 * 60 * 60, // 100 years (effectively never)
|
||||
CacheValueType::SearchResults | CacheValueType::SearchResultsV3 => {
|
||||
10 * 60 // 10 minutes
|
||||
}
|
||||
_ => 30 * 60, // 30 minutes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,22 +11,34 @@ export type VersionEntry = {
|
||||
|
||||
const VERSIONS: VersionEntry[] = [
|
||||
{
|
||||
date: `2026-07-08T22:58:25+00:00`,
|
||||
product: 'web',
|
||||
body: `## Changed
|
||||
- Revised design for the project download modal.`,
|
||||
},
|
||||
{
|
||||
date: `2026-07-08T15:05:35+00:00`,
|
||||
date: `2026-07-10T17:38:17+00:00`,
|
||||
product: 'app',
|
||||
version: '0.15.9',
|
||||
version: '0.15.10',
|
||||
body: `## Added
|
||||
- Added new advanced filter category to Discover content.
|
||||
- Added options to exclude plugins and data packs from mod search
|
||||
- Added options to exclude mods and plugins from data pack search
|
||||
|
||||
## Fixed
|
||||
- Instance edits not appearing to be immediately saved.`,
|
||||
- Instance edits not appearing to be immediately saved.
|
||||
- Fixed search cache being way longer than intended. Search results should now be cached for no more than 10 minutes, making it more possible to find newer or more recently updated content.
|
||||
- Fixed it being impossible to respond to the ad consent banner.
|
||||
- Improved installation issues
|
||||
- Added better error handling, with a "Copy details" button on the frontend for the failure notifications + shows up after 30 seconds of no recorded progress - so support can investigate failures and pass it on to us
|
||||
- Changed "preparing instance" stage -> "Queued to install" - now handles maximum three install jobs at a time to prevent install jobs from being bricked.
|
||||
- Improves download tracking by reporting download progress live rather than after each modpack content file is downloaded`,
|
||||
},
|
||||
{
|
||||
date: `2026-07-10T17:38:17+00:00`,
|
||||
product: 'web',
|
||||
body: `## Fixed
|
||||
- Fixed the empty state of the download modal when an unsupported combination of loaders and versions are selected.`,
|
||||
},
|
||||
{
|
||||
date: `2026-07-08T22:58:25+00:00`,
|
||||
product: 'web',
|
||||
body: `## Changed
|
||||
- Revised design for the project download modal.`,
|
||||
},
|
||||
{
|
||||
date: `2026-07-08T15:05:35+00:00`,
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import type { QuickReply } from '../types/quick-reply'
|
||||
|
||||
export default [
|
||||
{
|
||||
label: '✅ Corrections Applied',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/externals-permissions/corrections-applied.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🚫 Bad Proofs',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/externals-permissions/illegitimate-evidence.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '⛔ Inaccessible Proofs',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/externals-permissions/inaccessible-evidence.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🌐 Not Permission to Distribute',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/externals-permissions/but-its-online.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🍴 Forks',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/externals-permissions/forks.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '💲 Premium Content',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/externals-permissions/premium-content.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '⚖️ Prohibited Content',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/externals-permissions/prohibited-content.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🧑💻 Cheats and Hacks',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/externals-permissions/cheats-and-hacks.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
] as ReadonlyArray<QuickReply>
|
||||
@@ -1,51 +1,39 @@
|
||||
import type { KeybindListener } from '../types/keybinds'
|
||||
|
||||
const keybinds: KeybindListener[] = [
|
||||
{
|
||||
id: 'next-stage',
|
||||
const keybinds: { [id: string]: KeybindListener } = {
|
||||
'next-stage': {
|
||||
keybind: 'ArrowRight',
|
||||
description: 'Go to next stage',
|
||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||
action: (ctx) => ctx.actions.tryGoNext(),
|
||||
},
|
||||
{
|
||||
id: 'previous-stage',
|
||||
'previous-stage': {
|
||||
keybind: 'ArrowLeft',
|
||||
description: 'Go to previous stage',
|
||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||
action: (ctx) => ctx.actions.tryGoBack(),
|
||||
},
|
||||
{
|
||||
id: 'generate-message',
|
||||
'generate-message': {
|
||||
keybind: 'Ctrl+Shift+E',
|
||||
description: 'Generate moderation message',
|
||||
action: (ctx) => ctx.actions.tryGenerateMessage(),
|
||||
},
|
||||
{
|
||||
id: 'toggle-collapse',
|
||||
'toggle-collapse': {
|
||||
keybind: 'Shift+C',
|
||||
description: 'Toggle collapse/expand',
|
||||
action: (ctx) => ctx.actions.tryToggleCollapse(),
|
||||
},
|
||||
{
|
||||
id: 'reset-progress',
|
||||
'reset-progress': {
|
||||
keybind: 'Ctrl+Shift+R',
|
||||
description: 'Reset moderation progress',
|
||||
action: (ctx) => ctx.actions.tryResetProgress(),
|
||||
},
|
||||
{
|
||||
id: 'reset-progress-alt',
|
||||
keybind: 'Alt+R',
|
||||
description: 'Reset moderation progress',
|
||||
action: (ctx) => ctx.actions.tryResetProgress(),
|
||||
},
|
||||
{
|
||||
id: 'skip-project',
|
||||
'skip-project': {
|
||||
keybind: 'Ctrl+Shift+S',
|
||||
description: 'Skip to next project',
|
||||
enabled: (ctx) => ctx.state.futureProjectCount > 0 && !ctx.state.isDone,
|
||||
action: (ctx) => ctx.actions.trySkipProject(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default keybinds
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
## External Content Contains Commercial Restrictions
|
||||
|
||||
Some external content in your Modpack was found to be licensed under non-commercial terms.</br>
|
||||
To ensure your Modpack complies with these licensing requirements, you must [opt-out of project monetization](%PROJECT_MONETIZATION_SETTINGS_LINK%), or provide proof of your explicit permission to include this content in your monetized Modpack.</br>
|
||||
Keep in mind, if your project is approved with monetization disabled, you will not be able to re-enable it.
|
||||
|
||||
Please view your project's %PROJECT_PERMISSIONS_FLINK% for a list of content with non-commercial licensing requirements.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
## Project Monetization
|
||||
|
||||
It looks like you've set this project to [opt-out of monetization](%PROJECT_MONETIZATION_SETTINGS_LINK%) as required by the licensing of some included external content.
|
||||
|
||||
So that your project can be Approved, you will not be able to re-enable monetization.</br>
|
||||
Please view your project's %PROJECT_PERMISSIONS_FLINK% for a list of content with non-commercial licensing requirements.
|
||||
|
||||
If you think this is a mistake, %SUPPORT%
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
## Project Monetization
|
||||
|
||||
It looks like you've set this project to [opt-out of monetization](%PROJECT_MONETIZATION_SETTINGS_LINK%) in order to comply with the licensing requirements of included content.
|
||||
|
||||
So that your project can be Approved, you will not be able to re-enable monetization.
|
||||
|
||||
If you think this is a mistake, %SUPPORT%
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
The ARR license of this content does _not_ permit redistribution of files.
|
||||
|
||||
Listing content as ARR or "no license" indicates that the author intends to reserve **all** rights to their content, including the sole right to redistribution, unless otherwise specified.
|
||||
|
||||
If the author has indicated that this content may be distributed in Modpacks, please provide proof of this exception.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
This project's license contains a non-commercial clause.
|
||||
|
||||
This may prohibit your ability to monetize this Modpack if included.
|
||||
@@ -2,5 +2,5 @@ Unfortunately, the Moderation team is unable to assist with your issue.
|
||||
|
||||
The reporting system is exclusively for reporting issues to Moderation staff; only violations of [Modrinth's Content Rules](https://modrinth.com/legal/rules) should be reported. The members of the project you're reporting do not see that you have submitted a report.
|
||||
|
||||
If you are having issues with crashes, please check out our helpful guide to [Debugging Minecraft Crashes](https://support.modrinth.com/aen/articles/8792916) to learn how to diagnose and fix crashes.
|
||||
If you are having issues with crashes, please check out our helpful guide to [Debugging Minecraft Crashes](https://support.modrinth.com/aen/articles/8792916) to learn how to diagnose and fix crashes.</br>
|
||||
For other project-specific issues consider asking the project's own community, check for a Discord or Issues link on the project page.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Thank you for your report, we understand your concerns.
|
||||
|
||||
Currently, Modrinth does not prohibit "%TERM%" style mods or consider these to de facto violate section 3 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) due to their considerable legitimate use-cases.
|
||||
Currently, Modrinth does not prohibit "%TERM%" style mods or consider these to de facto violate section 3 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) due to their considerable legitimate use-cases.</br>
|
||||
If you have reason to believe a project on Modrinth is intentionally enabling or promoting cheating, we ask that you create a new report and provide sufficient evidence.
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
If you have reason to believe that a project on Modrinth is reuploading code or assets without permission, or otherwise violating section 4 of [Modrinth's Content Rules](https://modrinth.com/legal/rules), we ask that you create a new report and provide the necessary details for our Moderation team to take any applicable actions.
|
||||
|
||||
We ask that you provide examples of identical or significantly derived code or assets likely to have been copied from an original work.
|
||||
We ask that you provide examples of identical or significantly derived code or assets likely to have been copied from an original work.</br>
|
||||
Be sure to include:
|
||||
|
||||
- A link to the original work and its licensing, if available.
|
||||
- The relative file path of the identical class(es) or asset(s) for the infringing and source work.
|
||||
- Explanation of a reasonable belief that this content is copied from the provided source work, and not likely to have mutually originated or derived from another source.
|
||||
|
||||
Keep in mind that our Moderation team only enforces [Modrinth's Content Rules](https://modrinth.com/legal/rules) and can not arbitrate copyright disputes.
|
||||
Keep in mind that our Moderation team only enforces [Modrinth's Content Rules](https://modrinth.com/legal/rules) and can not arbitrate copyright disputes.</br>
|
||||
For more information, please read our [Copyright Policy](https://modrinth.com/legal/copyright).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Thank you for your report, we understand your concerns.
|
||||
|
||||
At this time, Modrinth does not prohibit the use of any specific tool, including AI tools, in the creation of project pages or content on Modrinth.
|
||||
At this time, Modrinth does not prohibit the use of any specific tool, including AI tools, in the creation of project pages or content on Modrinth.</br>
|
||||
If you have reason to believe that this project does not or cannot function as advertised, may be intentionally misleading, or otherwise violates [Modrinth's Content Rules](https://modrinth.com/legal/rules) please create a new report with the relevant evidence.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
## Indefinitely Rejected
|
||||
|
||||
A project you uploaded has been found to contain or distribute malicious files, this is strictly prohibited and a violation of [Modrinth's Terms of Use](https://modrinth.com/legal/terms).
|
||||
Our Moderation team has determined this project, and all projects associated with your account should be rejected indefinitely.
|
||||
A project you uploaded has been found to contain or distribute malicious files, this is strictly prohibited and a violation of [Modrinth's Terms of Use](https://modrinth.com/legal/terms).</br>
|
||||
Our Moderation team has determined this project, and all projects associated with your account should be rejected indefinitely.</br>
|
||||
We believe this is the best course of action at this time and ask that you **do not resubmit this project**.
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
## Description Clarity
|
||||
|
||||
Per section 2 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) It's important that your Description accurately and honestly represents the content of your project.
|
||||
Currently, some elements in your Description may be confusing or misleading.
|
||||
Please edit your description to ensure it accurately represents the current functionality of your project.
|
||||
Avoid making hyperbolic claims that could misrepresent the facts of your project.
|
||||
Per section 2 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) It's important that your Description accurately and honestly represents the content of your project.</br>
|
||||
Currently, some elements in your Description may be confusing or misleading.</br>
|
||||
Please edit your description to ensure it accurately represents the current functionality of your project.</br>
|
||||
Avoid making hyperbolic claims that could misrepresent the facts of your project.</br>
|
||||
Ensure that your Description is accurate and not likely to confuse users.
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { QuickReply } from '../../types/quick-reply'
|
||||
|
||||
export default [
|
||||
{
|
||||
label: '✅ Corrections Applied',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/externals-permissions/corrections-applied.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🚫 Bad Proofs',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/externals-permissions/illegitimate-evidence.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '⛔ Inaccessible Proofs',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/externals-permissions/inaccessible-evidence.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🌐 Not Permission to Distribute',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/externals-permissions/but-its-online.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '📝 ARR License',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/externals-permissions/arr-license.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '💰 Non-Commercial License',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/externals-permissions/nc-license.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🍴 Forks',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/externals-permissions/forks.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '💲 Premium Content',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/externals-permissions/premium-content.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '⚖️ Prohibited Content',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/externals-permissions/prohibited-content.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🧑💻 Cheats and Hacks',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/externals-permissions/cheats-and-hacks.md?raw'))
|
||||
.default,
|
||||
private: false,
|
||||
},
|
||||
] as ReadonlyArray<QuickReply>
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { QuickReply } from '../../types/quick-reply'
|
||||
import type { ExtendedReport } from '../../types/reports'
|
||||
|
||||
export default [
|
||||
{
|
||||
label: '✅⚖️ Action Taken',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/reports/action-taken.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '✅⚠️ Author Alerted',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/reports/author-alerted.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '✅🏷️ Metadata Corrected',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/reports/metadata-corrected.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🏷️ Metadata Insufficient',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/reports/metadata-insufficient.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌📤 Reupload Insufficient',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/reports/reupload-insufficient.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🏳️🌈 Spam',
|
||||
message: async () => (await import('../messages/quick-replies/reports/spam.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🛡️ Antivirus',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/reports/antivirus.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌👻 Functionality Disclosed',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/reports/functionality-disclosed.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🤖 Suspected AI',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/reports/suspected-ai.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌👩💻 Not a Hack',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/reports/not-a-hack.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🕹️ Gameplay Issue',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/reports/gameplay-issue.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🎧 Platform Issue',
|
||||
message: async () =>
|
||||
(await import('../messages/quick-replies/reports/platform-issue.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🪦 Stale',
|
||||
message: async () => (await import('../messages/quick-replies/reports/stale.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
] as ReadonlyArray<QuickReply<ExtendedReport>>
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
|
||||
import type { QuickReply } from '../types/quick-reply'
|
||||
import type { QuickReply } from '../../types/quick-reply'
|
||||
|
||||
export interface TechReviewContext {
|
||||
project: Labrinth.Projects.v3.Project
|
||||
@@ -12,31 +12,31 @@ export default [
|
||||
{
|
||||
label: '⚠️ Unclear/Misleading',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/tech-review/unclear-misleading.md?raw')).default,
|
||||
(await import('../messages/quick-replies/tech-review/unclear-misleading.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '📝 Request Source',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/tech-review/request-source.md?raw')).default,
|
||||
(await import('../messages/quick-replies/tech-review/request-source.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🔒 Request Source (Obf)',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/tech-review/request-source-obf.md?raw')).default,
|
||||
(await import('../messages/quick-replies/tech-review/request-source-obf.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '📦 Request Source (Bin)',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/tech-review/request-source-bin.md?raw')).default,
|
||||
(await import('../messages/quick-replies/tech-review/request-source-bin.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🚫 Malware',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/tech-review/malware.md?raw')).default,
|
||||
(await import('../messages/quick-replies/tech-review/malware.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
] as ReadonlyArray<QuickReply<TechReviewContext>>
|
||||
@@ -1,81 +0,0 @@
|
||||
import type { QuickReply } from '../types/quick-reply'
|
||||
import type { ExtendedReport } from '../types/reports'
|
||||
|
||||
export default [
|
||||
{
|
||||
label: '✅⚖️ Action Taken',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/reports/action-taken.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '✅⚠️ Author Alerted',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/reports/author-alerted.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '✅🏷️ Metadata Corrected',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/reports/metadata-corrected.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🏷️ Metadata Insufficient',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/reports/metadata-insufficient.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌📤 Reupload Insufficient',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/reports/reupload-insufficient.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🏳️🌈 Spam',
|
||||
message: async () => (await import('./messages/quick-replies/reports/spam.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🛡️ Antivirus',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/reports/antivirus.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌👻 Functionality Disclosed',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/reports/functionality-disclosed.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🤖 Suspected AI',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/reports/suspected-ai.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌👩💻 Not a Hack',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/reports/not-a-hack.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🕹️ Gameplay Issue',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/reports/gameplay-issue.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🎧 Platform Issue',
|
||||
message: async () =>
|
||||
(await import('./messages/quick-replies/reports/platform-issue.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '❌🪦 Stale',
|
||||
message: async () => (await import('./messages/quick-replies/reports/stale.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
] as ReadonlyArray<QuickReply<ExtendedReport>>
|
||||
@@ -48,6 +48,21 @@ const permissions: Stage = {
|
||||
(await import('../messages/checklist-messages/externals-permissions/missing.md?raw'))
|
||||
.default,
|
||||
},
|
||||
{
|
||||
id: 'non-commercial-external-content',
|
||||
type: 'button',
|
||||
label: 'Non-commercial externals',
|
||||
weight: 2003,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
shouldShow: (project, projectV3) =>
|
||||
projectV3.project_types?.includes('modpack') &&
|
||||
projectV3.monetization_status === 'monetized' &&
|
||||
!projectV3?.minecraft_server,
|
||||
message: async () =>
|
||||
(await import('../messages/checklist-messages/externals-permissions/non-commercial.md?raw'))
|
||||
.default,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@ const reupload: Stage = {
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
} as ToggleAction,
|
||||
},
|
||||
],
|
||||
|
||||
disablesActions: [
|
||||
|
||||
@@ -22,7 +22,7 @@ const statusAlerts: Stage = {
|
||||
shouldShow: (project) => project.status !== 'approved',
|
||||
message: async () =>
|
||||
(await import('../messages/checklist-messages/status-alerts/fixed.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
},
|
||||
{
|
||||
id: 'status_corrections_applied-approved',
|
||||
type: 'button',
|
||||
@@ -34,7 +34,7 @@ const statusAlerts: Stage = {
|
||||
message: async () =>
|
||||
(await import('../messages/checklist-messages/status-alerts/fixed-approved.md?raw'))
|
||||
.default,
|
||||
} as ButtonAction,
|
||||
},
|
||||
{
|
||||
id: 'status_private_use',
|
||||
type: 'button',
|
||||
@@ -46,7 +46,7 @@ const statusAlerts: Stage = {
|
||||
message: async () =>
|
||||
(await import('../messages/checklist-messages/status-alerts/private/private.md?raw'))
|
||||
.default,
|
||||
} as ButtonAction,
|
||||
},
|
||||
{
|
||||
id: 'status_private_use-server',
|
||||
type: 'button',
|
||||
@@ -58,7 +58,7 @@ const statusAlerts: Stage = {
|
||||
message: async () =>
|
||||
(await import('../messages/checklist-messages/status-alerts/private/private-server.md?raw'))
|
||||
.default,
|
||||
} as ButtonAction,
|
||||
},
|
||||
{
|
||||
id: 'status_server_use',
|
||||
type: 'button',
|
||||
@@ -68,7 +68,7 @@ const statusAlerts: Stage = {
|
||||
project.project_type === 'modpack' && !projectV3?.minecraft_server,
|
||||
message: async () =>
|
||||
(await import('../messages/checklist-messages/status-alerts/serverpack.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
},
|
||||
{
|
||||
id: 'status_account_issues',
|
||||
type: 'button',
|
||||
@@ -79,7 +79,7 @@ const statusAlerts: Stage = {
|
||||
message: async () =>
|
||||
(await import('../messages/checklist-messages/status-alerts/account_issues.md?raw'))
|
||||
.default,
|
||||
} as ButtonAction,
|
||||
},
|
||||
{
|
||||
id: 'status_automod_confusion',
|
||||
type: 'button',
|
||||
@@ -89,7 +89,35 @@ const statusAlerts: Stage = {
|
||||
message: async () =>
|
||||
(await import('../messages/checklist-messages/status-alerts/automod_confusion.md?raw'))
|
||||
.default,
|
||||
} as ButtonAction,
|
||||
},
|
||||
{
|
||||
id: 'status_demonetized',
|
||||
type: 'button',
|
||||
label: `Demonetized`,
|
||||
weight: -999999,
|
||||
shouldShow: (project, projectV3) =>
|
||||
projectV3.monetization_status === 'force-demonetized' &&
|
||||
!projectV3.project_types?.includes('modpack') &&
|
||||
!projectV3?.minecraft_server,
|
||||
message: async () =>
|
||||
(
|
||||
await import('../messages/checklist-messages/status-alerts/demonetized/demonetized.md?raw')
|
||||
).default,
|
||||
},
|
||||
{
|
||||
id: 'status_demonetized_modpack',
|
||||
type: 'button',
|
||||
label: `Demonetized`,
|
||||
weight: -999999,
|
||||
shouldShow: (project, projectV3) =>
|
||||
projectV3.monetization_status === 'force-demonetized' &&
|
||||
projectV3.project_types?.includes('modpack') &&
|
||||
!projectV3?.minecraft_server,
|
||||
message: async () =>
|
||||
(
|
||||
await import('../messages/checklist-messages/status-alerts/demonetized/demonetized-modpack.md?raw')
|
||||
).default,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ export { default as keybinds } from './data/keybinds'
|
||||
export { finalPermissionMessages } from './data/modpack-permissions-stage'
|
||||
export { default as nags } from './data/nags'
|
||||
export * from './data/nags/index'
|
||||
export { default as reportQuickReplies } from './data/report-quick-replies'
|
||||
export { default as reportQuickReplies } from './data/quick-replies/report-quick-replies'
|
||||
export {
|
||||
type TechReviewContext,
|
||||
default as techReviewQuickReplies,
|
||||
} from './data/tech-review-quick-replies'
|
||||
export { default as attributionQuickReplies } from './data/attribution-quick-replies'
|
||||
} from './data/quick-replies/tech-review-quick-replies'
|
||||
export { default as attributionQuickReplies } from './data/quick-replies/attribution-quick-replies'
|
||||
export * from './locales'
|
||||
export * from './types/actions'
|
||||
export * from './types/keybinds'
|
||||
|
||||
@@ -59,7 +59,6 @@ export interface KeybindDefinition {
|
||||
}
|
||||
|
||||
export interface KeybindListener {
|
||||
id: string
|
||||
keybind: KeybindDefinition | KeybindDefinition[] | string | string[]
|
||||
description: string
|
||||
enabled?: (ctx: ModerationContext) => boolean
|
||||
@@ -94,6 +93,17 @@ export function matchesKeybind(event: KeyboardEvent, keybind: KeybindDefinition
|
||||
)
|
||||
}
|
||||
|
||||
export function toKeybindDefinition(event: KeyboardEvent): KeybindDefinition {
|
||||
return {
|
||||
key: event.key.toLowerCase(),
|
||||
ctrl: event.ctrlKey,
|
||||
shift: event.shiftKey,
|
||||
alt: event.altKey,
|
||||
meta: event.metaKey,
|
||||
preventDefault: true,
|
||||
}
|
||||
}
|
||||
|
||||
export function handleKeybind(
|
||||
event: KeyboardEvent,
|
||||
ctx: ModerationContext,
|
||||
@@ -104,7 +114,8 @@ export function handleKeybind(
|
||||
event.target instanceof HTMLTextAreaElement ||
|
||||
(event.target as HTMLElement)?.closest('.cm-editor') ||
|
||||
(event.target as HTMLElement)?.classList?.contains('cm-content') ||
|
||||
(event.target as HTMLElement)?.classList?.contains('cm-line')
|
||||
(event.target as HTMLElement)?.classList?.contains('cm-line') ||
|
||||
document.getElementById('moderation-checklist-keybinds-modal')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -385,7 +385,10 @@ export function flattenProjectVariables(
|
||||
vars[`PROJECT_PERMISSIONS_LINK`] =
|
||||
`https://modrinth.com/project/${project.id}/settings/permissions`
|
||||
vars[`PROJECT_PERMISSIONS_FLINK`] =
|
||||
`[Permissions dashboard](https://modrinth.com/project/${project.id}/settings/permissions)`
|
||||
`[Permissions settings](https://modrinth.com/project/${project.id}/settings/permissions)`
|
||||
vars[`PROJECT_MONETIZATION_SETTINGS_LINK`] = `https://modrinth.com/project/${project.id}/settings`
|
||||
vars[`PROJECT_MONETIZATION_SETTINGS_FLINK`] =
|
||||
`[Monetization settings](https://modrinth.com/project/${project.id}/settings)`
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@
|
||||
"defaultMessage": "Re-install modpack"
|
||||
},
|
||||
"button.remove": {
|
||||
"defaultMessage": "Delete passkey"
|
||||
"defaultMessage": "Remove"
|
||||
},
|
||||
"button.remove-image": {
|
||||
"defaultMessage": "Remove image"
|
||||
|
||||
@@ -249,7 +249,7 @@ export const commonMessages = defineMessages({
|
||||
},
|
||||
removeButton: {
|
||||
id: 'button.remove',
|
||||
defaultMessage: 'Delete passkey',
|
||||
defaultMessage: 'Remove',
|
||||
},
|
||||
removeImageButton: {
|
||||
id: 'button.remove-image',
|
||||
|
||||
Reference in New Issue
Block a user