Feat: Many moderation checklist changes (#6730)

* Rule 1.x, 3.2 and 3.3x support

* reorganize

* Kinda Jank dynamic list stuff that should probably be implemented better but its 1:30 am

* Checklist Stage Title

* Make Stage Title Good

* Rule 1.x, 3.2 and 3.3x support

* reorganize

* Kinda Jank dynamic list stuff that should probably be implemented better but its 1:30 am

* Checklist Stage Title

* Make Stage Title Good

* oops i forgot 3.1

* Insufficient description improvement test

* "i forgor" checklist tooltips tooltips (kinda jank but they work)
update to latest

* only use indexedDb for moderation storage, localStorage forces a bunch of cringe

* store moderation stuff separately + compact checklist stuff

* give stages actual titles + rename what was previously called titles to hints

* Title & Slug, Summary Description, Links, License, Gallery, Versions and Reupload migrated to new system™️

* put stageoptions before nodes

* do state and variablse in tooltips

* checklist fully migrated to new system, not much new has been added (yet)

* forgot to only do modpack permissions for modpacks

* Idk why I wasn't just mutating the builder

* actual node type hierarchy + action abstraction + improve message/text handling

* dropdown + "functional" quick fixes

* dropdown + "functional" quick fixes

* Allow moderators to suggest correct environment.

* remove vestigial function

* Prioritization™️

* quickest fixes in the west

* start of metadata stage + optimize checklist parsing + a couple of qol features

* futureproof™️ environment metadata + just give me project context everywhere

* tidy up after today's refactoring

* tidy up after tidying up after today's refactoring

* move loaders to metadata (from versions)

* better required display + vueify + reorganize messages + tag quick fixes + todo

* fix log spam + concat md

* more bugs

* nested state access

* update description stage, add showcase clarity

* Fix links stage mostly, and tweak metadata stage.

* fix links lag + actions on stages

* setup links bonus buttons

* Change stage order

* Update rules stage

* Move conditional stages to the top where they belong.

* fix some metadata stuff

* fix stage selector

* don't show non-english buttons on servers without english support.

* prepr

* stop linter from trying to break my markdown

* fix: some issues

* fix: lint

* not sure why but link's links weren't links

* fix quick fixes (again) (idk how they keep breaking)

* make messages only actually count as messages if they actually have content

* rename horror button to spoilers button

* Update links name to match the links settings

* re-review stage

* unbreak stage selector

* passing off version stage fixing.

* actually fix the stage selector this time

* fix showcase clarity msg

* redirect when checklist updates slug
fix initial stage not being first if first stage is only shown under certain conditions
move to a consistent naming scheme

* redirect when checklist updates slug
fix initial stage not being first if first stage is only shown under certain conditions
move to a consistent naming scheme

* ok maybe lets use kebab

* move custom description advice stage to first button.

* temp priority on corrections applied

* make metadata page go to non setting versions page

* links stage bonus buttons, src required flag, show link value in msgs, fix msg groupings.

* update re-review button

* reupload stage navigates to project Description.

* fix: link stage using page context too late

* chore: run prepr

* fix(ci): tell typo check to ignore moderation utils

* fix: pr comment if storybook fails

* make complex nodes less complex (for the most part)
fix re-review stage shown condition
new fancy message collection system
all buttons that contribute to messages now have relevant tooltips
don't store default values in checklist db

* make complex nodes less complex (for the most part)
fix re-review stage shown condition
new fancy message collection system
all buttons that contribute to messages now have relevant tooltips
don't store default values in checklist db

* maybe fixed markdown escaping?

* tweak re-review wording.

* Update license stage handling and info

* add mixed option back for env info

* fix: formatEnvironments
fix: formatEnvironments

* update metadata environment group shown

* fix: status alert priority

* fix: display suggested slug in slug/misused message

* actually navigate when we change project slug

* ok like redirect but like don't lose state when slug changes :smart:

* make checklist reset button reset only current stage if there's modifications to it instead of full checklist

* fix versions stage messages not working

* fix donation links not being in a column

* I think slug stuff is working?
also like actually 100% garunteed markdown escaping works now

* prepr

* pseudo stages no longer interact with non-pseudo stages at all
also query projects better I think?
fix repetitive message spam in links stage

* apply quick fixes before refreshing page

* differentiate reset and return to start

* remove random empty lines in some situations

* better inaccurate slug message

* slug correction input reset button resets to current slug again instead of auto

* better keybind navigation

* incorrect environment message tweak

* use environment name(s) in checklist info

* prepr

---------

Signed-off-by: Prospector <6166773+Prospector@users.noreply.github.com>
Co-authored-by: chyzman <chyzalt@gmail.com>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
Co-authored-by: Gravy Boat <gravy@thatgravyboat.tech>
Co-authored-by: Michael H. <michael@iptables.sh>
Co-authored-by: chyz <32403637+chyzman@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
coolbot
2026-07-20 20:52:46 +00:00
committed by GitHub
co-authored by chyzman Calum H. Gravy Boat Michael H. chyz Prospector
parent 30e23e5cc2
commit e2085c43c5
205 changed files with 4689 additions and 3574 deletions
@@ -29,11 +29,12 @@
v-if="isOpen"
ref="menuRef"
data-pyro-telepopover-root
class="fixed isolate z-[9999] flex w-fit flex-col gap-2 overflow-hidden rounded-2xl border-[1px] border-solid border-surface-5 bg-bg-raised p-2 shadow-lg"
class="fixed isolate z-[9999] flex w-fit flex-col gap-2 overflow-x-hidden overflow-y-auto rounded-2xl border-[1px] border-solid border-surface-5 bg-bg-raised p-2 shadow-lg"
:style="menuStyle"
role="menu"
tabindex="-1"
@mousedown.stop
@wheel.stop
@mouseleave="handleMouseLeave"
>
<template
@@ -160,6 +161,7 @@ const props = withDefaults(
disabled?: boolean
tooltip?: string
ariaLabel?: string
placement?: 'right' | 'center'
}>(),
{
hoverable: false,
@@ -167,6 +169,7 @@ const props = withDefaults(
disabled: false,
tooltip: undefined,
ariaLabel: undefined,
placement: 'right',
},
)
@@ -189,41 +192,52 @@ const hoveringMenu = useElementHover(menuRef)
const hovering = computed(() => hoveringTrigger.value || hoveringMenu.value)
const menuStyle = ref({
top: '0px',
left: '0px',
const menuStyle = ref<Record<string, string>>({
top: '-9999px',
left: '-9999px',
})
const filteredOptions = computed(() => props.options.filter((option) => option.shown !== false))
const calculateMenuPosition = () => {
if (!triggerRef.value || !menuRef.value) return { top: '0px', left: '0px' }
if (!triggerRef.value || !menuRef.value) return null
const triggerRect = triggerRef.value.getBoundingClientRect()
// offsetWidth is not affected by CSS transforms (unlike getBoundingClientRect)
// scrollHeight gives the full content height regardless of any maxHeight constraint,
// preventing a feedback loop where clamped offsetHeight makes it look like the menu fits
const menuWidth = menuRef.value.offsetWidth
const menuHeight = menuRef.value.offsetHeight
const menuHeight = menuRef.value.scrollHeight
const margin = 8
let top: number
let left: number
let maxHeight: number | null = null
if (triggerRect.bottom + menuHeight + margin <= window.innerHeight) {
const spaceBelow = window.innerHeight - triggerRect.bottom - margin
const spaceAbove = triggerRect.top - margin
if (menuHeight <= spaceBelow) {
top = triggerRect.bottom + margin
} else if (triggerRect.top - menuHeight - margin >= 0) {
} else if (menuHeight <= spaceAbove) {
top = triggerRect.top - menuHeight - margin
} else if (spaceBelow >= spaceAbove) {
top = triggerRect.bottom + margin
maxHeight = spaceBelow
} else {
top = Math.max(margin, window.innerHeight - menuHeight - margin)
maxHeight = spaceAbove
top = triggerRect.top - maxHeight - margin
}
if (triggerRect.right - menuWidth >= margin) {
left = triggerRect.right - menuWidth
} else {
left = Math.max(margin, triggerRect.left)
}
const preferredLeft =
props.placement === 'center'
? triggerRect.left + triggerRect.width / 2 - menuWidth / 2
: triggerRect.right - menuWidth
const left = Math.max(margin, Math.min(preferredLeft, window.innerWidth - menuWidth - margin))
return {
top: `${top}px`,
left: `${left}px`,
...(maxHeight !== null ? { maxHeight: `${maxHeight}px` } : {}),
}
}
@@ -241,20 +255,24 @@ const toggleMenu = (event: MouseEvent) => {
const openMenu = () => {
if (props.disabled) return
menuStyle.value = { top: '-9999px', left: '-9999px' }
isOpen.value = true
emit('open')
disableBodyScroll()
nextTick(() => {
menuStyle.value = calculateMenuPosition()
document.addEventListener('mousemove', handleMouseMove)
focusFirstMenuItem()
})
// nextTick lets Vue render the element, then requestAnimationFrame waits for the
// browser to complete layout so offsetWidth/offsetHeight are real values.
nextTick(() =>
requestAnimationFrame(() => {
const pos = calculateMenuPosition()
if (pos) menuStyle.value = pos
document.addEventListener('mousemove', handleMouseMove)
focusFirstMenuItem()
}),
)
}
const closeMenu = () => {
isOpen.value = false
selectedIndex.value = -1
enableBodyScroll()
document.removeEventListener('mousemove', handleMouseMove)
}
@@ -324,14 +342,6 @@ const handleMouseOver = (index: number) => {
menuItemsRef.value[selectedIndex.value]?.focus?.()
}
const disableBodyScroll = () => {
document.body.style.overflow = 'hidden'
}
const enableBodyScroll = () => {
document.body.style.overflow = ''
}
const focusFirstMenuItem = () => {
if (menuItemsRef.value.length > 0) {
menuItemsRef.value[0]?.focus?.()
@@ -421,10 +431,25 @@ const handleKeydown = (event: KeyboardEvent) => {
}
}
const handleResizeOrScroll = () => {
if (isOpen.value) {
menuStyle.value = calculateMenuPosition()
const handleResize = () => {
if (!isOpen.value) return
const pos = calculateMenuPosition()
if (pos) menuStyle.value = pos
}
const handleScroll = () => {
if (!isOpen.value) return
const pos = calculateMenuPosition()
if (!pos) return
// On scroll only the vertical position changes — don't update left, which would shift
// the menu horizontally due to scrollbar appearing/disappearing changing offsetWidth
const updated: Record<string, string> = { ...menuStyle.value, top: pos.top }
if (pos.maxHeight) {
updated.maxHeight = pos.maxHeight
} else {
delete updated.maxHeight
}
menuStyle.value = updated
}
const throttle = <T extends unknown[]>(
@@ -441,23 +466,23 @@ const throttle = <T extends unknown[]>(
}
}
const throttledHandleResizeOrScroll = throttle(handleResizeOrScroll, 100)
const throttledHandleResize = throttle(handleResize, 100)
const throttledHandleScroll = throttle(handleScroll, 100)
onMounted(() => {
triggerRef.value?.addEventListener('keydown', handleKeydown)
window.addEventListener('resize', throttledHandleResizeOrScroll)
window.addEventListener('scroll', throttledHandleResizeOrScroll)
window.addEventListener('resize', throttledHandleResize)
window.addEventListener('scroll', throttledHandleScroll)
})
onUnmounted(() => {
triggerRef.value?.removeEventListener('keydown', handleKeydown)
window.removeEventListener('resize', throttledHandleResizeOrScroll)
window.removeEventListener('scroll', throttledHandleResizeOrScroll)
window.removeEventListener('resize', throttledHandleResize)
window.removeEventListener('scroll', throttledHandleScroll)
document.removeEventListener('mousemove', handleMouseMove)
if (typeAheadTimeout.value) {
clearTimeout(typeAheadTimeout.value)
}
enableBodyScroll()
})
watch(isOpen, (newValue) => {