mirror of
https://github.com/modrinth/code.git
synced 2026-09-03 13:36:48 +00:00
fix: less false positives in profanity and fix gallary nag
This commit is contained in:
@@ -92,7 +92,7 @@
|
|||||||
v-if="editIndex === -1"
|
v-if="editIndex === -1"
|
||||||
type="colored"
|
type="colored"
|
||||||
color="brand"
|
color="brand"
|
||||||
:disabled="shouldPreventActions || galleryFieldsInvalid"
|
:disabled="shouldPreventActions || !canSaveGalleryFields"
|
||||||
@click="createGalleryItem"
|
@click="createGalleryItem"
|
||||||
>
|
>
|
||||||
<PlusIcon aria-hidden="true" />
|
<PlusIcon aria-hidden="true" />
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
v-else
|
v-else
|
||||||
type="colored"
|
type="colored"
|
||||||
color="brand"
|
color="brand"
|
||||||
:disabled="shouldPreventActions || galleryFieldsInvalid"
|
:disabled="shouldPreventActions || !canSaveGalleryFields"
|
||||||
@click="editGalleryItem"
|
@click="editGalleryItem"
|
||||||
>
|
>
|
||||||
<SaveIcon aria-hidden="true" />
|
<SaveIcon aria-hidden="true" />
|
||||||
@@ -312,6 +312,7 @@ import {
|
|||||||
Textarea,
|
Textarea,
|
||||||
useFormatDateTime,
|
useFormatDateTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
import { isAdmin } from '@modrinth/utils'
|
||||||
import { useEventListener } from '@vueuse/core'
|
import { useEventListener } from '@vueuse/core'
|
||||||
|
|
||||||
import AiImageWarningModal from '~/components/ui/AiImageWarningModal.vue'
|
import AiImageWarningModal from '~/components/ui/AiImageWarningModal.vue'
|
||||||
@@ -390,6 +391,8 @@ const galleryFieldsInvalid = computed(
|
|||||||
galleryTitleValidation.value.some((validation) => validation.severity === 'error') ||
|
galleryTitleValidation.value.some((validation) => validation.severity === 'error') ||
|
||||||
galleryDescriptionValidation.value.some((validation) => validation.severity === 'error'),
|
galleryDescriptionValidation.value.some((validation) => validation.severity === 'error'),
|
||||||
)
|
)
|
||||||
|
const isAdminUser = computed(() => isAdmin(currentMember.value?.user))
|
||||||
|
const canSaveGalleryFields = computed(() => isAdminUser.value || !galleryFieldsInvalid.value)
|
||||||
|
|
||||||
// Constant for accepted file types
|
// Constant for accepted file types
|
||||||
const MC_SERVER_BANNER_NAME = '__mc_server_banner__'
|
const MC_SERVER_BANNER_NAME = '__mc_server_banner__'
|
||||||
@@ -490,7 +493,7 @@ function showPreviewImage() {
|
|||||||
|
|
||||||
// CRUD operations
|
// CRUD operations
|
||||||
async function createGalleryItem() {
|
async function createGalleryItem() {
|
||||||
if (galleryFieldsInvalid.value) return
|
if (!canSaveGalleryFields.value) return
|
||||||
shouldPreventActions.value = true
|
shouldPreventActions.value = true
|
||||||
startLoading()
|
startLoading()
|
||||||
|
|
||||||
@@ -511,7 +514,7 @@ async function createGalleryItem() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function editGalleryItem() {
|
async function editGalleryItem() {
|
||||||
if (galleryFieldsInvalid.value) return
|
if (!canSaveGalleryFields.value) return
|
||||||
shouldPreventActions.value = true
|
shouldPreventActions.value = true
|
||||||
startLoading()
|
startLoading()
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
v-if="editIndex === -1"
|
v-if="editIndex === -1"
|
||||||
type="colored"
|
type="colored"
|
||||||
color="brand"
|
color="brand"
|
||||||
:disabled="shouldPreventActions || galleryFieldsInvalid"
|
:disabled="shouldPreventActions || !canSaveGalleryFields"
|
||||||
@click="createGalleryItem"
|
@click="createGalleryItem"
|
||||||
>
|
>
|
||||||
<PlusIcon aria-hidden="true" />
|
<PlusIcon aria-hidden="true" />
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
v-else
|
v-else
|
||||||
type="colored"
|
type="colored"
|
||||||
color="brand"
|
color="brand"
|
||||||
:disabled="shouldPreventActions || galleryFieldsInvalid"
|
:disabled="shouldPreventActions || !canSaveGalleryFields"
|
||||||
@click="editGalleryItem"
|
@click="editGalleryItem"
|
||||||
>
|
>
|
||||||
<SaveIcon aria-hidden="true" />
|
<SaveIcon aria-hidden="true" />
|
||||||
@@ -303,6 +303,7 @@ import {
|
|||||||
Textarea,
|
Textarea,
|
||||||
useFormatDateTime,
|
useFormatDateTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
import { isAdmin } from '@modrinth/utils'
|
||||||
|
|
||||||
import AiImageWarningModal from '~/components/ui/AiImageWarningModal.vue'
|
import AiImageWarningModal from '~/components/ui/AiImageWarningModal.vue'
|
||||||
import ValidationMessage from '~/components/ValidationMessage.vue'
|
import ValidationMessage from '~/components/ValidationMessage.vue'
|
||||||
@@ -350,6 +351,8 @@ const galleryFieldsInvalid = computed(
|
|||||||
galleryTitleValidation.value.some((validation) => validation.severity === 'error') ||
|
galleryTitleValidation.value.some((validation) => validation.severity === 'error') ||
|
||||||
galleryDescriptionValidation.value.some((validation) => validation.severity === 'error'),
|
galleryDescriptionValidation.value.some((validation) => validation.severity === 'error'),
|
||||||
)
|
)
|
||||||
|
const isAdminUser = computed(() => isAdmin(currentMember.value?.user))
|
||||||
|
const canSaveGalleryFields = computed(() => isAdminUser.value || !galleryFieldsInvalid.value)
|
||||||
|
|
||||||
const MC_SERVER_BANNER_NAME = '__mc_server_banner__'
|
const MC_SERVER_BANNER_NAME = '__mc_server_banner__'
|
||||||
const acceptFileTypes = 'image/png,image/jpeg,image/gif,image/webp,.png,.jpeg,.gif,.webp'
|
const acceptFileTypes = 'image/png,image/jpeg,image/gif,image/webp,.png,.jpeg,.gif,.webp'
|
||||||
@@ -430,7 +433,7 @@ const showPreviewImage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createGalleryItem = async () => {
|
const createGalleryItem = async () => {
|
||||||
if (galleryFieldsInvalid.value) return
|
if (!canSaveGalleryFields.value) return
|
||||||
shouldPreventActions.value = true
|
shouldPreventActions.value = true
|
||||||
|
|
||||||
const success = await createGalleryItemMutation(
|
const success = await createGalleryItemMutation(
|
||||||
@@ -449,7 +452,7 @@ const createGalleryItem = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const editGalleryItem = async () => {
|
const editGalleryItem = async () => {
|
||||||
if (galleryFieldsInvalid.value) return
|
if (!canSaveGalleryFields.value) return
|
||||||
shouldPreventActions.value = true
|
shouldPreventActions.value = true
|
||||||
|
|
||||||
const success = await editGalleryItemMutation(
|
const success = await editGalleryItemMutation(
|
||||||
|
|||||||
@@ -33,14 +33,6 @@ const descriptionErrorCodes: readonly ProjectTextValidationCode[] = [
|
|||||||
'text-non-standard',
|
'text-non-standard',
|
||||||
]
|
]
|
||||||
|
|
||||||
const messages = {
|
|
||||||
galleryFailure: defineMessage({
|
|
||||||
id: 'nags.invalid-gallery-text.description',
|
|
||||||
defaultMessage:
|
|
||||||
'Gallery image {number} {field, select, gallery-name {name} gallery-description {description} other {text}}: {error}',
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFirstFailure(
|
function getFirstFailure(
|
||||||
context: NagContext,
|
context: NagContext,
|
||||||
fields: readonly ProjectValidationField[],
|
fields: readonly ProjectValidationField[],
|
||||||
@@ -80,18 +72,6 @@ function getCodedFailureDescription(context: NagContext, code: ProjectTextValida
|
|||||||
return formatMessage(failure.message, failure.values)
|
return formatMessage(failure.message, failure.values)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGalleryFailureDescription(context: NagContext): string {
|
|
||||||
const failure = getFirstFailure(context, ['gallery-name', 'gallery-description'], 'error')
|
|
||||||
if (!failure) return ''
|
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
|
||||||
return formatMessage(messages.galleryFailure, {
|
|
||||||
number: (failure.galleryIndex ?? 0) + 1,
|
|
||||||
field: failure.field,
|
|
||||||
error: formatMessage(failure.message, failure.values),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const projectValidationNags: Nag[] = [
|
export const projectValidationNags: Nag[] = [
|
||||||
{
|
{
|
||||||
id: 'invalid-project-name',
|
id: 'invalid-project-name',
|
||||||
@@ -341,7 +321,8 @@ export const projectValidationNags: Nag[] = [
|
|||||||
id: 'nags.invalid-gallery-text.title',
|
id: 'nags.invalid-gallery-text.title',
|
||||||
defaultMessage: 'Fix gallery text',
|
defaultMessage: 'Fix gallery text',
|
||||||
}),
|
}),
|
||||||
description: getGalleryFailureDescription,
|
description: (context) =>
|
||||||
|
getFailureDescription(context, ['gallery-name', 'gallery-description'], 'error'),
|
||||||
status: 'required',
|
status: 'required',
|
||||||
shouldShow: (context) =>
|
shouldShow: (context) =>
|
||||||
getFirstFailure(context, ['gallery-name', 'gallery-description'], 'error') !== undefined,
|
getFirstFailure(context, ['gallery-name', 'gallery-description'], 'error') !== undefined,
|
||||||
|
|||||||
@@ -1,380 +1,380 @@
|
|||||||
{
|
{
|
||||||
"nags.add-description.description": {
|
"nags.add-description.description": {
|
||||||
"defaultMessage": "A description that clearly describes the project's purpose and function is required."
|
"defaultMessage": "A description that clearly describes the project's purpose and function is required."
|
||||||
},
|
},
|
||||||
"nags.add-description.title": {
|
"nags.add-description.title": {
|
||||||
"defaultMessage": "Add a description"
|
"defaultMessage": "Add a description"
|
||||||
},
|
},
|
||||||
"nags.add-icon.description": {
|
"nags.add-icon.description": {
|
||||||
"defaultMessage": "Adding a unique, relevant, and engaging icon makes your project identifiable and helps it stand out."
|
"defaultMessage": "Adding a unique, relevant, and engaging icon makes your project identifiable and helps it stand out."
|
||||||
},
|
},
|
||||||
"nags.add-icon.title": {
|
"nags.add-icon.title": {
|
||||||
"defaultMessage": "Add an icon"
|
"defaultMessage": "Add an icon"
|
||||||
},
|
},
|
||||||
"nags.add-java-address.description": {
|
"nags.add-java-address.description": {
|
||||||
"defaultMessage": "Add the IP address and port Java Edition players can use to join your server."
|
"defaultMessage": "Add the IP address and port Java Edition players can use to join your server."
|
||||||
},
|
},
|
||||||
"nags.add-java-address.title": {
|
"nags.add-java-address.title": {
|
||||||
"defaultMessage": "Add a Java address"
|
"defaultMessage": "Add a Java address"
|
||||||
},
|
},
|
||||||
"nags.add-license-details.description": {
|
"nags.add-license-details.description": {
|
||||||
"defaultMessage": "Add a valid URL and name or SPDX identifier for your custom license."
|
"defaultMessage": "Add a valid URL and name or SPDX identifier for your custom license."
|
||||||
},
|
},
|
||||||
"nags.add-license-details.title": {
|
"nags.add-license-details.title": {
|
||||||
"defaultMessage": "Add license details"
|
"defaultMessage": "Add license details"
|
||||||
},
|
},
|
||||||
"nags.add-links-server.description": {
|
"nags.add-links-server.description": {
|
||||||
"defaultMessage": "Add any relevant links targeted outside of Modrinth, such as a website, store, or a Discord invite."
|
"defaultMessage": "Add any relevant links targeted outside of Modrinth, such as a website, store, or a Discord invite."
|
||||||
},
|
},
|
||||||
"nags.add-links-server.title": {
|
"nags.add-links-server.title": {
|
||||||
"defaultMessage": "Add external links"
|
"defaultMessage": "Add external links"
|
||||||
},
|
},
|
||||||
"nags.add-links.description": {
|
"nags.add-links.description": {
|
||||||
"defaultMessage": "Add any relevant links targeted outside of Modrinth, such as source code, an issue tracker, or a Discord invite."
|
"defaultMessage": "Add any relevant links targeted outside of Modrinth, such as source code, an issue tracker, or a Discord invite."
|
||||||
},
|
},
|
||||||
"nags.add-links.title": {
|
"nags.add-links.title": {
|
||||||
"defaultMessage": "Add external links"
|
"defaultMessage": "Add external links"
|
||||||
},
|
},
|
||||||
"nags.all-languages.description": {
|
"nags.all-languages.description": {
|
||||||
"defaultMessage": "You've selected all available language options. Please list only the languages your server actively supports."
|
"defaultMessage": "You've selected all available language options. Please list only the languages your server actively supports."
|
||||||
},
|
},
|
||||||
"nags.all-languages.title": {
|
"nags.all-languages.title": {
|
||||||
"defaultMessage": "Select accurate languages"
|
"defaultMessage": "Select accurate languages"
|
||||||
},
|
},
|
||||||
"nags.all-tags-selected.description": {
|
"nags.all-tags-selected.description": {
|
||||||
"defaultMessage": "You've selected all {totalAvailableTags, plural, one {# available tag} other {# available tags}}. This defeats the purpose of tags, which are meant to help users find relevant projects. Please select only the tags that are relevant to your project."
|
"defaultMessage": "You've selected all {totalAvailableTags, plural, one {# available tag} other {# available tags}}. This defeats the purpose of tags, which are meant to help users find relevant projects. Please select only the tags that are relevant to your project."
|
||||||
},
|
},
|
||||||
"nags.all-tags-selected.title": {
|
"nags.all-tags-selected.title": {
|
||||||
"defaultMessage": "Select accurate tags"
|
"defaultMessage": "Select accurate tags"
|
||||||
},
|
},
|
||||||
"nags.check-disclosures.description": {
|
"nags.check-disclosures.description": {
|
||||||
"defaultMessage": "Make sure users are aware of any important details by filling in content disclosures that apply to your {type}."
|
"defaultMessage": "Make sure users are aware of any important details by filling in content disclosures that apply to your {type}."
|
||||||
},
|
},
|
||||||
"nags.check-disclosures.title": {
|
"nags.check-disclosures.title": {
|
||||||
"defaultMessage": "Check content disclosures"
|
"defaultMessage": "Check content disclosures"
|
||||||
},
|
},
|
||||||
"nags.description-too-short.description": {
|
"nags.description-too-short.description": {
|
||||||
"defaultMessage": "Your description is {length, plural, one {# readable character} other {# readable characters}}. At least {minChars, plural, one {# character} other {# characters}} is recommended to create a clear and informative description."
|
"defaultMessage": "Your description is {length, plural, one {# readable character} other {# readable characters}}. At least {minChars, plural, one {# character} other {# characters}} is recommended to create a clear and informative description."
|
||||||
},
|
},
|
||||||
"nags.description-too-short.title": {
|
"nags.description-too-short.title": {
|
||||||
"defaultMessage": "Expand the description"
|
"defaultMessage": "Expand the description"
|
||||||
},
|
},
|
||||||
"nags.edit-description.title": {
|
"nags.edit-description.title": {
|
||||||
"defaultMessage": "Edit description"
|
"defaultMessage": "Edit description"
|
||||||
},
|
},
|
||||||
"nags.edit-gallery.title": {
|
"nags.edit-gallery.title": {
|
||||||
"defaultMessage": "Edit gallery"
|
"defaultMessage": "Edit gallery"
|
||||||
},
|
},
|
||||||
"nags.edit-license.title": {
|
"nags.edit-license.title": {
|
||||||
"defaultMessage": "Edit license"
|
"defaultMessage": "Edit license"
|
||||||
},
|
},
|
||||||
"nags.edit-summary.title": {
|
"nags.edit-summary.title": {
|
||||||
"defaultMessage": "Edit summary"
|
"defaultMessage": "Edit summary"
|
||||||
},
|
},
|
||||||
"nags.edit-tags.title": {
|
"nags.edit-tags.title": {
|
||||||
"defaultMessage": "Edit tags"
|
"defaultMessage": "Edit tags"
|
||||||
},
|
},
|
||||||
"nags.edit-title.title": {
|
"nags.edit-title.title": {
|
||||||
"defaultMessage": "Edit title"
|
"defaultMessage": "Edit title"
|
||||||
},
|
},
|
||||||
"nags.feature-gallery-image.description": {
|
"nags.feature-gallery-image.description": {
|
||||||
"defaultMessage": "The featured gallery image is often how your project makes its first impression."
|
"defaultMessage": "The featured gallery image is often how your project makes its first impression."
|
||||||
},
|
},
|
||||||
"nags.feature-gallery-image.title": {
|
"nags.feature-gallery-image.title": {
|
||||||
"defaultMessage": "Feature a gallery image"
|
"defaultMessage": "Feature a gallery image"
|
||||||
},
|
},
|
||||||
"nags.gallery.title": {
|
"nags.gallery.title": {
|
||||||
"defaultMessage": "Visit gallery page"
|
"defaultMessage": "Visit gallery page"
|
||||||
},
|
},
|
||||||
"nags.gpl-license-source-required.description": {
|
"nags.gpl-license-source-required.description": {
|
||||||
"defaultMessage": "Your {type} uses a license which requires source code to be available. Please provide a source code link or sources file for each additional version, or consider using a different license."
|
"defaultMessage": "Your {type} uses a license which requires source code to be available. Please provide a source code link or sources file for each additional version, or consider using a different license."
|
||||||
},
|
},
|
||||||
"nags.gpl-license-source-required.title": {
|
"nags.gpl-license-source-required.title": {
|
||||||
"defaultMessage": "Provide source code"
|
"defaultMessage": "Provide source code"
|
||||||
},
|
},
|
||||||
"nags.identical-links.description": {
|
"nags.identical-links.description": {
|
||||||
"defaultMessage": "Some of your external links appear to be identical. Each link should be entered only once and with the appropriate link type."
|
"defaultMessage": "Some of your external links appear to be identical. Each link should be entered only once and with the appropriate link type."
|
||||||
},
|
},
|
||||||
"nags.identical-links.title": {
|
"nags.identical-links.title": {
|
||||||
"defaultMessage": "Clean up identical links"
|
"defaultMessage": "Clean up identical links"
|
||||||
},
|
},
|
||||||
"nags.image-heavy-description.description": {
|
"nags.image-heavy-description.description": {
|
||||||
"defaultMessage": "Your Description should contain sufficient plain text or image alt-text, keeping it accessible to those using screen readers or with slow internet connections."
|
"defaultMessage": "Your Description should contain sufficient plain text or image alt-text, keeping it accessible to those using screen readers or with slow internet connections."
|
||||||
},
|
},
|
||||||
"nags.image-heavy-description.title": {
|
"nags.image-heavy-description.title": {
|
||||||
"defaultMessage": "Ensure accessibility"
|
"defaultMessage": "Ensure accessibility"
|
||||||
},
|
},
|
||||||
"nags.invalid-gallery-text.description": {
|
"nags.invalid-gallery-text.title": {
|
||||||
"defaultMessage": "Gallery image {number} {field, select, gallery-name {name} gallery-description {description} other {text}}: {error}"
|
"defaultMessage": "Fix gallery text"
|
||||||
},
|
},
|
||||||
"nags.invalid-gallery-text.title": {
|
"nags.invalid-license-url.description.default": {
|
||||||
"defaultMessage": "Fix gallery text"
|
"defaultMessage": "License URL is invalid."
|
||||||
},
|
},
|
||||||
"nags.invalid-license-url.description.default": {
|
"nags.invalid-license-url.description.domain": {
|
||||||
"defaultMessage": "License URL is invalid."
|
"defaultMessage": "Your license URL points to {domain}, which is not appropriate for license information. License URLs should link directly to your license file, not social media, gaming platforms, etc."
|
||||||
},
|
},
|
||||||
"nags.invalid-license-url.description.domain": {
|
"nags.invalid-license-url.description.malformed": {
|
||||||
"defaultMessage": "Your license URL points to {domain}, which is not appropriate for license information. License URLs should link directly to your license file, not social media, gaming platforms, etc."
|
"defaultMessage": "Your license URL appears to be malformed. Please provide a valid URL to your license text."
|
||||||
},
|
},
|
||||||
"nags.invalid-license-url.description.malformed": {
|
"nags.invalid-license-url.title": {
|
||||||
"defaultMessage": "Your license URL appears to be malformed. Please provide a valid URL to your license text."
|
"defaultMessage": "Add a valid license link"
|
||||||
},
|
},
|
||||||
"nags.invalid-license-url.title": {
|
"nags.invalid-project-description.title": {
|
||||||
"defaultMessage": "Add a valid license link"
|
"defaultMessage": "Fix the project description"
|
||||||
},
|
},
|
||||||
"nags.invalid-project-description.title": {
|
"nags.invalid-project-name.title": {
|
||||||
"defaultMessage": "Fix the project description"
|
"defaultMessage": "Fix the project name"
|
||||||
},
|
},
|
||||||
"nags.invalid-project-name.title": {
|
"nags.invalid-project-summary.title": {
|
||||||
"defaultMessage": "Fix the project name"
|
"defaultMessage": "Fix the project summary"
|
||||||
},
|
},
|
||||||
"nags.invalid-project-summary.title": {
|
"nags.link-shortener-usage.description": {
|
||||||
"defaultMessage": "Fix the project summary"
|
"defaultMessage": "Use of link shorteners or other methods to obscure where a link may lead in your external links or license link is prohibited, please only use appropriate full length links."
|
||||||
},
|
},
|
||||||
"nags.link-shortener-usage.description": {
|
"nags.link-shortener-usage.title": {
|
||||||
"defaultMessage": "Use of link shorteners or other methods to obscure where a link may lead in your external links or license link is prohibited, please only use appropriate full length links."
|
"defaultMessage": "Don't use link shorteners"
|
||||||
},
|
},
|
||||||
"nags.link-shortener-usage.title": {
|
"nags.link.discord.channel": {
|
||||||
"defaultMessage": "Don't use link shorteners"
|
"defaultMessage": "This is a link to a Discord channel, not a server invite."
|
||||||
},
|
},
|
||||||
"nags.link.discord.channel": {
|
"nags.link.discord.invite.expires": {
|
||||||
"defaultMessage": "This is a link to a Discord channel, not a server invite."
|
"defaultMessage": "This Discord invite is set to expire"
|
||||||
},
|
},
|
||||||
"nags.link.discord.invite.expires": {
|
"nags.link.discord.invite.invalid": {
|
||||||
"defaultMessage": "This Discord invite is set to expire"
|
"defaultMessage": "This Discord invite is invalid or has expired."
|
||||||
},
|
},
|
||||||
"nags.link.discord.invite.invalid": {
|
"nags.link.discord.invite.not-guild": {
|
||||||
"defaultMessage": "This Discord invite is invalid or has expired."
|
"defaultMessage": "This Discord invite does not lead to a server."
|
||||||
},
|
},
|
||||||
"nags.link.discord.invite.not-guild": {
|
"nags.link.discord.user": {
|
||||||
"defaultMessage": "This Discord invite does not lead to a server."
|
"defaultMessage": "This is a link to a Discord user, not a server invite."
|
||||||
},
|
},
|
||||||
"nags.link.discord.user": {
|
"nags.link.expected-type": {
|
||||||
"defaultMessage": "This is a link to a Discord user, not a server invite."
|
"defaultMessage": "This isn't a valid {label} link."
|
||||||
},
|
},
|
||||||
"nags.link.expected-type": {
|
"nags.link.git.archived": {
|
||||||
"defaultMessage": "This isn't a valid {label} link."
|
"defaultMessage": "This repository is archived, which disables issues."
|
||||||
},
|
},
|
||||||
"nags.link.git.archived": {
|
"nags.link.git.empty": {
|
||||||
"defaultMessage": "This repository is archived, which disables issues."
|
"defaultMessage": "This repository appears to be empty."
|
||||||
},
|
},
|
||||||
"nags.link.git.empty": {
|
"nags.link.git.issues-disabled": {
|
||||||
"defaultMessage": "This repository appears to be empty."
|
"defaultMessage": "Issues are disabled on this repository."
|
||||||
},
|
},
|
||||||
"nags.link.git.issues-disabled": {
|
"nags.link.git.not-found": {
|
||||||
"defaultMessage": "Issues are disabled on this repository."
|
"defaultMessage": "This repository could not be found (it may be private or deleted)."
|
||||||
},
|
},
|
||||||
"nags.link.git.not-found": {
|
"nags.link.git.wiki-disabled": {
|
||||||
"defaultMessage": "This repository could not be found (it may be private or deleted)."
|
"defaultMessage": "The wiki is disabled on this repository."
|
||||||
},
|
},
|
||||||
"nags.link.git.wiki-disabled": {
|
"nags.link.invalid-url": {
|
||||||
"defaultMessage": "The wiki is disabled on this repository."
|
"defaultMessage": "There's an invalid URL in the description."
|
||||||
},
|
},
|
||||||
"nags.link.invalid-url": {
|
"nags.link.license.url-mismatch": {
|
||||||
"defaultMessage": "There's an invalid URL in the description."
|
"defaultMessage": "This link points to the {detected} license, but your project is set to {selected}."
|
||||||
},
|
},
|
||||||
"nags.link.license.url-mismatch": {
|
"nags.link.license.url-redundant": {
|
||||||
"defaultMessage": "This link points to the {detected} license, but your project is set to {selected}."
|
"defaultMessage": "You don't need to link to a generic license page for a supported license — consider linking to your repository's own license file instead, or leaving this blank."
|
||||||
},
|
},
|
||||||
"nags.link.license.url-redundant": {
|
"nags.link.never-valid": {
|
||||||
"defaultMessage": "You don't need to link to a generic license page for a supported license — consider linking to your repository's own license file instead, or leaving this blank."
|
"defaultMessage": "{label} links aren't allowed here."
|
||||||
},
|
},
|
||||||
"nags.link.never-valid": {
|
"nags.link.unverifiable-redirect": {
|
||||||
"defaultMessage": "{label} links aren't allowed here."
|
"defaultMessage": "This doesn't look like a {platform} link."
|
||||||
},
|
},
|
||||||
"nags.link.unverifiable-redirect": {
|
"nags.link.wrong-field": {
|
||||||
"defaultMessage": "This doesn't look like a {platform} link."
|
"defaultMessage": "{label} links aren't valid for this field."
|
||||||
},
|
},
|
||||||
"nags.link.wrong-field": {
|
"nags.link.youtube.unrecognized": {
|
||||||
"defaultMessage": "{label} links aren't valid for this field."
|
"defaultMessage": "This doesn't look like a YouTube donation link."
|
||||||
},
|
},
|
||||||
"nags.link.youtube.unrecognized": {
|
"nags.long-headers.description": {
|
||||||
"defaultMessage": "This doesn't look like a YouTube donation link."
|
"defaultMessage": "{count, plural, one {# header} other {# headers}} in your description {count, plural, one {is} other {are}} too long. Headers should be concise and act as section titles, not full sentences."
|
||||||
},
|
},
|
||||||
"nags.long-headers.description": {
|
"nags.long-headers.title": {
|
||||||
"defaultMessage": "{count, plural, one {# header} other {# headers}} in your description {count, plural, one {is} other {are}} too long. Headers should be concise and act as section titles, not full sentences."
|
"defaultMessage": "Shorten headers"
|
||||||
},
|
},
|
||||||
"nags.long-headers.title": {
|
"nags.minecraft-title-clause.description": {
|
||||||
"defaultMessage": "Shorten headers"
|
"defaultMessage": "Projects must not use Minecraft's branding or include \"Minecraft\" as a significant part of the name."
|
||||||
},
|
},
|
||||||
"nags.minecraft-title-clause.description": {
|
"nags.minecraft-title-clause.title": {
|
||||||
"defaultMessage": "Projects must not use Minecraft's branding or include \"Minecraft\" as a significant part of the name."
|
"defaultMessage": "Avoid brand infringement"
|
||||||
},
|
},
|
||||||
"nags.minecraft-title-clause.title": {
|
"nags.missing-alt-text.description": {
|
||||||
"defaultMessage": "Avoid brand infringement"
|
"defaultMessage": "Some of your images are missing alt text, which is important for accessibility, especially for visually impaired users."
|
||||||
},
|
},
|
||||||
"nags.missing-alt-text.description": {
|
"nags.missing-alt-text.title": {
|
||||||
"defaultMessage": "Some of your images are missing alt text, which is important for accessibility, especially for visually impaired users."
|
"defaultMessage": "Add image alt text"
|
||||||
},
|
},
|
||||||
"nags.missing-alt-text.title": {
|
"nags.misused-discord-link-description": {
|
||||||
"defaultMessage": "Add image alt text"
|
"defaultMessage": "Discord invites can not be used for other link types. Please put your Discord link in the Discord Invite link field only."
|
||||||
},
|
},
|
||||||
"nags.misused-discord-link-description": {
|
"nags.misused-discord-link.title": {
|
||||||
"defaultMessage": "Discord invites can not be used for other link types. Please put your Discord link in the Discord Invite link field only."
|
"defaultMessage": "Move Discord invite"
|
||||||
},
|
},
|
||||||
"nags.misused-discord-link.title": {
|
"nags.moderation.title": {
|
||||||
"defaultMessage": "Move Discord invite"
|
"defaultMessage": "Visit moderation thread"
|
||||||
},
|
},
|
||||||
"nags.moderation.title": {
|
"nags.moderator-feedback.description": {
|
||||||
"defaultMessage": "Visit moderation thread"
|
"defaultMessage": "Review and address all concerns from the moderation team before resubmitting."
|
||||||
},
|
},
|
||||||
"nags.moderator-feedback.description": {
|
"nags.moderator-feedback.title": {
|
||||||
"defaultMessage": "Review and address all concerns from the moderation team before resubmitting."
|
"defaultMessage": "Review feedback"
|
||||||
},
|
},
|
||||||
"nags.moderator-feedback.title": {
|
"nags.multiple-resolution-tags.description": {
|
||||||
"defaultMessage": "Review feedback"
|
"defaultMessage": "You've selected {count, plural, one {# resolution tag} other {# resolution tags}} ({tags}). Resource packs should typically only have one resolution tag that matches their primary resolution."
|
||||||
},
|
},
|
||||||
"nags.multiple-resolution-tags.description": {
|
"nags.multiple-resolution-tags.title": {
|
||||||
"defaultMessage": "You've selected {count, plural, one {# resolution tag} other {# resolution tags}} ({tags}). Resource packs should typically only have one resolution tag that matches their primary resolution."
|
"defaultMessage": "Select correct resolution"
|
||||||
},
|
},
|
||||||
"nags.multiple-resolution-tags.title": {
|
"nags.project-name-metadata.title": {
|
||||||
"defaultMessage": "Select correct resolution"
|
"defaultMessage": "Remove technical details from the name"
|
||||||
},
|
},
|
||||||
"nags.project-name-metadata.title": {
|
"nags.project-summary-content.title": {
|
||||||
"defaultMessage": "Remove technical details from the name"
|
"defaultMessage": "Review the project summary"
|
||||||
},
|
},
|
||||||
"nags.project-summary-content.title": {
|
"nags.review-permissions.description": {
|
||||||
"defaultMessage": "Review the project summary"
|
"defaultMessage": "Make sure you have provided proof of your permission to distribute any external content in your Modpack."
|
||||||
},
|
},
|
||||||
"nags.review-permissions.description": {
|
"nags.review-permissions.title": {
|
||||||
"defaultMessage": "Make sure you have provided proof of your permission to distribute any external content in your Modpack."
|
"defaultMessage": "Review external permissions"
|
||||||
},
|
},
|
||||||
"nags.review-permissions.title": {
|
"nags.select-compatibility.description": {
|
||||||
"defaultMessage": "Review external permissions"
|
"defaultMessage": "Select what versions your server supports, choose a Modpack, or upload your own."
|
||||||
},
|
},
|
||||||
"nags.select-compatibility.description": {
|
"nags.select-compatibility.title": {
|
||||||
"defaultMessage": "Select what versions your server supports, choose a Modpack, or upload your own."
|
"defaultMessage": "Select compatibility"
|
||||||
},
|
},
|
||||||
"nags.select-compatibility.title": {
|
"nags.select-country.description": {
|
||||||
"defaultMessage": "Select compatibility"
|
"defaultMessage": "Let players know what region your server is located in."
|
||||||
},
|
},
|
||||||
"nags.select-country.description": {
|
"nags.select-country.title": {
|
||||||
"defaultMessage": "Let players know what region your server is located in."
|
"defaultMessage": "Select a region"
|
||||||
},
|
},
|
||||||
"nags.select-country.title": {
|
"nags.select-language.description": {
|
||||||
"defaultMessage": "Select a region"
|
"defaultMessage": "List the language or languages supported by your server."
|
||||||
},
|
},
|
||||||
"nags.select-language.description": {
|
"nags.select-language.title": {
|
||||||
"defaultMessage": "List the language or languages supported by your server."
|
"defaultMessage": "Select a language"
|
||||||
},
|
},
|
||||||
"nags.select-language.title": {
|
"nags.select-license.description": {
|
||||||
"defaultMessage": "Select a language"
|
"defaultMessage": "Select the license your {type} is distributed under."
|
||||||
},
|
},
|
||||||
"nags.select-license.description": {
|
"nags.select-license.title": {
|
||||||
"defaultMessage": "Select the license your {type} is distributed under."
|
"defaultMessage": "Select a license"
|
||||||
},
|
},
|
||||||
"nags.select-license.title": {
|
"nags.select-tags.description": {
|
||||||
"defaultMessage": "Select a license"
|
"defaultMessage": "Select the tags that correctly apply to your project to help the right users find it."
|
||||||
},
|
},
|
||||||
"nags.select-tags.description": {
|
"nags.select-tags.title": {
|
||||||
"defaultMessage": "Select the tags that correctly apply to your project to help the right users find it."
|
"defaultMessage": "Select tags"
|
||||||
},
|
},
|
||||||
"nags.select-tags.title": {
|
"nags.server.title": {
|
||||||
"defaultMessage": "Select tags"
|
"defaultMessage": "Visit server settings"
|
||||||
},
|
},
|
||||||
"nags.server.title": {
|
"nags.settings.description.title": {
|
||||||
"defaultMessage": "Visit server settings"
|
"defaultMessage": "Visit description settings"
|
||||||
},
|
},
|
||||||
"nags.settings.description.title": {
|
"nags.settings.disclosures.title": {
|
||||||
"defaultMessage": "Visit description settings"
|
"defaultMessage": "Visit disclosure settings"
|
||||||
},
|
},
|
||||||
"nags.settings.disclosures.title": {
|
"nags.settings.license.title": {
|
||||||
"defaultMessage": "Visit disclosure settings"
|
"defaultMessage": "Visit license settings"
|
||||||
},
|
},
|
||||||
"nags.settings.license.title": {
|
"nags.settings.links.title": {
|
||||||
"defaultMessage": "Visit license settings"
|
"defaultMessage": "Visit links settings"
|
||||||
},
|
},
|
||||||
"nags.settings.links.title": {
|
"nags.settings.permissions.title": {
|
||||||
"defaultMessage": "Visit links settings"
|
"defaultMessage": "Visit permissions dashboard"
|
||||||
},
|
},
|
||||||
"nags.settings.permissions.title": {
|
"nags.settings.tags.title": {
|
||||||
"defaultMessage": "Visit permissions dashboard"
|
"defaultMessage": "Visit tag settings"
|
||||||
},
|
},
|
||||||
"nags.settings.tags.title": {
|
"nags.settings.title": {
|
||||||
"defaultMessage": "Visit tag settings"
|
"defaultMessage": "Visit general settings"
|
||||||
},
|
},
|
||||||
"nags.settings.title": {
|
"nags.settings.versions.title": {
|
||||||
"defaultMessage": "Visit general settings"
|
"defaultMessage": "Visit versions settings"
|
||||||
},
|
},
|
||||||
"nags.settings.versions.title": {
|
"nags.summary-special-formatting.description": {
|
||||||
"defaultMessage": "Visit versions settings"
|
"defaultMessage": "Your summary should not contain formatting, line breaks, or special characters, since the summary will only display plain text."
|
||||||
},
|
},
|
||||||
"nags.summary-special-formatting.description": {
|
"nags.summary-special-formatting.title": {
|
||||||
"defaultMessage": "Your summary should not contain formatting, line breaks, or special characters, since the summary will only display plain text."
|
"defaultMessage": "Clean up the summary"
|
||||||
},
|
},
|
||||||
"nags.summary-special-formatting.title": {
|
"nags.summary-too-short.title": {
|
||||||
"defaultMessage": "Clean up the summary"
|
"defaultMessage": "Expand the summary"
|
||||||
},
|
},
|
||||||
"nags.summary-too-short.title": {
|
"nags.too-many-languages.description": {
|
||||||
"defaultMessage": "Expand the summary"
|
"defaultMessage": "You've selected {languageCount, plural, one {# language} other {# languages}}. Please list only the languages your server actively supports."
|
||||||
},
|
},
|
||||||
"nags.too-many-languages.description": {
|
"nags.too-many-languages.title": {
|
||||||
"defaultMessage": "You've selected {languageCount, plural, one {# language} other {# languages}}. Please list only the languages your server actively supports."
|
"defaultMessage": "Select accurate languages"
|
||||||
},
|
},
|
||||||
"nags.too-many-languages.title": {
|
"nags.too-many-tags-server.description": {
|
||||||
"defaultMessage": "Select accurate languages"
|
"defaultMessage": "You've selected {tagCount, plural, one {# tag} other {# tags}}. Please reduce to {maxTagCount} or fewer to make sure your server appears in relevant search results."
|
||||||
},
|
},
|
||||||
"nags.too-many-tags-server.description": {
|
"nags.too-many-tags-server.title": {
|
||||||
"defaultMessage": "You've selected {tagCount, plural, one {# tag} other {# tags}}. Please reduce to {maxTagCount} or fewer to make sure your server appears in relevant search results."
|
"defaultMessage": "Select accurate tags"
|
||||||
},
|
},
|
||||||
"nags.too-many-tags-server.title": {
|
"nags.too-many-tags.description": {
|
||||||
"defaultMessage": "Select accurate tags"
|
"defaultMessage": "You've selected {tagCount, plural, one {# tag} other {# tags}}. Consider reducing to {maxTagCount} or fewer to make sure your project appears in relevant search results."
|
||||||
},
|
},
|
||||||
"nags.too-many-tags.description": {
|
"nags.too-many-tags.title": {
|
||||||
"defaultMessage": "You've selected {tagCount, plural, one {# tag} other {# tags}}. Consider reducing to {maxTagCount} or fewer to make sure your project appears in relevant search results."
|
"defaultMessage": "Select accurate tags"
|
||||||
},
|
},
|
||||||
"nags.too-many-tags.title": {
|
"nags.upload-gallery-image.description": {
|
||||||
"defaultMessage": "Select accurate tags"
|
"defaultMessage": "At least one gallery image is required to showcase the content of your {type}."
|
||||||
},
|
},
|
||||||
"nags.upload-gallery-image.description": {
|
"nags.upload-gallery-image.description-resourcepack": {
|
||||||
"defaultMessage": "At least one gallery image is required to showcase the content of your {type}."
|
"defaultMessage": "At least one gallery image is required to showcase the content of your resource pack, except for audio or localization packs. If this describes your pack, please select the appropriate tag."
|
||||||
},
|
},
|
||||||
"nags.upload-gallery-image.description-resourcepack": {
|
"nags.upload-gallery-image.description-shader": {
|
||||||
"defaultMessage": "At least one gallery image is required to showcase the content of your resource pack, except for audio or localization packs. If this describes your pack, please select the appropriate tag."
|
"defaultMessage": "At least three gallery images are required to showcase the content of your shader in a variety of situations and conditions."
|
||||||
},
|
},
|
||||||
"nags.upload-gallery-image.description-shader": {
|
"nags.upload-gallery-image.title": {
|
||||||
"defaultMessage": "At least three gallery images are required to showcase the content of your shader in a variety of situations and conditions."
|
"defaultMessage": "Upload a gallery image"
|
||||||
},
|
},
|
||||||
"nags.upload-gallery-image.title": {
|
"nags.upload-version.description": {
|
||||||
"defaultMessage": "Upload a gallery image"
|
"defaultMessage": "At least one version is required for a project to be submitted for review."
|
||||||
},
|
},
|
||||||
"nags.upload-version.description": {
|
"nags.upload-version.title": {
|
||||||
"defaultMessage": "At least one version is required for a project to be submitted for review."
|
"defaultMessage": "Upload a version"
|
||||||
},
|
},
|
||||||
"nags.upload-version.title": {
|
"nags.verify-external-links.description": {
|
||||||
"defaultMessage": "Upload a version"
|
"defaultMessage": "Some of your external links may be using domains that are inappropriate for that type of link."
|
||||||
},
|
},
|
||||||
"nags.verify-external-links.description": {
|
"nags.verify-external-links.title": {
|
||||||
"defaultMessage": "Some of your external links may be using domains that are inappropriate for that type of link."
|
"defaultMessage": "Verify external links"
|
||||||
},
|
},
|
||||||
"nags.verify-external-links.title": {
|
"nags.visit-links-settings.title": {
|
||||||
"defaultMessage": "Verify external links"
|
"defaultMessage": "Visit links settings"
|
||||||
},
|
},
|
||||||
"nags.visit-links-settings.title": {
|
"project.text-validation.description-profanity": {
|
||||||
"defaultMessage": "Visit links settings"
|
"defaultMessage": "Excessive profanity is not allowed. Detected: {values}."
|
||||||
},
|
},
|
||||||
"project.text-validation.non-standard-text": {
|
"project.text-validation.non-standard-text": {
|
||||||
"defaultMessage": "Non-standard text characters are not allowed."
|
"defaultMessage": "Non-standard text characters are not allowed."
|
||||||
},
|
},
|
||||||
"project.text-validation.profanity": {
|
"project.text-validation.profanity": {
|
||||||
"defaultMessage": "The detected profanity “{value}” is not allowed."
|
"defaultMessage": "The detected profanity “{value}” is not allowed."
|
||||||
},
|
},
|
||||||
"project.text-validation.slur": {
|
"project.text-validation.slur": {
|
||||||
"defaultMessage": "The detected slur “{value}” is not allowed."
|
"defaultMessage": "The detected slur “{value}” is not allowed."
|
||||||
},
|
},
|
||||||
"project.text-validation.summary-link": {
|
"project.text-validation.summary-link": {
|
||||||
"defaultMessage": "Links should not be included in project summaries."
|
"defaultMessage": "Links should not be included in project summaries."
|
||||||
},
|
},
|
||||||
"project.text-validation.summary-matches-title": {
|
"project.text-validation.summary-matches-title": {
|
||||||
"defaultMessage": "A project summary should not be the same as its title."
|
"defaultMessage": "A project summary should not be the same as its title."
|
||||||
},
|
},
|
||||||
"project.text-validation.summary-too-short": {
|
"project.text-validation.summary-too-short": {
|
||||||
"defaultMessage": "Your summary is {length, plural, one {# character} other {# characters}}. At least {minChars, plural, one {# character} other {# characters}} is recommended to create an informative and enticing summary."
|
"defaultMessage": "Your summary is {length, plural, one {# character} other {# characters}}. At least {minChars, plural, one {# character} other {# characters}} is recommended to create an informative and enticing summary."
|
||||||
},
|
},
|
||||||
"project.text-validation.title-game-version": {
|
"project.text-validation.title-game-version": {
|
||||||
"defaultMessage": "Project titles should not include the Minecraft version “{value}”."
|
"defaultMessage": "Project titles should not include the Minecraft version “{value}”."
|
||||||
},
|
},
|
||||||
"project.text-validation.title-loader": {
|
"project.text-validation.title-loader": {
|
||||||
"defaultMessage": "Project titles should not include the loader “{value}”."
|
"defaultMessage": "Project titles should not include the loader “{value}”."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,6 +248,44 @@ function isWordCharacter(character: string | undefined): boolean {
|
|||||||
return character !== undefined && /^[\p{L}\p{M}\p{N}_]$/u.test(character)
|
return character !== undefined && /^[\p{L}\p{M}\p{N}_]$/u.test(character)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LEET_SPEAK_CHARACTERS = new Set(['@', '(', '|', '!', '/', '$'])
|
||||||
|
|
||||||
|
function isObfuscatedWordCharacter(character: string): boolean {
|
||||||
|
return isWordCharacter(character) || LEET_SPEAK_CHARACTERS.has(character)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCharacterByCharacterObfuscation(text: string): boolean {
|
||||||
|
const chunkLengths: number[] = []
|
||||||
|
let currentChunkLength = 0
|
||||||
|
let hasInvisibleSeparator = false
|
||||||
|
let hasVisibleSeparator = false
|
||||||
|
|
||||||
|
for (const character of text) {
|
||||||
|
if (isObfuscatedWordCharacter(character)) {
|
||||||
|
currentChunkLength++
|
||||||
|
} else {
|
||||||
|
if (/^\p{Cf}$/u.test(character)) {
|
||||||
|
hasInvisibleSeparator = true
|
||||||
|
} else {
|
||||||
|
hasVisibleSeparator = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentChunkLength > 0) {
|
||||||
|
chunkLengths.push(currentChunkLength)
|
||||||
|
currentChunkLength = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentChunkLength > 0) chunkLengths.push(currentChunkLength)
|
||||||
|
|
||||||
|
return (
|
||||||
|
chunkLengths.length > 1 &&
|
||||||
|
((hasInvisibleSeparator && !hasVisibleSeparator) ||
|
||||||
|
chunkLengths.every((length) => length === 1))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function getCharacterBefore(text: string, index: number): string | undefined {
|
function getCharacterBefore(text: string, index: number): string | undefined {
|
||||||
if (index <= 0) return undefined
|
if (index <= 0) return undefined
|
||||||
|
|
||||||
@@ -282,27 +320,45 @@ export function createProfanityValidator(
|
|||||||
|
|
||||||
return { kind: pattern.kind, term }
|
return { kind: pattern.kind, term }
|
||||||
})
|
})
|
||||||
const matcher = new RegExpMatcher({
|
const blacklistedTerms = entries.map(({ term }, id) => ({ id, pattern: parseRawPattern(term) }))
|
||||||
blacklistedTerms: entries.map(({ term }, id) => ({ id, pattern: parseRawPattern(term) })),
|
const baseTransformers = [
|
||||||
|
resolveConfusablesTransformer(),
|
||||||
|
resolveLeetSpeakTransformer(),
|
||||||
|
toAsciiLowerCaseTransformer(),
|
||||||
|
]
|
||||||
|
const duplicateTransformer = () =>
|
||||||
|
collapseDuplicatesTransformer({
|
||||||
|
customThresholds: getDuplicateThresholds(entries.map(({ term }) => term)),
|
||||||
|
})
|
||||||
|
const strictMatcher = new RegExpMatcher({
|
||||||
|
blacklistedTerms,
|
||||||
|
blacklistMatcherTransformers: [...baseTransformers, duplicateTransformer()],
|
||||||
|
})
|
||||||
|
const separatorMatcher = new RegExpMatcher({
|
||||||
|
blacklistedTerms,
|
||||||
blacklistMatcherTransformers: [
|
blacklistMatcherTransformers: [
|
||||||
resolveConfusablesTransformer(),
|
...baseTransformers,
|
||||||
resolveLeetSpeakTransformer(),
|
|
||||||
toAsciiLowerCaseTransformer(),
|
|
||||||
skipNonAlphabeticTransformer(),
|
skipNonAlphabeticTransformer(),
|
||||||
collapseDuplicatesTransformer({
|
duplicateTransformer(),
|
||||||
customThresholds: getDuplicateThresholds(entries.map(({ term }) => term)),
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
function findAll(text: string): ProfanityMatch[] {
|
function findAll(text: string): ProfanityMatch[] {
|
||||||
const matches: ProfanityMatch[] = []
|
const matches: ProfanityMatch[] = []
|
||||||
|
const strictMatches = new Set(
|
||||||
|
[...strictMatcher.getAllMatches(text, true)].map(
|
||||||
|
(match) => `${match.termId}:${match.startIndex}:${match.endIndex}`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
for (const match of matcher.getAllMatches(text, true)) {
|
for (const match of separatorMatcher.getAllMatches(text, true)) {
|
||||||
const profanityPattern = entries[match.termId]
|
const profanityPattern = entries[match.termId]
|
||||||
const end = match.endIndex + 1
|
const end = match.endIndex + 1
|
||||||
|
const rawText = text.slice(match.startIndex, end)
|
||||||
|
const matchKey = `${match.termId}:${match.startIndex}:${match.endIndex}`
|
||||||
if (
|
if (
|
||||||
!profanityPattern ||
|
!profanityPattern ||
|
||||||
|
(!strictMatches.has(matchKey) && !isCharacterByCharacterObfuscation(rawText)) ||
|
||||||
!isWholeWordMatch(text, match.startIndex, end) ||
|
!isWholeWordMatch(text, match.startIndex, end) ||
|
||||||
match.startIndex < (matches.at(-1)?.end ?? 0)
|
match.startIndex < (matches.at(-1)?.end ?? 0)
|
||||||
) {
|
) {
|
||||||
@@ -311,7 +367,7 @@ export function createProfanityValidator(
|
|||||||
|
|
||||||
matches.push({
|
matches.push({
|
||||||
...profanityPattern,
|
...profanityPattern,
|
||||||
rawText: text.slice(match.startIndex, end),
|
rawText,
|
||||||
start: match.startIndex,
|
start: match.startIndex,
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ test('does not join ordinary words or match inside larger words', () => {
|
|||||||
assert.equal(validateProfanity('s e m e n').valid, false)
|
assert.equal(validateProfanity('s e m e n').valid, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('does not join multi-character chunks into a slur', () => {
|
||||||
|
assert.equal(validateProfanity('6000 -> OK').valid, true)
|
||||||
|
assert.equal(validateProfanity('6000 OK').valid, true)
|
||||||
|
})
|
||||||
|
|
||||||
test('rejects any uncensored configured profanity', () => {
|
test('rejects any uncensored configured profanity', () => {
|
||||||
assert.equal(validateProfanity('A clean project').valid, true)
|
assert.equal(validateProfanity('A clean project').valid, true)
|
||||||
assert.equal(validateProfanity('This is shit').valid, false)
|
assert.equal(validateProfanity('This is shit').valid, false)
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ const messages = defineMessages({
|
|||||||
id: 'project.text-validation.profanity',
|
id: 'project.text-validation.profanity',
|
||||||
defaultMessage: 'The detected profanity “{value}” is not allowed.',
|
defaultMessage: 'The detected profanity “{value}” is not allowed.',
|
||||||
},
|
},
|
||||||
|
descriptionProfanity: {
|
||||||
|
id: 'project.text-validation.description-profanity',
|
||||||
|
defaultMessage: 'Excessive profanity is not allowed. Detected: {values}',
|
||||||
|
},
|
||||||
nonStandardText: {
|
nonStandardText: {
|
||||||
id: 'project.text-validation.non-standard-text',
|
id: 'project.text-validation.non-standard-text',
|
||||||
defaultMessage: 'Non-standard text characters are not allowed.',
|
defaultMessage: 'Non-standard text characters are not allowed.',
|
||||||
@@ -398,6 +402,20 @@ export function validateProjectDescription(
|
|||||||
maxProfanityCount: DESCRIPTION_MAX_PROFANITY_COUNT,
|
maxProfanityCount: DESCRIPTION_MAX_PROFANITY_COUNT,
|
||||||
nonStandardTextFailureThreshold: DESCRIPTION_NON_STANDARD_TEXT_FAILURE_THRESHOLD,
|
nonStandardTextFailureThreshold: DESCRIPTION_NON_STANDARD_TEXT_FAILURE_THRESHOLD,
|
||||||
})
|
})
|
||||||
|
if (results[0]?.code === 'text-profanity') {
|
||||||
|
const detectedValues = validateProfanity(description ?? '')
|
||||||
|
.matches.filter((match) => match.kind === 'profanity')
|
||||||
|
.map((match) => `"${match.rawText}"`)
|
||||||
|
.join(', ')
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...results[0],
|
||||||
|
message: messages.descriptionProfanity,
|
||||||
|
values: { values: detectedValues },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
if (results.length > 0) return results
|
if (results.length > 0) return results
|
||||||
|
|
||||||
const normalizedDescription = normalizeProjectFieldText(description ?? '')
|
const normalizedDescription = normalizeProjectFieldText(description ?? '')
|
||||||
|
|||||||
@@ -158,10 +158,13 @@ test('allows one profanity match in descriptions but rejects a second match or a
|
|||||||
code: 'text-profanity',
|
code: 'text-profanity',
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
message: {
|
message: {
|
||||||
id: 'project.text-validation.profanity',
|
id: 'project.text-validation.description-profanity',
|
||||||
defaultMessage: 'The detected profanity “{value}” is not allowed.',
|
defaultMessage: 'Excessive profanity is not allowed. Detected: {values}',
|
||||||
},
|
},
|
||||||
values: { value: 'FUCK' },
|
values: { values: '"shit", "FUCK"' },
|
||||||
|
})
|
||||||
|
assert.deepEqual(validateProjectDescription(`${description} shit FUCK bastard`)[0]?.values, {
|
||||||
|
values: '"shit", "FUCK", "bastard"',
|
||||||
})
|
})
|
||||||
assert.equal(validateProjectDescription(`${description} nigga`)[0]?.code, 'text-slur')
|
assert.equal(validateProjectDescription(`${description} nigga`)[0]?.code, 'text-slur')
|
||||||
assert.equal(validateProjectDescription(`${description} nigger`)[0]?.code, 'text-slur')
|
assert.equal(validateProjectDescription(`${description} nigger`)[0]?.code, 'text-slur')
|
||||||
|
|||||||
Reference in New Issue
Block a user