From ab7359a4c57392c3b028494d7403a54bea8d2de7 Mon Sep 17 00:00:00 2001 From: Prospector <6166773+Prospector@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:58:47 -0700 Subject: [PATCH] minor fixes to disclosures, basic markdown support (#7133) --- .../src/components/base/BasicMarkdownText.vue | 47 +++++++++++ packages/ui/src/components/base/index.ts | 1 + .../project/ArchivedProjectBanner.vue | 6 +- .../project/ProjectSidebarDetails.vue | 77 ++++++++++++++++--- packages/ui/src/locales/en-US/index.json | 6 ++ packages/utils/parse.ts | 51 ++++++++++++ 6 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 packages/ui/src/components/base/BasicMarkdownText.vue diff --git a/packages/ui/src/components/base/BasicMarkdownText.vue b/packages/ui/src/components/base/BasicMarkdownText.vue new file mode 100644 index 0000000000..c6c2d25771 --- /dev/null +++ b/packages/ui/src/components/base/BasicMarkdownText.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/packages/ui/src/components/base/index.ts b/packages/ui/src/components/base/index.ts index 2127b5a3a3..3a82053838 100644 --- a/packages/ui/src/components/base/index.ts +++ b/packages/ui/src/components/base/index.ts @@ -7,6 +7,7 @@ export { default as AutoLink } from './AutoLink.vue' export { default as Avatar } from './Avatar.vue' export { default as Badge } from './Badge.vue' export { default as BaseTerminal } from './BaseTerminal.vue' +export { default as BasicMarkdownText } from './BasicMarkdownText.vue' export { default as BigOptionButton } from './BigOptionButton.vue' export { default as BulletDivider } from './BulletDivider.vue' export { default as Button } from './buttons/Button.vue' diff --git a/packages/ui/src/components/project/ArchivedProjectBanner.vue b/packages/ui/src/components/project/ArchivedProjectBanner.vue index 876414472a..0cc6b6921e 100644 --- a/packages/ui/src/components/project/ArchivedProjectBanner.vue +++ b/packages/ui/src/components/project/ArchivedProjectBanner.vue @@ -3,6 +3,7 @@ import { ArchiveIcon } from '@modrinth/assets' import { computed } from 'vue' import Admonition from '#ui/components/base/Admonition.vue' +import BasicMarkdownText from '#ui/components/base/BasicMarkdownText.vue' import { defineMessages, useVIntl } from '#ui/composables/i18n' const props = defineProps<{ @@ -32,6 +33,9 @@ const messages = defineMessages({ - {{ trimmedReason ?? formatMessage(messages.body, { title }) }} + + + + {{ formatMessage(messages.body, { title }) }} diff --git a/packages/ui/src/components/project/ProjectSidebarDetails.vue b/packages/ui/src/components/project/ProjectSidebarDetails.vue index 814d608c81..8be4b125e9 100644 --- a/packages/ui/src/components/project/ProjectSidebarDetails.vue +++ b/packages/ui/src/components/project/ProjectSidebarDetails.vue @@ -14,7 +14,7 @@

{{ formatMessage(commonMessages.detailsLabel) }}

@@ -34,7 +34,7 @@ {{ capitalizeString(aiGeneratedLabel) }} - {{ aiDisclosure.note }} + @@ -45,7 +45,7 @@ {{ capitalizeString(formatMessage(messages.advertisingTitle)) }} - {{ advertisingDisclosure.note }} + @@ -60,7 +60,7 @@ :key="`${feature}-${index}`" class="text-sm text-secondary" > - {{ feature }} + @@ -81,7 +81,7 @@ :key="`${entry}-${index}`" class="text-sm text-secondary" > - {{ entry }} + @@ -92,7 +92,7 @@ {{ capitalizeString(formatMessage(messages.systemInteractionsTitle)) }} - {{ systemInteractionsDisclosure.note }} + @@ -130,7 +130,7 @@ {{ capitalizeString(formatMessage(messages.derivativeWork)) }}
@@ -139,14 +139,36 @@ :href="source.link" :target="linkTarget" rel="noopener nofollow ugc" - class="text-blue text-sm flex items-center gap-1 hover:underline" + class="text-blue text-sm min-w-0 break-words hover:underline" > {{ source.label }} - {{ source.label }} - {{ source.note }} + + + + + +
+
@@ -207,6 +229,7 @@ import { CalendarIcon, CircleDollarSignIcon, CircuitBoardIcon, + DropdownIcon, ExternalIcon, EyeIcon, GitForkIcon, @@ -226,7 +249,7 @@ import { defineMessage, defineMessages, useVIntl } from '../../composables/i18n' import { injectModrinthClient } from '../../providers' import { commonMessages } from '../../utils/common-messages' import { getActiveDisclosures } from '../../utils/disclosures' -import { Avatar, IntlFormatted } from '../base' +import { Avatar, BasicMarkdownText, IntlFormatted } from '../base' import { NewModal } from '../modal' const LICENSE_STALE_TIME = 1000 * 60 * 10 @@ -285,6 +308,14 @@ const messages = defineMessages({ id: 'project.disclosure.derivative-work.title', defaultMessage: 'This is a derivative work of:', }, + showMoreDerivativeSources: { + id: 'project.disclosure.derivative-work.show-more', + defaultMessage: 'Show {count} more', + }, + showLessDerivativeSources: { + id: 'project.disclosure.derivative-work.show-fewer', + defaultMessage: 'Show fewer', + }, telemetryTitle: { id: 'project.disclosure.telemetry.title', defaultMessage: @@ -322,6 +353,28 @@ const derivativeWorkDisclosure = computed(() => findDisclosure('derivative_work' const photosensitivityDisclosure = computed(() => findDisclosure('epilepsy_triggers')) const systemInteractionsDisclosure = computed(() => findDisclosure('system_interactions')) +const DERIVATIVE_SOURCES_PREVIEW_LIMIT = 3 +const showAllDerivativeSources = ref(false) + +const visibleDerivativeSources = computed(() => { + const sources = derivativeWorkDisclosure.value?.sources ?? [] + if (showAllDerivativeSources.value || sources.length <= DERIVATIVE_SOURCES_PREVIEW_LIMIT) { + return sources + } + return sources.slice(0, DERIVATIVE_SOURCES_PREVIEW_LIMIT) +}) + +const hasMoreDerivativeSources = computed( + () => (derivativeWorkDisclosure.value?.sources.length ?? 0) > DERIVATIVE_SOURCES_PREVIEW_LIMIT, +) + +const hiddenDerivativeSourcesCount = computed(() => + Math.max( + 0, + (derivativeWorkDisclosure.value?.sources.length ?? 0) - DERIVATIVE_SOURCES_PREVIEW_LIMIT, + ), +) + const aiUseLabels = { code: defineMessage({ id: 'project.disclosure.ai-generated-content.use.code', diff --git a/packages/ui/src/locales/en-US/index.json b/packages/ui/src/locales/en-US/index.json index e975188b4c..aaa994bd13 100644 --- a/packages/ui/src/locales/en-US/index.json +++ b/packages/ui/src/locales/en-US/index.json @@ -3386,6 +3386,12 @@ "project.disclosure.ai-generated-content.use.text": { "defaultMessage": "text" }, + "project.disclosure.derivative-work.show-fewer": { + "defaultMessage": "Show fewer" + }, + "project.disclosure.derivative-work.show-more": { + "defaultMessage": "Show {count} more" + }, "project.disclosure.derivative-work.title": { "defaultMessage": "This is a derivative work of:" }, diff --git a/packages/utils/parse.ts b/packages/utils/parse.ts index e398d46b6a..848b9a2e6b 100644 --- a/packages/utils/parse.ts +++ b/packages/utils/parse.ts @@ -229,3 +229,54 @@ export const md = (options = {}) => { } export const renderString = (string: string) => configuredXss.process(md().render(string)) + +const basicMarkdownXss = new FilterXSS({ + whiteList: { + a: ['href', 'target', 'rel'], + strong: [], + em: [], + code: [], + br: [], + }, + stripIgnoreTag: true, + stripIgnoreTagBody: ['script', 'style'], +}) + +export const renderBasicInlineMarkdown = ( + string: string, + options: { + target?: string + } = {}, +) => { + const instance = new MarkdownIt({ + html: false, + linkify: true, + breaks: true, + }) + + instance.disable(['image', 'strikethrough']) + + instance.linkify.set({ + fuzzyLink: false, + fuzzyIP: false, + }) + + const defaultLinkOpenRenderer = + instance.renderer.rules.link_open || + function (tokens, idx, options, _env, self) { + return self.renderToken(tokens, idx, options) + } + + instance.renderer.rules.link_open = function (tokens, idx, renderOptions, env, self) { + const token = tokens[idx] + token.attrSet('rel', 'noopener nofollow ugc') + + if (options.target) { + token.attrSet('target', options.target) + } + + return defaultLinkOpenRenderer(tokens, idx, renderOptions, env, self) + } + + return basicMarkdownXss.process(instance.renderInline(string)) +}