chore(frontend): introduce prettier + eslint and prune unused code
- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports) - Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier - Add root scripts: format, format:check, lint, lint:fix - Reformat 257 files across admin-web, ops-web, desktop-client, packages - Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters - Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex - Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,274 +1,279 @@
|
||||
const BAIJIAHAO_TABLE_STYLE = [
|
||||
"width: 100%",
|
||||
"border-collapse: collapse",
|
||||
"border-spacing: 0",
|
||||
"margin: 12px 0",
|
||||
"font-size: 15px",
|
||||
"line-height: 1.6",
|
||||
"color: #1f2937",
|
||||
].join("; ");
|
||||
'width: 100%',
|
||||
'border-collapse: collapse',
|
||||
'border-spacing: 0',
|
||||
'margin: 12px 0',
|
||||
'font-size: 15px',
|
||||
'line-height: 1.6',
|
||||
'color: #1f2937',
|
||||
].join('; ')
|
||||
|
||||
const BAIJIAHAO_TABLE_CELL_STYLE = [
|
||||
"border: 1px solid #d9d9d9",
|
||||
"padding: 8px 10px",
|
||||
"vertical-align: middle",
|
||||
"word-break: break-word",
|
||||
"min-width: 64px",
|
||||
].join("; ");
|
||||
'border: 1px solid #d9d9d9',
|
||||
'padding: 8px 10px',
|
||||
'vertical-align: middle',
|
||||
'word-break: break-word',
|
||||
'min-width: 64px',
|
||||
].join('; ')
|
||||
|
||||
const BAIJIAHAO_TABLE_HEADER_CELL_STYLE = [
|
||||
BAIJIAHAO_TABLE_CELL_STYLE,
|
||||
"background-color: #f5f7fa",
|
||||
"font-weight: 600",
|
||||
].join("; ");
|
||||
'background-color: #f5f7fa',
|
||||
'font-weight: 600',
|
||||
].join('; ')
|
||||
|
||||
const TABLE_PARAGRAPH_CELL_SEPARATOR = " ";
|
||||
const TABLE_PARAGRAPH_CELL_SEPARATOR = ' '
|
||||
|
||||
const BAIJIAHAO_TABLE_ALLOWED_ATTRS = ["colspan", "rowspan", "align", "valign", "width"];
|
||||
const BAIJIAHAO_TABLE_ALLOWED_ATTRS = ['colspan', 'rowspan', 'align', 'valign', 'width']
|
||||
|
||||
type ParsedParagraphTableRow = string[];
|
||||
type ParsedParagraphTableRow = string[]
|
||||
|
||||
function hasUnescapedPipe(value: string): boolean {
|
||||
let escaped = false;
|
||||
let escaped = false
|
||||
for (const char of value) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
if (char === "\\") {
|
||||
escaped = true;
|
||||
continue;
|
||||
if (char === '\\') {
|
||||
escaped = true
|
||||
continue
|
||||
}
|
||||
if (char === "|") {
|
||||
return true;
|
||||
if (char === '|') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
function splitMarkdownTableCells(value: string): string[] {
|
||||
const cells: string[] = [];
|
||||
let cell = "";
|
||||
let escaped = false;
|
||||
const trimmed = value.trim();
|
||||
const source = trimmed.startsWith("|") ? trimmed.slice(1) : trimmed;
|
||||
const content = source.endsWith("|") && !source.endsWith("\\|") ? source.slice(0, -1) : source;
|
||||
const cells: string[] = []
|
||||
let cell = ''
|
||||
let escaped = false
|
||||
const trimmed = value.trim()
|
||||
const source = trimmed.startsWith('|') ? trimmed.slice(1) : trimmed
|
||||
const content = source.endsWith('|') && !source.endsWith('\\|') ? source.slice(0, -1) : source
|
||||
|
||||
for (const char of content) {
|
||||
if (escaped) {
|
||||
cell += char;
|
||||
escaped = false;
|
||||
continue;
|
||||
cell += char
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
if (char === "\\") {
|
||||
escaped = true;
|
||||
cell += char;
|
||||
continue;
|
||||
if (char === '\\') {
|
||||
escaped = true
|
||||
cell += char
|
||||
continue
|
||||
}
|
||||
if (char === "|") {
|
||||
cells.push(cell.trim());
|
||||
cell = "";
|
||||
continue;
|
||||
if (char === '|') {
|
||||
cells.push(cell.trim())
|
||||
cell = ''
|
||||
continue
|
||||
}
|
||||
cell += char;
|
||||
cell += char
|
||||
}
|
||||
|
||||
cells.push(cell.trim());
|
||||
return cells;
|
||||
cells.push(cell.trim())
|
||||
return cells
|
||||
}
|
||||
|
||||
function isMarkdownTableRow(value: string): boolean {
|
||||
const trimmed = value.trim();
|
||||
return trimmed !== "" && hasUnescapedPipe(trimmed) && splitMarkdownTableCells(trimmed).length >= 2;
|
||||
const trimmed = value.trim()
|
||||
return trimmed !== '' && hasUnescapedPipe(trimmed) && splitMarkdownTableCells(trimmed).length >= 2
|
||||
}
|
||||
|
||||
function isMarkdownTableSeparator(value: string): boolean {
|
||||
const cells = splitMarkdownTableCells(value).filter(Boolean);
|
||||
return cells.length >= 2 && cells.every((cell) => /^:?-{2,}:?$/.test(cell.trim()));
|
||||
const cells = splitMarkdownTableCells(value).filter(Boolean)
|
||||
return cells.length >= 2 && cells.every((cell) => /^:?-{2,}:?$/.test(cell.trim()))
|
||||
}
|
||||
|
||||
function isMarkdownTableStart(lines: string[], index: number): boolean {
|
||||
return isMarkdownTableRow(lines[index] ?? "") && isMarkdownTableSeparator(lines[index + 1] ?? "");
|
||||
return isMarkdownTableRow(lines[index] ?? '') && isMarkdownTableSeparator(lines[index + 1] ?? '')
|
||||
}
|
||||
|
||||
function isFenceBoundary(value: string): boolean {
|
||||
return /^\s*(```|~~~)/.test(value);
|
||||
return /^\s*(```|~~~)/.test(value)
|
||||
}
|
||||
|
||||
export function prepareBaijiahaoMarkdown(markdown: string): string {
|
||||
const source = markdown.replace(/\r\n?/g, "\n").trim();
|
||||
const source = markdown.replace(/\r\n?/g, '\n').trim()
|
||||
if (!source) {
|
||||
return "";
|
||||
return ''
|
||||
}
|
||||
|
||||
const lines = source.split("\n");
|
||||
const output: string[] = [];
|
||||
let inCodeFence = false;
|
||||
const lines = source.split('\n')
|
||||
const output: string[] = []
|
||||
let inCodeFence = false
|
||||
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
const line = lines[index] ?? "";
|
||||
const line = lines[index] ?? ''
|
||||
if (isFenceBoundary(line)) {
|
||||
inCodeFence = !inCodeFence;
|
||||
output.push(line);
|
||||
continue;
|
||||
inCodeFence = !inCodeFence
|
||||
output.push(line)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!inCodeFence && isMarkdownTableStart(lines, index)) {
|
||||
while (output.length > 0 && output[output.length - 1]?.trim() === "") {
|
||||
output.pop();
|
||||
while (output.length > 0 && output[output.length - 1]?.trim() === '') {
|
||||
output.pop()
|
||||
}
|
||||
if (/^\s*#{1,6}\s*$/.test(output[output.length - 1] ?? "")) {
|
||||
output.pop();
|
||||
if (/^\s*#{1,6}\s*$/.test(output[output.length - 1] ?? '')) {
|
||||
output.pop()
|
||||
}
|
||||
if (output.length > 0 && output[output.length - 1]?.trim() !== "") {
|
||||
output.push("");
|
||||
if (output.length > 0 && output[output.length - 1]?.trim() !== '') {
|
||||
output.push('')
|
||||
}
|
||||
|
||||
output.push(lines[index] ?? "");
|
||||
output.push(lines[index + 1] ?? "");
|
||||
index += 2;
|
||||
output.push(lines[index] ?? '')
|
||||
output.push(lines[index + 1] ?? '')
|
||||
index += 2
|
||||
|
||||
while (index < lines.length && isMarkdownTableRow(lines[index] ?? "")) {
|
||||
output.push(lines[index] ?? "");
|
||||
index += 1;
|
||||
while (index < lines.length && isMarkdownTableRow(lines[index] ?? '')) {
|
||||
output.push(lines[index] ?? '')
|
||||
index += 1
|
||||
}
|
||||
|
||||
if (index < lines.length && lines[index]?.trim() !== "") {
|
||||
output.push("");
|
||||
if (index < lines.length && lines[index]?.trim() !== '') {
|
||||
output.push('')
|
||||
}
|
||||
index -= 1;
|
||||
continue;
|
||||
index -= 1
|
||||
continue
|
||||
}
|
||||
|
||||
output.push(line);
|
||||
output.push(line)
|
||||
}
|
||||
|
||||
return output.join("\n").trim();
|
||||
return output.join('\n').trim()
|
||||
}
|
||||
|
||||
function escapeAttribute(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
}
|
||||
|
||||
function extractAttribute(sourceTag: string, attributeName: string): string | null {
|
||||
const matcher = new RegExp(`\\s${attributeName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'>]+))`, "i");
|
||||
const match = matcher.exec(sourceTag);
|
||||
const value = match?.[1] ?? match?.[2] ?? match?.[3] ?? "";
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed : null;
|
||||
const matcher = new RegExp(
|
||||
`\\s${attributeName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'>]+))`,
|
||||
'i',
|
||||
)
|
||||
const match = matcher.exec(sourceTag)
|
||||
const value = match?.[1] ?? match?.[2] ?? match?.[3] ?? ''
|
||||
const trimmed = value.trim()
|
||||
return trimmed ? trimmed : null
|
||||
}
|
||||
|
||||
function allowedTableAttributes(sourceTag: string): string {
|
||||
return BAIJIAHAO_TABLE_ALLOWED_ATTRS
|
||||
.map((attributeName) => {
|
||||
const value = extractAttribute(sourceTag, attributeName);
|
||||
return value ? `${attributeName}="${escapeAttribute(value)}"` : "";
|
||||
})
|
||||
return BAIJIAHAO_TABLE_ALLOWED_ATTRS.map((attributeName) => {
|
||||
const value = extractAttribute(sourceTag, attributeName)
|
||||
return value ? `${attributeName}="${escapeAttribute(value)}"` : ''
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
function normalizeCellContent(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed : "<br/>";
|
||||
const trimmed = value.trim()
|
||||
return trimmed ? trimmed : '<br/>'
|
||||
}
|
||||
|
||||
function normalizeTableCell(sourceTag: string, content: string, header: boolean): string {
|
||||
const attrs = allowedTableAttributes(sourceTag);
|
||||
const attrPrefix = attrs ? `${attrs} ` : "";
|
||||
const style = header ? BAIJIAHAO_TABLE_HEADER_CELL_STYLE : BAIJIAHAO_TABLE_CELL_STYLE;
|
||||
const normalizedContent = normalizeCellContent(content);
|
||||
const cellContent = header ? `<strong>${normalizedContent}</strong>` : normalizedContent;
|
||||
return `<td ${attrPrefix}style="${style}">${cellContent}</td>`;
|
||||
const attrs = allowedTableAttributes(sourceTag)
|
||||
const attrPrefix = attrs ? `${attrs} ` : ''
|
||||
const style = header ? BAIJIAHAO_TABLE_HEADER_CELL_STYLE : BAIJIAHAO_TABLE_CELL_STYLE
|
||||
const normalizedContent = normalizeCellContent(content)
|
||||
const cellContent = header ? `<strong>${normalizedContent}</strong>` : normalizedContent
|
||||
return `<td ${attrPrefix}style="${style}">${cellContent}</td>`
|
||||
}
|
||||
|
||||
function normalizeSingleTable(tableHtml: string): string {
|
||||
let body = tableHtml
|
||||
.replace(/^\s*<table\b[^>]*>/i, "")
|
||||
.replace(/<\/table>\s*$/i, "")
|
||||
.replace(/<caption\b[^>]*>[\s\S]*?<\/caption>/gi, "")
|
||||
.replace(/<colgroup\b[^>]*>[\s\S]*?<\/colgroup>/gi, "")
|
||||
.replace(/<col\b[^>]*\/?>/gi, "")
|
||||
.replace(/<\/?(?:thead|tbody|tfoot)\b[^>]*>/gi, "");
|
||||
.replace(/^\s*<table\b[^>]*>/i, '')
|
||||
.replace(/<\/table>\s*$/i, '')
|
||||
.replace(/<caption\b[^>]*>[\s\S]*?<\/caption>/gi, '')
|
||||
.replace(/<colgroup\b[^>]*>[\s\S]*?<\/colgroup>/gi, '')
|
||||
.replace(/<col\b[^>]*\/?>/gi, '')
|
||||
.replace(/<\/?(?:thead|tbody|tfoot)\b[^>]*>/gi, '')
|
||||
|
||||
body = body.replace(/<th\b([^>]*)>([\s\S]*?)<\/th>/gi, (_match, attrs: string, content: string) =>
|
||||
normalizeTableCell(`<th${attrs}>`, content, true),
|
||||
);
|
||||
)
|
||||
body = body.replace(/<td\b([^>]*)>([\s\S]*?)<\/td>/gi, (_match, attrs: string, content: string) =>
|
||||
normalizeTableCell(`<td${attrs}>`, content, false),
|
||||
);
|
||||
)
|
||||
|
||||
let rowIndex = 0;
|
||||
let rowIndex = 0
|
||||
body = body.replace(/<tr\b[^>]*>/gi, () => {
|
||||
rowIndex += 1;
|
||||
return rowIndex === 1 ? `<tr class="firstRow">` : "<tr>";
|
||||
});
|
||||
rowIndex += 1
|
||||
return rowIndex === 1 ? `<tr class="firstRow">` : '<tr>'
|
||||
})
|
||||
|
||||
return `<table data-sort="sortDisabled" border="1" cellpadding="0" cellspacing="0" style="${BAIJIAHAO_TABLE_STYLE}"><tbody>${body.trim()}</tbody></table>`;
|
||||
return `<table data-sort="sortDisabled" border="1" cellpadding="0" cellspacing="0" style="${BAIJIAHAO_TABLE_STYLE}"><tbody>${body.trim()}</tbody></table>`
|
||||
}
|
||||
|
||||
export function normalizeBaijiahaoTables(html: string): string {
|
||||
if (!/<table\b/i.test(html)) {
|
||||
return html;
|
||||
return html
|
||||
}
|
||||
return html.replace(/<table\b[^>]*>[\s\S]*?<\/table>/gi, (tableHtml) => normalizeSingleTable(tableHtml));
|
||||
return html.replace(/<table\b[^>]*>[\s\S]*?<\/table>/gi, (tableHtml) =>
|
||||
normalizeSingleTable(tableHtml),
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeParagraphCellContent(value: string): string {
|
||||
return value
|
||||
.trim()
|
||||
.replace(/<br\s*\/?>/gi, " ")
|
||||
.replace(/<\/?(?:p|div|section|article|header|footer|ul|ol|li|blockquote|h[1-6])\b[^>]*>/gi, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
.replace(/<br\s*\/?>/gi, ' ')
|
||||
.replace(
|
||||
/<\/?(?:p|div|section|article|header|footer|ul|ol|li|blockquote|h[1-6])\b[^>]*>/gi,
|
||||
' ',
|
||||
)
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
}
|
||||
|
||||
function parseParagraphTableRows(tableHtml: string): ParsedParagraphTableRow[] {
|
||||
const rows: ParsedParagraphTableRow[] = [];
|
||||
const rows: ParsedParagraphTableRow[] = []
|
||||
for (const rowMatch of tableHtml.matchAll(/<tr\b[^>]*>([\s\S]*?)<\/tr>/gi)) {
|
||||
const cells: ParsedParagraphTableRow = [];
|
||||
const rowContent = rowMatch[1] ?? "";
|
||||
const cells: ParsedParagraphTableRow = []
|
||||
const rowContent = rowMatch[1] ?? ''
|
||||
for (const cellMatch of rowContent.matchAll(/<(?:th|td)\b[^>]*>([\s\S]*?)<\/(?:th|td)>/gi)) {
|
||||
const content = normalizeParagraphCellContent(cellMatch[1] ?? "");
|
||||
const content = normalizeParagraphCellContent(cellMatch[1] ?? '')
|
||||
if (content) {
|
||||
cells.push(content);
|
||||
cells.push(content)
|
||||
}
|
||||
}
|
||||
if (cells.length > 0) {
|
||||
rows.push(cells);
|
||||
rows.push(cells)
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
return rows
|
||||
}
|
||||
|
||||
function renderSingleTableAsParagraphRows(tableHtml: string): string {
|
||||
const rows = parseParagraphTableRows(tableHtml);
|
||||
const rows = parseParagraphTableRows(tableHtml)
|
||||
if (!rows.length) {
|
||||
return "";
|
||||
return ''
|
||||
}
|
||||
|
||||
return rows
|
||||
.map((row) => `<p>${row.join(TABLE_PARAGRAPH_CELL_SEPARATOR)}</p>`)
|
||||
.join("");
|
||||
return rows.map((row) => `<p>${row.join(TABLE_PARAGRAPH_CELL_SEPARATOR)}</p>`).join('')
|
||||
}
|
||||
|
||||
export function renderTablesAsParagraphRows(html: string): string {
|
||||
if (!/<table\b/i.test(html)) {
|
||||
return html;
|
||||
return html
|
||||
}
|
||||
return html.replace(/<table\b[^>]*>[\s\S]*?<\/table>/gi, (tableHtml) =>
|
||||
renderSingleTableAsParagraphRows(tableHtml),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function renderBaijiahaoTablesAsBlocks(html: string): string {
|
||||
return renderTablesAsParagraphRows(html);
|
||||
return renderTablesAsParagraphRows(html)
|
||||
}
|
||||
|
||||
export function prepareBaijiahaoArticleHtml(html: string): string {
|
||||
return renderTablesAsParagraphRows(html).trim();
|
||||
return renderTablesAsParagraphRows(html).trim()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user