refactor(admin-web): move answer markdown normalization to a tested lib
Extract normalizeLooseMarkdownLine from TrackingQuestionDetailView into a shared monitoring-answer-markdown module and harden it (model bullet `**-` and `*` handling). Add a vitest runner and unit tests for the normalization rules. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
"test": "vitest run",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"build:analyze": "vue-tsc --noEmit && ANALYZE=true vite build",
|
"build:analyze": "vue-tsc --noEmit && ANALYZE=true vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.3.2",
|
"vite": "^7.3.2",
|
||||||
"vite-plugin-compression2": "^2.5.3",
|
"vite-plugin-compression2": "^2.5.3",
|
||||||
|
"vitest": "^4.1.5",
|
||||||
"vue-tsc": "^3.2.6"
|
"vue-tsc": "^3.2.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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('## 推荐清单')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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 ')
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import MarkdownPreview from '@/components/MarkdownPreview.vue'
|
|||||||
import { monitoringApi } from '@/lib/api'
|
import { monitoringApi } from '@/lib/api'
|
||||||
import { formatDateTime } from '@/lib/display'
|
import { formatDateTime } from '@/lib/display'
|
||||||
import { formatError } from '@/lib/errors'
|
import { formatError } from '@/lib/errors'
|
||||||
|
import { normalizeLooseMarkdownLine } from '@/lib/monitoring-answer-markdown'
|
||||||
import { isMonitoringPlatformId, normalizeMonitoringPlatformId } from '@/lib/monitoring-platforms'
|
import { isMonitoringPlatformId, normalizeMonitoringPlatformId } from '@/lib/monitoring-platforms'
|
||||||
import { useCompanyStore } from '@/stores/company'
|
import { useCompanyStore } from '@/stores/company'
|
||||||
|
|
||||||
@@ -790,23 +791,6 @@ function sanitizeAnswerBody(answerText: string | null | undefined): string | nul
|
|||||||
return sanitized || null
|
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 {
|
function countMarkdownTablePipes(line: string): number {
|
||||||
return (line.match(/\|/g) ?? []).length
|
return (line.match(/\|/g) ?? []).length
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+3
@@ -135,6 +135,9 @@ importers:
|
|||||||
vite-plugin-compression2:
|
vite-plugin-compression2:
|
||||||
specifier: ^2.5.3
|
specifier: ^2.5.3
|
||||||
version: 2.5.3(rollup@4.60.2)
|
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:
|
vue-tsc:
|
||||||
specifier: ^3.2.6
|
specifier: ^3.2.6
|
||||||
version: 3.2.6(typescript@5.9.3)
|
version: 3.2.6(typescript@5.9.3)
|
||||||
|
|||||||
Reference in New Issue
Block a user