Files
cc-web/public/markdown-normalizer.js
2026-09-28 08:13:09 +08:00

89 lines
2.9 KiB
JavaScript

(function (root, factory) {
const normalize = factory();
if (typeof module === 'object' && module.exports) module.exports = normalize;
if (root) root.ccNormalizeMarkdownText = normalize;
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
const CODE_LIKE_PATTERN = /(?:=>|[{};]|^\s*(?:const|let|var|function|class|return|import|export|def|print|console\.)\b|<\/?[a-z][^>]*>)/im;
const FENCE_PATTERN = /^ {0,3}(`{3,}|~{3,})/;
function indentationWidth(line) {
let width = 0;
for (const character of line.match(/^[ \t]*/)?.[0] || '') {
width = character === '\t' ? width + (4 - (width % 4)) : width + 1;
}
return width;
}
function removeIndent(line, width) {
let column = 0;
let index = 0;
while (index < line.length && column < width) {
const character = line[index];
if (character !== ' ' && character !== '\t') break;
const nextColumn = character === '\t' ? column + (4 - (column % 4)) : column + 1;
index += 1;
if (nextColumn > width) return `${' '.repeat(nextColumn - width)}${line.slice(index)}`;
column = nextColumn;
}
return line.slice(index);
}
function isNaturalLanguage(lines) {
const text = lines.map((line) => line.trim()).join(' ').trim();
if (!text || CODE_LIKE_PATTERN.test(text)) return false;
const cjkCount = (text.match(/[\u3400-\u9fff]/g) || []).length;
if (cjkCount >= 4) return /[。!?;:]/.test(text);
const wordCount = (text.match(/[a-z][a-z'-]*/gi) || []).length;
return wordCount >= 8 && /[.!?]/.test(text);
}
function normalizeParagraph(lines) {
const contentLines = lines.filter((line) => line.trim());
if (contentLines.length === 0) return lines;
const commonIndent = Math.min(...contentLines.map(indentationWidth));
if (commonIndent < 4 || !isNaturalLanguage(contentLines)) return lines;
return lines.map((line) => (line.trim() ? removeIndent(line, commonIndent) : line));
}
return function normalizeMarkdownText(value) {
const lines = String(value ?? '').split('\n');
const output = [];
let paragraph = [];
let fence = null;
function flushParagraph() {
if (paragraph.length > 0) output.push(...normalizeParagraph(paragraph));
paragraph = [];
}
for (const line of lines) {
const fenceMatch = line.match(FENCE_PATTERN);
if (fence) {
output.push(line);
if (fenceMatch && fenceMatch[1][0] === fence.character && fenceMatch[1].length >= fence.length) {
fence = null;
}
continue;
}
if (fenceMatch) {
flushParagraph();
fence = { character: fenceMatch[1][0], length: fenceMatch[1].length };
output.push(line);
} else if (!line.trim() || /^\s{0,3}<\/?[a-z][^>]*>/i.test(line)) {
flushParagraph();
output.push(line);
} else {
paragraph.push(line);
}
}
flushParagraph();
return output.join('\n');
};
});