diff --git a/app/composables/useMarkdown.ts b/app/composables/useMarkdown.ts index 4f3cfe730..2af91f129 100644 --- a/app/composables/useMarkdown.ts +++ b/app/composables/useMarkdown.ts @@ -1,3 +1,4 @@ +import { regExpEscape } from '@li/regexp-escape-polyfill' import { decodeHtmlEntities } from '~/utils/formatters' interface UseMarkdownOptions { @@ -45,10 +46,8 @@ function stripAndEscapeHtml(text: string, packageName?: string): string { stripped = stripped.trim() // Collapse multiple whitespace into single space stripped = stripped.replace(/\s+/g, ' ') - // Escape special regex characters in package name - const escapedName = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Match package name at the start, optionally followed by: space, dash, colon, hyphen, or just space - const namePattern = new RegExp(`^${escapedName}\\s*[-:—]?\\s*`, 'i') + const namePattern = new RegExp(`^${regExpEscape(packageName)}\\s*[-:—]?\\s*`, 'i') stripped = stripped.replace(namePattern, '').trim() } diff --git a/app/composables/useStructuredFilters.ts b/app/composables/useStructuredFilters.ts index a33d4d150..bba08667c 100644 --- a/app/composables/useStructuredFilters.ts +++ b/app/composables/useStructuredFilters.ts @@ -17,6 +17,7 @@ import { parseSortOption, UPDATED_WITHIN_OPTIONS, } from '#shared/types/preferences' +import { regExpEscape } from '@li/regexp-escape-polyfill' /** * Parsed search operators from text input @@ -412,7 +413,9 @@ export function useStructuredFilters(options: UseStructuredFiltersOptions) { function removeKeyword(keyword: string) { filters.value.keywords = filters.value.keywords.filter(k => k !== keyword) - const newQ = searchQuery.value.replace(new RegExp(`keyword:${keyword}($| )`, 'g'), '').trim() + const newQ = searchQuery.value + .replace(new RegExp(`keyword:${regExpEscape(keyword)}($| )`, 'g'), '') + .trim() router.replace({ query: { ...route.query, q: newQ || undefined } }) if (searchQueryModel) searchQueryModel.value = newQ } diff --git a/package.json b/package.json index 86c266870..f40f521a6 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@iconify-json/svg-spinners": "1.2.4", "@iconify-json/vscode-icons": "1.2.40", "@intlify/shared": "11.2.8", + "@li/regexp-escape-polyfill": "jsr:0.3.4", "@lunariajs/core": "https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@f07e1a3", "@nuxt/a11y": "1.0.0-alpha.1", "@nuxt/fonts": "0.13.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d90982664..aca71d386 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: '@intlify/shared': specifier: 11.2.8 version: 11.2.8 + '@li/regexp-escape-polyfill': + specifier: jsr:0.3.4 + version: '@jsr/li__regexp-escape-polyfill@0.3.4' '@lunariajs/core': specifier: https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@f07e1a3 version: https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@f07e1a3 @@ -1857,6 +1860,9 @@ packages: '@jsr/deno__graph@0.86.9': resolution: {integrity: sha512-+qrrma5/bL+hcG20mfaEeC8SLopqoyd1RjcKFMRu++3SAXyrTKuvuIjBJCn/NyN7X+kV+QrJG67BCHX38Rzw+g==, tarball: https://npm.jsr.io/~/11/@jsr/deno__graph/0.86.9.tgz} + '@jsr/li__regexp-escape-polyfill@0.3.4': + resolution: {integrity: sha512-UGHzM9zAlRt0DUQIgsXAuXBd9jdzJ3t1v+fHsIcIj2fbUKplO9A8v2FE/w9KZo8hs79n5IamuBvqCTndGADM2Q==, tarball: https://npm.jsr.io/~/11/@jsr/li__regexp-escape-polyfill/0.3.4.tgz} + '@jsr/std__bytes@1.0.6': resolution: {integrity: sha512-St6yKggjFGhxS52IFLJWvkchRFbAKg2Xh8UxA4S1EGz7GJ2Ui+ssDDldj/w2c8vCxvl6qgR0HaYbKeFJNqujmA==, tarball: https://npm.jsr.io/~/11/@jsr/std__bytes/1.0.6.tgz} @@ -11425,6 +11431,8 @@ snapshots: '@jsr/deno__graph@0.86.9': {} + '@jsr/li__regexp-escape-polyfill@0.3.4': {} + '@jsr/std__bytes@1.0.6': {} '@jsr/std__fmt@1.0.9': {} diff --git a/shared/utils/dev-dependency.ts b/shared/utils/dev-dependency.ts index f45449ba5..ae30d8855 100644 --- a/shared/utils/dev-dependency.ts +++ b/shared/utils/dev-dependency.ts @@ -1,3 +1,5 @@ +import { regExpEscape } from '@li/regexp-escape-polyfill' + export type DevDependencySuggestionReason = 'known-package' | 'readme-hint' export interface DevDependencySuggestion { @@ -59,15 +61,11 @@ function isKnownDevDependencyPackage(packageName: string): boolean { ) } -function escapeRegExp(text: string): string { - return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') -} - function hasReadmeDevInstallHint(packageName: string, readmeContent?: string | null): boolean { if (!readmeContent) return false - const escapedName = escapeRegExp(packageName) - const escapedNpmName = escapeRegExp(`npm:${packageName}`) + const escapedName = regExpEscape(packageName) + const escapedNpmName = regExpEscape(`npm:${packageName}`) const packageSpec = `(?:${escapedName}|${escapedNpmName})(?:@[\\w.-]+)?` const patterns = [