diff --git a/apps/frontend/src/composables/project-slug-suggestions.ts b/apps/frontend/src/composables/project-slug-suggestions.ts index 4c0476864a..519dbb2480 100644 --- a/apps/frontend/src/composables/project-slug-suggestions.ts +++ b/apps/frontend/src/composables/project-slug-suggestions.ts @@ -5,8 +5,7 @@ import { type MaybeRefOrGetter, onScopeDispose, ref, toValue, watch } from 'vue' const STALE_TIME = 1000 * 60 * 5 const CHECK_DEBOUNCE = 300 -const PROJECT_SLUG_UNSAFE_CHARS = /[^a-zA-Z0-9._-]/g -const PROJECT_SLUG_REGEX = /^[a-zA-Z0-9._-]{3,64}$/ +const PROJECT_SLUG_REGEX = /^[a-z0-9._-]{3,64}$/ interface ProjectSlugSuggestionOptions { title: MaybeRefOrGetter @@ -19,13 +18,20 @@ export function generateUrlSlug(value: string) { return value .trim() .toLowerCase() - .replaceAll(' ', '-') - .replaceAll(PROJECT_SLUG_UNSAFE_CHARS, '') - .replaceAll(/--+/gm, '-') + .replaceAll('+', '-plus') + .replaceAll(/[!"'`]/g, '') + .replaceAll(/[^a-z0-9._-]+/g, '-') + .replaceAll(/-+/g, '-') + .replaceAll(/^-+|-+$/g, '') } function isValidProjectSlug(value: string) { - return PROJECT_SLUG_REGEX.test(value) + return ( + PROJECT_SLUG_REGEX.test(value) && + !value.startsWith('-') && + !value.endsWith('-') && + !value.includes('--') + ) } function generateProjectSlugSuggestions(title: string, username?: string | null) { @@ -40,7 +46,7 @@ function generateProjectSlugSuggestions(title: string, username?: string | null) const usernameSlug = username ? generateUrlSlug(username) : '' let withUsername = '' - if (titleSlug && usernameSlug) { + if (titleSlug && usernameSlug && usernameSlug.length <= 62) { const availableTitleLength = 64 - usernameSlug.length - 1 const truncatedTitle = titleSlug.slice(0, availableTitleLength).replace(/-+$/, '') if (truncatedTitle) withUsername = `${truncatedTitle}-${usernameSlug}`