Fix up markdown rendering to be scrollable
This commit is contained in:
@@ -1,11 +1,22 @@
|
||||
/// Utility class to convert BBCode to Markdown for RimWorld mod descriptions
|
||||
import 'dart:math' as math;
|
||||
|
||||
class BBCodeConverter {
|
||||
/// Converts BBCode formatted text to Markdown format
|
||||
static String toMarkdown(String bbcode) {
|
||||
if (bbcode.isEmpty) return '';
|
||||
|
||||
// First, normalize line endings
|
||||
String result = bbcode.replaceAll('\r\n', '\n');
|
||||
// First, normalize line endings and escape any literal backslashes
|
||||
String result = bbcode.replaceAll('\r\n', '\n').replaceAll('\\', '\\\\');
|
||||
|
||||
// Ensure paragraphs - double newlines between paragraphs
|
||||
// First normalize any consecutive newlines to a single one
|
||||
result = result.replaceAll(RegExp(r'\n+'), '\n');
|
||||
|
||||
// Then add empty line after each paragraph where needed
|
||||
result = result.replaceAll('.\n', '.\n\n');
|
||||
result = result.replaceAll('!\n', '!\n\n');
|
||||
result = result.replaceAll('?\n', '?\n\n');
|
||||
|
||||
// Fix unclosed tags - RimWorld descriptions often have unclosed tags
|
||||
final List<String> tagTypes = ['b', 'i', 'color', 'size', 'url', 'code', 'quote'];
|
||||
@@ -42,30 +53,29 @@ class BBCodeConverter {
|
||||
// Italic
|
||||
result = result.replaceAll('[i]', '_').replaceAll('[/i]', '_');
|
||||
|
||||
// Headers
|
||||
// Headers - ensure they start on their own line
|
||||
result = result.replaceAllMapped(
|
||||
RegExp(r'\[h1\](.*?)\[/h1\]', dotAll: true),
|
||||
(match) => '# ${match.group(1)}'
|
||||
(match) => '\n\n# ${match.group(1)?.trim()}\n\n'
|
||||
);
|
||||
result = result.replaceAllMapped(
|
||||
RegExp(r'\[h2\](.*?)\[/h2\]', dotAll: true),
|
||||
(match) => '## ${match.group(1)}'
|
||||
(match) => '\n\n## ${match.group(1)?.trim()}\n\n'
|
||||
);
|
||||
result = result.replaceAllMapped(
|
||||
RegExp(r'\[h3\](.*?)\[/h3\]', dotAll: true),
|
||||
(match) => '### ${match.group(1)}'
|
||||
(match) => '\n\n### ${match.group(1)?.trim()}\n\n'
|
||||
);
|
||||
|
||||
// Lists - handle nested lists too
|
||||
result = result.replaceAll('[list]', '\n').replaceAll('[/list]', '\n');
|
||||
|
||||
// Handle list items - giving them proper indentation
|
||||
int listLevel = 0;
|
||||
result = result.replaceAllMapped(
|
||||
RegExp(r'\[\*\](.*?)(?=\[\*\]|\[/list\]|$)', dotAll: true),
|
||||
(match) {
|
||||
final content = match.group(1)?.trim() ?? '';
|
||||
return '* $content\n';
|
||||
return '\n* $content\n';
|
||||
}
|
||||
);
|
||||
|
||||
@@ -102,35 +112,64 @@ class BBCodeConverter {
|
||||
final tableContent = tableMatch.group(1) ?? '';
|
||||
final rows = RegExp(r'\[tr\](.*?)\[/tr\]', dotAll: true).allMatches(tableContent);
|
||||
|
||||
final markdownTable = StringBuffer();
|
||||
// Only process tables that have rows
|
||||
if (rows.isEmpty) {
|
||||
result = result.replaceFirst(tableMatch.group(0)!, '');
|
||||
continue;
|
||||
}
|
||||
|
||||
final markdownTable = StringBuffer('\n');
|
||||
var isFirstRow = true;
|
||||
|
||||
// First determine the number of columns by examining all rows
|
||||
int maxColumns = 0;
|
||||
for (final rowMatch in rows) {
|
||||
final rowContent = rowMatch.group(1) ?? '';
|
||||
final cellCount = RegExp(r'\[td\]').allMatches(rowContent).length;
|
||||
maxColumns = math.max(maxColumns, cellCount);
|
||||
}
|
||||
|
||||
// Ensure we have at least 1 column
|
||||
maxColumns = math.max(1, maxColumns);
|
||||
|
||||
for (final rowMatch in rows) {
|
||||
final rowContent = rowMatch.group(1) ?? '';
|
||||
final cells = RegExp(r'\[td\](.*?)\[/td\]', dotAll: true).allMatches(rowContent);
|
||||
|
||||
if (cells.isEmpty) continue;
|
||||
|
||||
final rowBuffer = StringBuffer('|');
|
||||
final rowBuffer = StringBuffer('| ');
|
||||
|
||||
int cellsAdded = 0;
|
||||
for (final cellMatch in cells) {
|
||||
final cellContent = cellMatch.group(1)?.trim() ?? '';
|
||||
// Clean up any newlines inside cell content
|
||||
final cleanCell = cellContent.replaceAll('\n', ' ').trim();
|
||||
rowBuffer.write(' $cleanCell |');
|
||||
rowBuffer.write('$cleanCell | ');
|
||||
cellsAdded++;
|
||||
}
|
||||
|
||||
// Add empty cells if needed to maintain table structure
|
||||
while (cellsAdded < maxColumns) {
|
||||
rowBuffer.write(' | ');
|
||||
cellsAdded++;
|
||||
}
|
||||
|
||||
markdownTable.writeln(rowBuffer.toString());
|
||||
|
||||
// Add header separator after first row
|
||||
if (isFirstRow) {
|
||||
final cellCount = RegExp(r'\[td\]').allMatches(rowContent).length;
|
||||
markdownTable.writeln('|${' --- |' * cellCount}');
|
||||
final headerRow = StringBuffer('| ');
|
||||
for (int i = 0; i < maxColumns; i++) {
|
||||
headerRow.write('--- | ');
|
||||
}
|
||||
markdownTable.writeln(headerRow.toString());
|
||||
isFirstRow = false;
|
||||
}
|
||||
}
|
||||
|
||||
result = result.replaceFirst(tableMatch.group(0)!, '\n${markdownTable.toString()}\n');
|
||||
markdownTable.write('\n');
|
||||
result = result.replaceFirst(tableMatch.group(0)!, markdownTable.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +188,7 @@ class BBCodeConverter {
|
||||
(match) {
|
||||
final content = match.group(1)?.trim() ?? '';
|
||||
if (content.isEmpty) return '';
|
||||
return '\n> ${content.replaceAll('\n', '\n> ')}\n';
|
||||
return '\n> ${content.replaceAll('\n', '\n> ')}\n\n';
|
||||
}
|
||||
);
|
||||
|
||||
@@ -163,12 +202,25 @@ class BBCodeConverter {
|
||||
// [h1] without closing tag is common
|
||||
result = result.replaceAllMapped(
|
||||
RegExp(r'\[h1\]([^\[]+)'),
|
||||
(match) => '# ${match.group(1)}\n'
|
||||
(match) => '\n\n# ${match.group(1)?.trim()}\n\n'
|
||||
);
|
||||
|
||||
// Replace multiple newlines with at most two newlines
|
||||
// Convert simple newlines to double newlines for proper markdown rendering
|
||||
// But avoid doing this for lines that are already marked up as headings, lists, or tables
|
||||
result = result.replaceAllMapped(
|
||||
RegExp(r'([^\n#*>|])\n([^\n#*>|-])'),
|
||||
(match) => '${match.group(1)}\n\n${match.group(2)}'
|
||||
);
|
||||
|
||||
// Normalize multiple spaces
|
||||
result = result.replaceAll(RegExp(r' {2,}'), ' ');
|
||||
|
||||
// Remove excessive newlines (more than 2 consecutive)
|
||||
result = result.replaceAll(RegExp(r'\n{3,}'), '\n\n');
|
||||
|
||||
// Ensure document starts and ends cleanly
|
||||
result = result.trim();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user