mirror of
https://github.com/modrinth/code.git
synced 2026-08-29 02:54:51 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7226c25af | ||
|
|
40f0e41d23 | ||
|
|
bf93e01114 | ||
|
|
105d8e7619 | ||
|
|
b81e4304b1 |
@@ -37,21 +37,22 @@
|
|||||||
>
|
>
|
||||||
{{ formatRelativeTime(report.created) }}
|
{{ formatRelativeTime(report.created) }}
|
||||||
</span>
|
</span>
|
||||||
<ButtonStyled circular>
|
<div class="flex items-center gap-2">
|
||||||
<OverflowMenu :options="quickActions">
|
<ButtonStyled circular>
|
||||||
<template #default>
|
<button v-tooltip="'Copy ID'" @click="copyId">
|
||||||
<EllipsisVerticalIcon class="size-4" />
|
|
||||||
</template>
|
|
||||||
<template #copy-id>
|
|
||||||
<ClipboardCopyIcon />
|
<ClipboardCopyIcon />
|
||||||
<span class="hidden sm:inline">Copy ID</span>
|
</button>
|
||||||
</template>
|
</ButtonStyled>
|
||||||
<template #copy-link>
|
<ButtonStyled circular>
|
||||||
<LinkIcon />
|
<a
|
||||||
<span class="hidden sm:inline">Copy link</span>
|
v-tooltip="'Open in new tab'"
|
||||||
</template>
|
:href="`/moderation/reports/${props.report.id}`"
|
||||||
</OverflowMenu>
|
target="_blank"
|
||||||
</ButtonStyled>
|
>
|
||||||
|
<ExternalIcon />
|
||||||
|
</a>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -183,12 +184,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import { CheckCircleIcon, ClipboardCopyIcon, ExternalIcon } from '@modrinth/assets'
|
||||||
CheckCircleIcon,
|
|
||||||
ClipboardCopyIcon,
|
|
||||||
EllipsisVerticalIcon,
|
|
||||||
LinkIcon,
|
|
||||||
} from '@modrinth/assets'
|
|
||||||
import { type ExtendedReport, reportQuickReplies } from '@modrinth/moderation'
|
import { type ExtendedReport, reportQuickReplies } from '@modrinth/moderation'
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
@@ -196,8 +192,6 @@ import {
|
|||||||
CollapsibleRegion,
|
CollapsibleRegion,
|
||||||
getProjectTypeIcon,
|
getProjectTypeIcon,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
OverflowMenu,
|
|
||||||
type OverflowMenuOption,
|
|
||||||
useFormatDateTime,
|
useFormatDateTime,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
} from '@modrinth/ui'
|
} 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(() => {
|
const reportItemAvatarUrl = computed(() => {
|
||||||
switch (props.report.item_type) {
|
switch (props.report.item_type) {
|
||||||
case 'project':
|
case 'project':
|
||||||
@@ -395,4 +360,14 @@ const formattedReportType = computed(() => {
|
|||||||
const words = reportType.includes('-') ? reportType.split('-') : reportType.split(' ')
|
const words = reportType.includes('-') ? reportType.split('-') : reportType.split(' ')
|
||||||
return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')
|
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>
|
</script>
|
||||||
|
|||||||
@@ -123,30 +123,36 @@ const projectStatusActions = computed<OverflowMenuOption[]>(() => [
|
|||||||
color: 'green',
|
color: 'green',
|
||||||
action: () => setStatus('approved'),
|
action: () => setStatus('approved'),
|
||||||
hoverFilled: true,
|
hoverFilled: true,
|
||||||
disabled: isProjectApproved.value || isLoadingStatusAction.value,
|
disabled: isStatusActionDisabled('approved'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'withhold',
|
id: 'withhold',
|
||||||
color: 'orange',
|
color: 'orange',
|
||||||
action: () => setStatus('withheld'),
|
action: () => setStatus('withheld'),
|
||||||
hoverFilled: true,
|
hoverFilled: true,
|
||||||
disabled: projectStatus.value === 'withheld' || isLoadingStatusAction.value,
|
disabled: isStatusActionDisabled('withheld'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'send-to-review',
|
id: 'send-to-review',
|
||||||
action: () => setStatus('processing'),
|
action: () => setStatus('processing'),
|
||||||
hoverFilled: true,
|
hoverFilled: true,
|
||||||
disabled: projectStatus.value === 'processing' || isLoadingStatusAction.value,
|
disabled: isStatusActionDisabled('processing'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'reject',
|
id: 'reject',
|
||||||
color: 'red',
|
color: 'red',
|
||||||
action: () => setStatus('rejected'),
|
action: () => setStatus('rejected'),
|
||||||
hoverFilled: true,
|
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) {
|
async function setStatus(status: Labrinth.Projects.v2.ProjectStatus) {
|
||||||
isLoadingStatusAction.value = true
|
isLoadingStatusAction.value = true
|
||||||
try {
|
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>
|
<template>
|
||||||
<NewModal ref="modal" header="Moderation shortcuts" :closable="true">
|
<NewModal ref="modal" header="Moderation shortcuts" :closable="true">
|
||||||
<div>
|
<div id="moderation-checklist-keybinds-modal">
|
||||||
<div class="keybinds-sections">
|
<div class="keybinds-sections">
|
||||||
<div class="grid grid-cols-2 gap-x-12 gap-y-3">
|
<div class="grid grid-cols-2 gap-x-12 gap-y-3">
|
||||||
<div
|
<div
|
||||||
v-for="keybind in keybinds"
|
v-for="[id, keybind] in Object.entries(keybinds)"
|
||||||
:key="keybind.id"
|
:key="id"
|
||||||
class="keybind-item flex items-center justify-between gap-4"
|
class="keybind-item flex flex-wrap items-center justify-between gap-4"
|
||||||
:class="{
|
: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>
|
<span class="text-sm text-secondary">{{ keybind.description }}</span>
|
||||||
<div class="flex items-center gap-1">
|
<ChecklistKeybind
|
||||||
<kbd
|
:definitions="
|
||||||
v-for="(key, index) in parseKeybindDisplay(keybind.keybind)"
|
(!Array.isArray(keybind.keybind) ? [keybind.keybind] : keybind.keybind).map(
|
||||||
:key="`${keybind.id}-key-${index}`"
|
normalizeKeybind,
|
||||||
class="keybind-key"
|
)
|
||||||
>
|
"
|
||||||
{{ key }}
|
:on-change="
|
||||||
</kbd>
|
(definitions) => {
|
||||||
</div>
|
keybinds[id].keybind = definitions
|
||||||
|
saveModerationKeybinds()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,43 +35,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 NewModal from '@modrinth/ui/src/components/modal/NewModal.vue'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import { saveModerationKeybinds } from '#imports'
|
||||||
|
import ChecklistKeybind from '~/components/ui/moderation/checklist/ChecklistKeybind.vue'
|
||||||
|
|
||||||
const modal = ref<InstanceType<typeof NewModal>>()
|
const modal = ref<InstanceType<typeof NewModal>>()
|
||||||
|
const keybinds = useModerationKeybinds()
|
||||||
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')
|
|
||||||
}
|
|
||||||
|
|
||||||
function show(event?: MouseEvent) {
|
function show(event?: MouseEvent) {
|
||||||
modal.value?.show(event)
|
modal.value?.show(event)
|
||||||
@@ -82,29 +60,6 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<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) {
|
@media (max-width: 768px) {
|
||||||
.keybinds-sections {
|
.keybinds-sections {
|
||||||
.grid {
|
.grid {
|
||||||
|
|||||||
@@ -478,7 +478,6 @@ import {
|
|||||||
handleKeybind,
|
handleKeybind,
|
||||||
initializeActionState,
|
initializeActionState,
|
||||||
kebabToTitleCase,
|
kebabToTitleCase,
|
||||||
keybinds,
|
|
||||||
type MultiSelectChipsAction,
|
type MultiSelectChipsAction,
|
||||||
processMessage,
|
processMessage,
|
||||||
type Stage,
|
type Stage,
|
||||||
@@ -533,6 +532,7 @@ import ModpackPermissionsFlow from './ModpackPermissionsFlow.vue'
|
|||||||
const notifications = injectNotificationManager()
|
const notifications = injectNotificationManager()
|
||||||
const { addNotification } = notifications
|
const { addNotification } = notifications
|
||||||
const debug = useDebugLogger('ModerationChecklist')
|
const debug = useDebugLogger('ModerationChecklist')
|
||||||
|
const keybinds = useModerationKeybinds()
|
||||||
|
|
||||||
const keybindsModal = ref<InstanceType<typeof KeybindsModal>>()
|
const keybindsModal = ref<InstanceType<typeof KeybindsModal>>()
|
||||||
const takeOverModal = ref<InstanceType<typeof ConfirmModal>>()
|
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
|
||||||
|
}
|
||||||
@@ -246,7 +246,9 @@ const { data: allReports } = await useLazyAsyncData('new-moderation-reports', as
|
|||||||
const enrichmentPromise = enrichReportBatch(reports)
|
const enrichmentPromise = enrichReportBatch(reports)
|
||||||
enrichmentPromises.push(enrichmentPromise)
|
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) {
|
if (enrichmentPromises.length >= 3) {
|
||||||
const completed = await Promise.all(enrichmentPromises.splice(0, 2))
|
const completed = await Promise.all(enrichmentPromises.splice(0, 2))
|
||||||
|
|||||||
@@ -1,51 +1,39 @@
|
|||||||
import type { KeybindListener } from '../types/keybinds'
|
import type { KeybindListener } from '../types/keybinds'
|
||||||
|
|
||||||
const keybinds: KeybindListener[] = [
|
const keybinds: { [id: string]: KeybindListener } = {
|
||||||
{
|
'next-stage': {
|
||||||
id: 'next-stage',
|
|
||||||
keybind: 'ArrowRight',
|
keybind: 'ArrowRight',
|
||||||
description: 'Go to next stage',
|
description: 'Go to next stage',
|
||||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||||
action: (ctx) => ctx.actions.tryGoNext(),
|
action: (ctx) => ctx.actions.tryGoNext(),
|
||||||
},
|
},
|
||||||
{
|
'previous-stage': {
|
||||||
id: 'previous-stage',
|
|
||||||
keybind: 'ArrowLeft',
|
keybind: 'ArrowLeft',
|
||||||
description: 'Go to previous stage',
|
description: 'Go to previous stage',
|
||||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||||
action: (ctx) => ctx.actions.tryGoBack(),
|
action: (ctx) => ctx.actions.tryGoBack(),
|
||||||
},
|
},
|
||||||
{
|
'generate-message': {
|
||||||
id: 'generate-message',
|
|
||||||
keybind: 'Ctrl+Shift+E',
|
keybind: 'Ctrl+Shift+E',
|
||||||
description: 'Generate moderation message',
|
description: 'Generate moderation message',
|
||||||
action: (ctx) => ctx.actions.tryGenerateMessage(),
|
action: (ctx) => ctx.actions.tryGenerateMessage(),
|
||||||
},
|
},
|
||||||
{
|
'toggle-collapse': {
|
||||||
id: 'toggle-collapse',
|
|
||||||
keybind: 'Shift+C',
|
keybind: 'Shift+C',
|
||||||
description: 'Toggle collapse/expand',
|
description: 'Toggle collapse/expand',
|
||||||
action: (ctx) => ctx.actions.tryToggleCollapse(),
|
action: (ctx) => ctx.actions.tryToggleCollapse(),
|
||||||
},
|
},
|
||||||
{
|
'reset-progress': {
|
||||||
id: 'reset-progress',
|
|
||||||
keybind: 'Ctrl+Shift+R',
|
keybind: 'Ctrl+Shift+R',
|
||||||
description: 'Reset moderation progress',
|
description: 'Reset moderation progress',
|
||||||
action: (ctx) => ctx.actions.tryResetProgress(),
|
action: (ctx) => ctx.actions.tryResetProgress(),
|
||||||
},
|
},
|
||||||
{
|
'skip-project': {
|
||||||
id: 'reset-progress-alt',
|
|
||||||
keybind: 'Alt+R',
|
|
||||||
description: 'Reset moderation progress',
|
|
||||||
action: (ctx) => ctx.actions.tryResetProgress(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'skip-project',
|
|
||||||
keybind: 'Ctrl+Shift+S',
|
keybind: 'Ctrl+Shift+S',
|
||||||
description: 'Skip to next project',
|
description: 'Skip to next project',
|
||||||
enabled: (ctx) => ctx.state.futureProjectCount > 0 && !ctx.state.isDone,
|
enabled: (ctx) => ctx.state.futureProjectCount > 0 && !ctx.state.isDone,
|
||||||
action: (ctx) => ctx.actions.trySkipProject(),
|
action: (ctx) => ctx.actions.trySkipProject(),
|
||||||
},
|
},
|
||||||
]
|
}
|
||||||
|
|
||||||
export default keybinds
|
export default keybinds
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ export interface KeybindDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface KeybindListener {
|
export interface KeybindListener {
|
||||||
id: string
|
|
||||||
keybind: KeybindDefinition | KeybindDefinition[] | string | string[]
|
keybind: KeybindDefinition | KeybindDefinition[] | string | string[]
|
||||||
description: string
|
description: string
|
||||||
enabled?: (ctx: ModerationContext) => boolean
|
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(
|
export function handleKeybind(
|
||||||
event: KeyboardEvent,
|
event: KeyboardEvent,
|
||||||
ctx: ModerationContext,
|
ctx: ModerationContext,
|
||||||
@@ -104,7 +114,8 @@ export function handleKeybind(
|
|||||||
event.target instanceof HTMLTextAreaElement ||
|
event.target instanceof HTMLTextAreaElement ||
|
||||||
(event.target as HTMLElement)?.closest('.cm-editor') ||
|
(event.target as HTMLElement)?.closest('.cm-editor') ||
|
||||||
(event.target as HTMLElement)?.classList?.contains('cm-content') ||
|
(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
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user