diff --git a/apps/admin-web/package.json b/apps/admin-web/package.json index 86fda24..5004d93 100644 --- a/apps/admin-web/package.json +++ b/apps/admin-web/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "vite", + "test": "vitest run", "build": "vue-tsc --noEmit && vite build", "build:analyze": "vue-tsc --noEmit && ANALYZE=true vite build", "preview": "vite preview", @@ -43,6 +44,7 @@ "typescript": "^5.9.3", "vite": "^7.3.2", "vite-plugin-compression2": "^2.5.3", + "vitest": "^4.1.5", "vue-tsc": "^3.2.6" } } diff --git a/apps/admin-web/src/lib/monitoring-answer-markdown.test.ts b/apps/admin-web/src/lib/monitoring-answer-markdown.test.ts new file mode 100644 index 0000000..fd8d135 --- /dev/null +++ b/apps/admin-web/src/lib/monitoring-answer-markdown.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest' + +import { normalizeLooseMarkdownLine } from './monitoring-answer-markdown' + +describe('normalizeLooseMarkdownLine', () => { + it('turns a bold-prefixed model bullet into standard Markdown', () => { + expect(normalizeLooseMarkdownLine('**-拾光里家居**:15年本土营')).toBe( + '- **拾光里家居**:15年本土营', + ) + expect(normalizeLooseMarkdownLine(' **- 拾光里家居**:15年本土营')).toBe( + ' - **拾光里家居**:15年本土营', + ) + }) + + it('keeps ordinary bold text unchanged', () => { + expect(normalizeLooseMarkdownLine('**本地性价比高**,老板就在跑')).toBe( + '**本地性价比高**,老板就在跑', + ) + }) + + it('keeps normalizing compact Markdown markers', () => { + expect(normalizeLooseMarkdownLine('-拾光里家居')).toBe('- 拾光里家居') + expect(normalizeLooseMarkdownLine('2.看封边')).toBe('2. 看封边') + expect(normalizeLooseMarkdownLine('##推荐清单')).toBe('## 推荐清单') + }) +}) diff --git a/apps/admin-web/src/lib/monitoring-answer-markdown.ts b/apps/admin-web/src/lib/monitoring-answer-markdown.ts new file mode 100644 index 0000000..2d07098 --- /dev/null +++ b/apps/admin-web/src/lib/monitoring-answer-markdown.ts @@ -0,0 +1,18 @@ +export function normalizeLooseMarkdownLine(line: string): string { + const headingNormalizedLine = line + .replace(/^[\u200b-\u200f\ufeff]+/, '') + .replace(/^([ \t]{0,12})(?:\\#|#|#|#)/i, '$1#') + const answerScopedHeading = headingNormalizedLine.replace( + /^([ \t]{0,12})#{1,2}(?!#)\s*(?=\S)/, + '$1### ', + ) + const modelBulletNormalized = answerScopedHeading.replace(/^(\s*)\*\*-\s*(?=\S)/, '$1- **') + + return modelBulletNormalized + .replace(/^([ \t]{0,12})(#{1,6})(?=\S)/, '$1$2 ') + .replace(/^([ \t]{0,12}#{1,6}\s+)#{1,6}\s*/, '$1') + .replace(/^([ \t]{0,12}#{1,6}\s+\d+[.)、])(?=\S)/, '$1 ') + .replace(/^(\s*)([-+])(?=\S)/, '$1$2 ') + .replace(/^(\s*)\*(?!\*)(?=\S)/, '$1* ') + .replace(/^(\s*)(\d{1,3})([.)])(?=\S)/, '$1$2$3 ') +} diff --git a/apps/admin-web/src/views/TrackingQuestionDetailView.vue b/apps/admin-web/src/views/TrackingQuestionDetailView.vue index e2d90df..e6b31cb 100644 --- a/apps/admin-web/src/views/TrackingQuestionDetailView.vue +++ b/apps/admin-web/src/views/TrackingQuestionDetailView.vue @@ -17,6 +17,7 @@ import MarkdownPreview from '@/components/MarkdownPreview.vue' import { monitoringApi } from '@/lib/api' import { formatDateTime } from '@/lib/display' import { formatError } from '@/lib/errors' +import { normalizeLooseMarkdownLine } from '@/lib/monitoring-answer-markdown' import { isMonitoringPlatformId, normalizeMonitoringPlatformId } from '@/lib/monitoring-platforms' import { useCompanyStore } from '@/stores/company' @@ -790,23 +791,6 @@ function sanitizeAnswerBody(answerText: string | null | undefined): string | nul return sanitized || null } -function normalizeLooseMarkdownLine(line: string): string { - const headingNormalizedLine = line - .replace(/^[\u200b-\u200f\ufeff]+/, '') - .replace(/^([ \t]{0,12})(?:\\#|#|#|#)/i, '$1#') - const answerScopedHeading = headingNormalizedLine.replace( - /^([ \t]{0,12})#{1,2}(?!#)\s*(?=\S)/, - '$1### ', - ) - - return answerScopedHeading - .replace(/^([ \t]{0,12})(#{1,6})(?=\S)/, '$1$2 ') - .replace(/^([ \t]{0,12}#{1,6}\s+)#{1,6}\s*/, '$1') - .replace(/^([ \t]{0,12}#{1,6}\s+\d+[.)、])(?=\S)/, '$1 ') - .replace(/^(\s*)([-*+])(?=\S)/, '$1$2 ') - .replace(/^(\s*)(\d{1,3})([.)])(?=\S)/, '$1$2$3 ') -} - function countMarkdownTablePipes(line: string): number { return (line.match(/\|/g) ?? []).length } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bae640b..91e46bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,6 +135,9 @@ importers: vite-plugin-compression2: specifier: ^2.5.3 version: 2.5.3(rollup@4.60.2) + vitest: + specifier: ^4.1.5 + version: 4.1.5(@types/node@24.12.0)(vite@7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)) vue-tsc: specifier: ^3.2.6 version: 3.2.6(typescript@5.9.3)