@@ -87,41 +90,41 @@
>
- {{ formatMessage(messages.customTitle) }}
+ Title
*
- {{ formatMessage(messages.customPriority) }}
+ Priority
- {{ formatMessage(messages.actions) }}
+ Actions
@@ -135,7 +138,7 @@
-
{{ formatMessage(messages.issueMessage) }}
+
Message
@@ -192,18 +193,12 @@
:color="editingMessage ? 'brand' : undefined"
:circular="!editingMessage"
:icon-only="!editingMessage"
- :aria-label="
- formatMessage(
- editingMessage ? commonMessages.doneLabel : commonMessages.editButton,
- )
- "
+ :aria-label="editingMessage ? 'Done' : 'Edit'"
:disabled="pending || disabled || generating"
:class="editingMessage ? '!h-7' : ''"
@click="editingMessage = !editingMessage"
>
- {{
- formatMessage(commonMessages.doneLabel)
- }}
+ Done
@@ -220,7 +215,7 @@
size="xs"
type="quiet"
class="!h-auto !min-h-0 !rounded-full !bg-surface-3 !px-2.5 !py-1 !text-xs !font-medium hover:!bg-surface-4"
- :aria-label="formatMessage(messages.openPanel, { panel: binding.panel.title })"
+ :aria-label="`Open ${binding.panel.title} review panel`"
@click="revealPanel(binding.key)"
>
-
- {{ formatMessage(messages.customMessageRequired) }}
-
- {{ formatMessage(messages.chooseOption) }}
+ Choose at least one option for this issue.
@@ -265,17 +253,7 @@ import {
} from '@modrinth/assets'
import { moderationSettings } from '@modrinth/moderation'
import { IssuePriority } from '@modrinth/moderation/src/data/issues'
-import { issueTargetLabels } from '@modrinth/moderation/src/data/issues/component-builders/targets'
-import {
- Button,
- Combobox,
- commonMessages,
- defineMessages,
- Input,
- MultiSelect,
- Tooltip,
- useVIntl,
-} from '@modrinth/ui'
+import { Button, Combobox, Input, MultiSelect, Tooltip } from '@modrinth/ui'
import { computed, nextTick, ref, useId, watch } from 'vue'
import TemplateMarkdownEditor from '~/components/project-review/message-thread/template-markdown-editor.vue'
@@ -290,6 +268,7 @@ import {
} from '~/providers/project-review/review-panels'
import { injectReviewPreviousIssues } from '~/providers/project-review/review-previous-issues'
import { injectReviewSubmission } from '~/providers/project-review/review-submission'
+import { issueTargetLabels } from '~/providers/project-review/review-target-labels'
import { injectReviewContext } from '../review-panel/context'
import Controls from '../review-panel/controls/index.vue'
@@ -319,7 +298,7 @@ const previousIssue = computed(() => {
const issue = computed(() =>
'controls' in props.issue ? props.issue : previousIssues.cardIssue(props.issue),
)
-const issueTitle = computed(() => issue.value.title || formatMessage(messages.customIssue))
+const issueTitle = computed(() => issue.value.title || 'Custom issue')
const active = computed(() =>
panels.activeIssues.value.some((entry) => entry.id === issue.value.id),
)
@@ -374,79 +353,7 @@ watch(
if (disabled) editingMessage.value = false
},
)
-const { formatMessage } = useVIntl()
-const messages = defineMessages({
- customIssue: { id: 'project-review.issues.custom', defaultMessage: 'Custom issue' },
- customTitle: { id: 'project-review.issues.custom-title', defaultMessage: 'Title' },
- customTitlePlaceholder: {
- id: 'project-review.issues.custom-title-placeholder',
- defaultMessage: 'Enter title...',
- },
- issueMessage: { id: 'project-review.issues.issue-message', defaultMessage: 'Message' },
- customPriority: { id: 'project-review.issues.custom-priority', defaultMessage: 'Priority' },
- customPlaceholder: {
- id: 'project-review.issues.custom-placeholder',
- defaultMessage: 'Enter custom issue message...',
- },
- customMessageRequired: {
- id: 'project-review.issues.custom-message-required',
- defaultMessage: 'Enter a message for this custom issue.',
- },
- acknowledgeCheckbox: {
- id: 'project-review.issues.custom-acknowledge-checkbox',
- defaultMessage: 'Acknowledge checkbox',
- },
- acknowledgeReply: {
- id: 'project-review.issues.custom-acknowledge-reply',
- defaultMessage: 'Acknowledge reply',
- },
- searchFacets: {
- id: 'project-review.issues.custom-search-facets',
- defaultMessage: 'Search actions…',
- },
- actions: { id: 'project-review.issues.custom-actions', defaultMessage: 'Actions' },
- addFacets: { id: 'project-review.issues.custom-add-facets', defaultMessage: 'Mark as addressed' },
- notResolved: {
- id: 'project-review.issues.not-resolved',
- defaultMessage: 'Not resolved',
- },
- reReview: {
- id: 'project-review.previous-issues.re-review',
- defaultMessage: 'Re-review',
- },
- openPanel: {
- id: 'project-review.issues.open-panel',
- defaultMessage: 'Open {panel} review panel',
- },
- edited: {
- id: 'project-review.issues.edited',
- defaultMessage: '(Edited)',
- },
- editedTooltip: {
- id: 'project-review.issues.edited-tooltip',
- defaultMessage: 'Issue message has been edited',
- },
- remove: {
- id: 'project-review.issues.remove',
- defaultMessage: 'Remove {issue}',
- },
- add: {
- id: 'project-review.issues.add',
- defaultMessage: 'Add {issue}',
- },
- message: {
- id: 'project-review.issues.message',
- defaultMessage: 'Message for {issue}',
- },
- reset: {
- id: 'project-review.issues.reset-message',
- defaultMessage: 'Reset message',
- },
- chooseOption: {
- id: 'project-review.issues.choose-option',
- defaultMessage: 'Choose at least one option for this issue.',
- },
-})
+
const priorityOptions = Object.keys(IssuePriority).map((value) => ({
value,
label: value.charAt(0).toUpperCase() + value.slice(1).toLowerCase(),
@@ -457,13 +364,12 @@ const facetOptions = computed(() =>
.filter((type) => type !== 'modify_server_languages' || panels.hasServer.value)
.map((value) => ({
value,
- label: formatMessage(
+ label:
value === 'acknowledge_checkbox'
- ? messages.acknowledgeCheckbox
+ ? 'Acknowledge checkbox'
: value === 'acknowledge_reply'
- ? messages.acknowledgeReply
+ ? 'Acknowledge reply'
: issueTargetLabels[value as keyof typeof issueTargetLabels],
- ),
})),
)
const customTitleMissing = computed(() => !!issue.value.custom && !issue.value.custom.title.trim())
@@ -474,7 +380,7 @@ const facetLabels = computed(() => {
[]
return [...new Set(facets.map(({ what }) => what.type))]
.filter((type) => type !== 'mark_addressed')
- .map((type) => ({ type, label: formatMessage(issueTargetLabels[type]) }))
+ .map((type) => ({ type, label: issueTargetLabels[type] }))
})
const displayMessage = computed(() => {
if (props.disabled && props.previewMessage !== undefined) return props.previewMessage
@@ -494,6 +400,12 @@ const fields = computed(() => {
}
return [...bindings.values()]
})
+const missingRequiredFieldCount = computed(
+ () =>
+ panels.validationErrors.value.filter(
+ ({ issueId, key }) => issueId === issue.value.id && key !== 'toggle',
+ ).length,
+)
const needsToggle = computed(() =>
panels.validationErrors.value.some(
({ issueId, key }) => issueId === issue.value.id && key === 'toggle',
diff --git a/apps/frontend/src/components/project-review/issue-list/issue-picker.vue b/apps/frontend/src/components/project-review/issue-list/issue-picker.vue
index ac93989ab87..d54866c0c54 100644
--- a/apps/frontend/src/components/project-review/issue-list/issue-picker.vue
+++ b/apps/frontend/src/components/project-review/issue-list/issue-picker.vue
@@ -3,11 +3,12 @@
ref="picker"
:model-value="draft"
:options="options"
+ :max-tag-rows="3"
:disabled="pending"
:searchable="true"
- :search-placeholder="formatMessage(messages.search)"
- :no-options-message="formatMessage(messages.empty)"
- :no-results-message="formatMessage(messages.empty)"
+ search-placeholder="Search issues…"
+ no-options-message="No matching issues."
+ no-results-message="No matching issues."
:dropdown-width="288"
dropdown-align="right"
:max-height="440"
@@ -25,7 +26,7 @@
- {{ formatMessage(messages.add) }}
+ Issue
@@ -33,7 +34,7 @@
diff --git a/apps/frontend/src/components/project-review/layout/empty-popout.vue b/apps/frontend/src/components/project-review/layout/empty-popout.vue
index 7b5cd6ddef2..82117dea478 100644
--- a/apps/frontend/src/components/project-review/layout/empty-popout.vue
+++ b/apps/frontend/src/components/project-review/layout/empty-popout.vue
@@ -1,17 +1,13 @@
- {{ formatMessage(projectReviewMessages.emptyPopout) }}
+ No tabs open in this panel
diff --git a/apps/frontend/src/components/project-review/layout/index.client.vue b/apps/frontend/src/components/project-review/layout/index.client.vue
index 36a1c410d44..de13b833a4d 100644
--- a/apps/frontend/src/components/project-review/layout/index.client.vue
+++ b/apps/frontend/src/components/project-review/layout/index.client.vue
@@ -1,7 +1,7 @@
@@ -20,25 +20,28 @@
diff --git a/apps/frontend/src/components/project-review/link-info/index.vue b/apps/frontend/src/components/project-review/link-info/index.vue
index 7617e5c0c55..cfb38981e0d 100644
--- a/apps/frontend/src/components/project-review/link-info/index.vue
+++ b/apps/frontend/src/components/project-review/link-info/index.vue
@@ -2,7 +2,7 @@
- {{ match.platform }} · {{ formatMessage(messages[match.kind]) }}
+ {{ match.platform }} · {{ linkInfoLabels[match.kind] }}
- {{ formatMessage(message.message, message.values) }}
+ {{ message.text }}
- {{ formatMessage(messages[warning]) }}
+ {{ linkInfoLabels[warning] }}
@@ -40,9 +40,7 @@
{{ profile.title }}
-
{{
- formatMessage(messages[profile.label])
- }}
+
{{ linkInfoLabels[profile.label] }}
{{ profile.description }}
@@ -50,18 +48,22 @@
- {{ match.name }}
-
- {{ formatMessage(messages.loading) }}
+
+ {{ match.name }}
+ Loading link details…
- {{ formatMessage(notFound ? messages.notFound : messages.unavailable) }}
+ {{
+ notFound
+ ? 'This resource was not found. It may be private, deleted, or expired.'
+ : 'Additional details could not be loaded. The service may be unavailable, private, rate limited, or blocked by the browser.'
+ }}
- {{ formatMessage(messages.unsupported) }}
+ No additional public metadata is available for this link.
- {{ formatMessage(messages.externalLinks) }}
+ External links on this page
diff --git a/apps/frontend/src/components/project-review/review-panel/use-action-keybinds.ts b/apps/frontend/src/components/project-review/review-panel/use-action-keybinds.ts
index 11b299aba84..7d82c472973 100644
--- a/apps/frontend/src/components/project-review/review-panel/use-action-keybinds.ts
+++ b/apps/frontend/src/components/project-review/review-panel/use-action-keybinds.ts
@@ -1,30 +1,18 @@
import type { Ref } from 'vue'
+import { useModerationKeybinds } from '~/composables/moderation'
+
import type { createReviewShortcuts } from '../shortcuts'
export function useActionKeybinds(
element: Readonly
[>,
shortcuts: ReturnType],
) {
+ const keybinds = useModerationKeybinds()
shortcuts.listen('keydown', (event) => {
- if (
- event.defaultPrevented ||
- event.repeat ||
- event.isComposing ||
- event.ctrlKey ||
- event.metaKey ||
- (event.shiftKey && event.altKey)
- ) {
+ if (event.defaultPrevented || event.repeat || event.isComposing) {
return
}
- const digit = /^[1-9]$/.test(event.key)
- ? event.key
- : (event.shiftKey || event.altKey) && /^Digit[1-9]$/.test(event.code)
- ? event.code.slice(-1)
- : undefined
- if (!digit) return
- const modifier = event.altKey ? 'Alt+' : event.shiftKey ? 'Shift+' : ''
- const keybind = `${modifier}${digit}`
const target = event.target
if (
@@ -46,15 +34,28 @@ export function useActionKeybinds(
) {
return
}
- const button = panel.querySelector(
- `button[data-review-keybind="${keybind}"]`,
- )
- if (!button || button.disabled || button.closest('[data-review-panel]') !== panel) return
-
- event.preventDefault()
- const focused = panel.ownerDocument.activeElement
- if (focused instanceof HTMLElement) focused.blur()
- button.click()
- button.blur()
+ const toggleButton = (index: number) => {
+ const button = panel.querySelector(
+ `button[data-review-toggle-index="${index}"]`,
+ )
+ return button &&
+ !button.disabled &&
+ button.closest('[data-review-panel]') === panel &&
+ button.getClientRects().length > 0
+ ? button
+ : undefined
+ }
+ keybinds.value.handle(event, {
+ scope: 'review-panel-toggle',
+ available: (index) => !!toggleButton(index),
+ toggle: (index) => {
+ const button = toggleButton(index)
+ if (!button) return
+ const focused = panel.ownerDocument.activeElement
+ if (focused instanceof HTMLElement) focused.blur()
+ button.click()
+ button.blur()
+ },
+ })
})
}
diff --git a/apps/frontend/src/components/project-review/slicer/index.vue b/apps/frontend/src/components/project-review/slicer/index.vue
index 9b7d8233d18..6e7e9c7a832 100644
--- a/apps/frontend/src/components/project-review/slicer/index.vue
+++ b/apps/frontend/src/components/project-review/slicer/index.vue
@@ -2,34 +2,31 @@
-
{{ formatMessage(messages.loadError) }}
-
{{ formatMessage(messages.retry) }}
+
Could not load project data
+
Retry
-
- {{ formatMessage(messages.loading) }}
-
- {{ formatMessage(messages.emptyVersions) }}
+ Loading...
+ No versions
+
+
+
+
+
+ {{ formatMessage(messages.panelAppearance) }}
+
+
+
+
+
+ {{ formatMessage(messages.noResults) }}
+
+
+
+ {{ formatMessage(group.title) }}
+
+
+
+
+ {{ setting.title }}
+ {{ setting.description }}
+
+
+
+ {{ defaultLabel(setting) }}
+
+
+
+
+
+
configuredSettings.set(setting, value)"
+ />
+
+
+ configuredSettings.set(setting, value === undefined ? undefined : Number(value))
+ "
+ >
+
+ {{ setting.unit }}
+
+
+
+
+
+
+
+
+
+ {{
+ formatMessage(
+ setting.id === general.IssueToggleColor.id
+ ? messages.issueTogglePreview
+ : messages.highlightPreview,
+ )
+ }}
+
+
+
+
+
+
+
+ {{ formatMessage(messages.floatingPanelPreview) }}
+
+
+
+
+
+
+ configuredSettings.set(setting, value)"
+ />
+
+
+
+
+
diff --git a/apps/frontend/src/components/ui/moderation/settings/ModerationAppearanceThemes.vue b/apps/frontend/src/components/ui/moderation/settings/ModerationAppearanceThemes.vue
new file mode 100644
index 00000000000..208996543bb
--- /dev/null
+++ b/apps/frontend/src/components/ui/moderation/settings/ModerationAppearanceThemes.vue
@@ -0,0 +1,185 @@
+
+
+
+
+
+ {{ formatMessage(messages.import) }}
+ {{ formatMessage(messages.export) }}
+
+
+
+
+ {{ formatMessage(mode === 'import' ? messages.importHelp : messages.exportHelp) }}
+
+
{{
+ formatMessage(messages.json)
+ }}
+
+
+ {{ error }}
+
+
+
+
+
+
+ {{ formatMessage(messages.file) }}
+
+ {{
+ formatMessage(messages.apply)
+ }}
+
+
+
+
+ {{ formatMessage(messages.download) }}
+
+ {{
+ formatMessage(copied ? messages.copied : messages.copy)
+ }}
+
+
+
+
+
+
diff --git a/apps/frontend/src/components/ui/moderation/settings/ModerationColorSetting.vue b/apps/frontend/src/components/ui/moderation/settings/ModerationColorSetting.vue
new file mode 100644
index 00000000000..e9623b23f7f
--- /dev/null
+++ b/apps/frontend/src/components/ui/moderation/settings/ModerationColorSetting.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+ {{ model || defaultLabel }}
+
+
+
+
+
+
diff --git a/apps/frontend/src/components/ui/moderation/settings/ModerationKeybind.vue b/apps/frontend/src/components/ui/moderation/settings/ModerationKeybind.vue
index 8660fb2dbf3..9b5723bc6d7 100644
--- a/apps/frontend/src/components/ui/moderation/settings/ModerationKeybind.vue
+++ b/apps/frontend/src/components/ui/moderation/settings/ModerationKeybind.vue
@@ -77,7 +77,7 @@ import {
} from '@modrinth/assets'
import { formatKeybind, type KeybindDefinition, toKeybindDefinition } from '@modrinth/moderation'
import { Button, defineMessages, IconButton, useVIntl } from '@modrinth/ui'
-import { computed, onMounted, onUnmounted, ref, useId, useTemplateRef } from 'vue'
+import { computed, onMounted, onUnmounted, ref, useId, useTemplateRef, watch } from 'vue'
const props = defineProps<{
title: string
@@ -151,6 +151,15 @@ function stopEditing() {
recordingWindow = null
}
+watch(
+ () => props.definitions,
+ (value) => {
+ stopEditing()
+ definitions.value = JSON.parse(JSON.stringify(value))
+ },
+ { deep: true },
+)
+
function resetToDefault() {
stopEditing()
definitions.value = JSON.parse(JSON.stringify(props.default))
diff --git a/apps/frontend/src/components/ui/moderation/settings/ModerationKeybindTransfer.vue b/apps/frontend/src/components/ui/moderation/settings/ModerationKeybindTransfer.vue
new file mode 100644
index 00000000000..f5ee95822d2
--- /dev/null
+++ b/apps/frontend/src/components/ui/moderation/settings/ModerationKeybindTransfer.vue
@@ -0,0 +1,207 @@
+
+
+
+
+
+ {{ formatMessage(messages.import) }}
+ {{ formatMessage(messages.export) }}
+
+
+
+
+ {{ formatMessage(mode === 'import' ? messages.importHelp : messages.exportHelp) }}
+
+
{{
+ formatMessage(messages.json)
+ }}
+
+
+ {{ error }}
+
+
+
+
+
+
+ {{ formatMessage(messages.file) }}
+
+ {{
+ formatMessage(messages.apply)
+ }}
+
+
+
+
+ {{ formatMessage(messages.download) }}
+
+ {{
+ formatMessage(copied ? messages.copied : messages.copy)
+ }}
+
+
+
+
+
+
diff --git a/apps/frontend/src/components/ui/moderation/settings/ModerationKeybinds.vue b/apps/frontend/src/components/ui/moderation/settings/ModerationKeybinds.vue
index cabc08f327f..90f8b14aa56 100644
--- a/apps/frontend/src/components/ui/moderation/settings/ModerationKeybinds.vue
+++ b/apps/frontend/src/components/ui/moderation/settings/ModerationKeybinds.vue
@@ -5,6 +5,7 @@ import { defineMessages, Input, useVIntl } from '@modrinth/ui'
import { computed, ref } from 'vue'
import ModerationKeybind from '~/components/ui/moderation/settings/ModerationKeybind.vue'
+import ModerationKeybindTransfer from '~/components/ui/moderation/settings/ModerationKeybindTransfer.vue'
import { saveModerationOptions, useModerationKeybinds } from '~/composables/moderation'
defineProps<{
@@ -29,6 +30,10 @@ const messages = defineMessages({
id: 'moderation.keybinds.items',
defaultMessage: 'Review items and checks',
},
+ panelToggles: {
+ id: 'moderation.keybinds.panel-toggles',
+ defaultMessage: 'Panel toggles',
+ },
decisions: {
id: 'moderation.keybinds.decisions',
defaultMessage: 'Review outcomes and queue',
@@ -66,6 +71,7 @@ const messages = defineMessages({
const navigationIds = [
'review-toggle-icon',
'review-cycle-conversation',
+ 'review-cycle-misc',
'review-toggle-left',
'review-toggle-right',
]
@@ -125,6 +131,11 @@ const groups = computed(() => {
title: messages.technical,
bindings: bindings.value.filter(([, binding]) => binding.scope === 'tech-review'),
},
+ {
+ id: 'panel-toggles',
+ title: messages.panelToggles,
+ bindings: bindings.value.filter(([, binding]) => binding.scope === 'review-panel-toggle'),
+ },
]
return categories.filter((group) => group.bindings.length)
})
@@ -164,7 +175,10 @@ const conflictLabels = computed(() => {
-
{{ formatMessage(messages.keybinds) }}
+
+
{{ formatMessage(messages.keybinds) }}
+
+
{
@@ -112,7 +139,7 @@ const filteredGroups = computed(() => {
.filter((group) => group.settings.length)
})
-function defaultLabel(setting: (typeof general)[keyof typeof general]) {
+function defaultLabel(setting: (typeof groups)[number]['settings'][number]) {
if (setting.type === 'toggle') return formatMessage(setting.default ? messages.on : messages.off)
if (setting.type === 'enum')
return (
@@ -126,7 +153,9 @@ const configuredSettings = useModerationSettings()
-
{{ formatMessage(messages.title) }}
+
+ {{ formatMessage(messages.title) }}
+
{{ formatMessage(messages.noResults) }}
-
- {{ formatMessage(group.title) }}
+
+
+ {{ formatMessage(group.title) }}
+
-
+
{{ setting.title }}
{{ setting.description }}
@@ -164,7 +200,7 @@ const configuredSettings = useModerationSettings()
-
+
-
+
-
-
+ :variant="variant"
+ :resize="showLockControls ? 'vertical' : 'none'"
+ :disabled="disabled"
+ :placeholder="formatMessage(messages.notePlaceholder)"
+ />
diff --git a/apps/frontend/src/components/ui/project-settings/disclosures/AiDisclosureCard.vue b/apps/frontend/src/components/ui/project-settings/disclosures/AiDisclosureCard.vue
index b423461bc73..6b075bbed16 100644
--- a/apps/frontend/src/components/ui/project-settings/disclosures/AiDisclosureCard.vue
+++ b/apps/frontend/src/components/ui/project-settings/disclosures/AiDisclosureCard.vue
@@ -8,10 +8,10 @@ import {
IntlFormatted,
normalizeChildren,
SettingsFormGroup,
- Textarea,
useVIntl,
} from '@modrinth/ui'
+import DisclosureExplanation from './DisclosureExplanation.vue'
import DisclosureToggleCard from './DisclosureToggleCard.vue'
import type { AiDisclosure, AiUsage, DisclosureCardMetaProps, DisclosureLockStatus } from './types'
@@ -45,6 +45,10 @@ const messages = defineMessages({
defaultMessage:
"Still unsure? Refer to our guide to Disclosure and Usage of AI and section 6 of Modrinth's Content Rules for more information.",
},
+ selectUses: {
+ id: 'project-review.disclosures.select-ai-uses',
+ defaultMessage: 'Select AI usages',
+ },
typesDescription: {
id: 'project.settings.disclosures.ai.types-description',
defaultMessage: 'What types of content have been generated with AI?',
@@ -113,27 +117,23 @@ function setUse(use: AiUsage, enabled: boolean) {
:key="use"
:model-value="hasUse(use)"
:disabled="disabled"
+ class="!gap-1.5"
@update:model-value="(enabled) => setUse(use, enabled)"
>
{{ formatMessage(disclosureAiUsageMessages[use]) }}
-
-
-
+ :variant="variant"
+ :resize="showLockControls ? 'vertical' : 'none'"
+ :disabled="disabled"
+ :placeholder="formatMessage(messages.notePlaceholder)"
+ />
diff --git a/apps/frontend/src/components/ui/project-settings/disclosures/AiFunctionalityDisclosureCard.vue b/apps/frontend/src/components/ui/project-settings/disclosures/AiFunctionalityDisclosureCard.vue
index 3283701ba4c..2810ef19556 100644
--- a/apps/frontend/src/components/ui/project-settings/disclosures/AiFunctionalityDisclosureCard.vue
+++ b/apps/frontend/src/components/ui/project-settings/disclosures/AiFunctionalityDisclosureCard.vue
@@ -1,7 +1,8 @@
+
setDisclosureLockStatus('ai_content', status)
+ (status: DisclosureLockStatus) => setDisclosureLockStatus(card.type, status)
"
/>
-
-
setDisclosureLockStatus('ai_functionality', status)
+ (status: DisclosureLockStatus) => setDisclosureLockStatus(card.type, status)
"
/>
-
-
setDisclosureLockStatus('advertisements', status)
+ (status: DisclosureLockStatus) => setDisclosureLockStatus(card.type, status)
"
/>
-
-
setDisclosureLockStatus('paid_features', status)
+ (status: DisclosureLockStatus) => setDisclosureLockStatus(card.type, status)
"
/>
-
-
setDisclosureLockStatus('telemetry', status)
+ (status: DisclosureLockStatus) => setDisclosureLockStatus(card.type, status)
"
/>
-
-
setDisclosureLockStatus('derivative_work', status)
+ (status: DisclosureLockStatus) => setDisclosureLockStatus(card.type, status)
"
/>
-
-
setDisclosureLockStatus('epilepsy_triggers', status)
+ (status: DisclosureLockStatus) => setDisclosureLockStatus(card.type, status)
"
/>
-
-
setDisclosureLockStatus('system_interactions', status)
+ (status: DisclosureLockStatus) => setDisclosureLockStatus(card.type, status)
"
/>
-
-
setDisclosureLockStatus('archived', status)
+ (status: DisclosureLockStatus) => setDisclosureLockStatus(card.type, status)
"
/>
diff --git a/apps/frontend/src/components/ui/project-settings/disclosures/DisclosureExplanation.vue b/apps/frontend/src/components/ui/project-settings/disclosures/DisclosureExplanation.vue
new file mode 100644
index 00000000000..ad1a9d00cb9
--- /dev/null
+++ b/apps/frontend/src/components/ui/project-settings/disclosures/DisclosureExplanation.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/apps/frontend/src/components/ui/project-settings/disclosures/DisclosureLockControls.vue b/apps/frontend/src/components/ui/project-settings/disclosures/DisclosureLockControls.vue
index d6d7cf59e49..dc0491e2e5f 100644
--- a/apps/frontend/src/components/ui/project-settings/disclosures/DisclosureLockControls.vue
+++ b/apps/frontend/src/components/ui/project-settings/disclosures/DisclosureLockControls.vue
@@ -1,15 +1,30 @@
-
+
+
+
+
+ {{ item.label }}
+
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
import { EyeIcon } from '@modrinth/assets'
-import { commonMessages, defineMessages, SettingsFormGroup, Textarea, useVIntl } from '@modrinth/ui'
+import { commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
+import DisclosureExplanation from './DisclosureExplanation.vue'
import DisclosureToggleCard from './DisclosureToggleCard.vue'
import type { DisclosureCardMetaProps, DisclosureLockStatus, NoteDisclosure } from './types'
@@ -44,20 +45,15 @@ const messages = defineMessages({
@set-lock-status="emit('setLockStatus', $event)"
>
-
-
-
+ :variant="variant"
+ :resize="showLockControls ? 'vertical' : 'none'"
+ :disabled="disabled"
+ :placeholder="formatMessage(messages.notePlaceholder)"
+ />
diff --git a/apps/frontend/src/components/ui/project-settings/disclosures/ProjectReviewDisclosureCard.vue b/apps/frontend/src/components/ui/project-settings/disclosures/ProjectReviewDisclosureCard.vue
index e4109f5c260..8b6c96820c9 100644
--- a/apps/frontend/src/components/ui/project-settings/disclosures/ProjectReviewDisclosureCard.vue
+++ b/apps/frontend/src/components/ui/project-settings/disclosures/ProjectReviewDisclosureCard.vue
@@ -1,16 +1,27 @@
@@ -20,7 +31,7 @@ const isToggleLocked = computed(() => !!props.disabled || (!!props.toggleDisable
:aria-labelledby="titleId"
>
-
+
!!props.disabled || (!!props.toggleDisable
:disabled="isToggleLocked"
:aria-labelledby="titleId"
/>
+
@@ -51,15 +63,49 @@ const isToggleLocked = computed(() => !!props.disabled || (!!props.toggleDisable
-
-
-
-
+
+
+ Cancel
+
+
+ Save
+
+
+
+
+
+
+
+
+
+
+ Could not save all disclosure changes. Your edits have been kept. Try saving again.
+
+
+ {{ sectionSaveDisabledReason.join(' ') }}
+
+
+
+
diff --git a/apps/frontend/src/components/ui/project-settings/disclosures/SystemInteractionsDisclosureCard.vue b/apps/frontend/src/components/ui/project-settings/disclosures/SystemInteractionsDisclosureCard.vue
index 494660c1e5c..2744fc918a8 100644
--- a/apps/frontend/src/components/ui/project-settings/disclosures/SystemInteractionsDisclosureCard.vue
+++ b/apps/frontend/src/components/ui/project-settings/disclosures/SystemInteractionsDisclosureCard.vue
@@ -1,7 +1,8 @@