feat: implement minimal server-first markdown processing
Backend implementation: - Add goldmark dependency for markdown processing - Create MarkdownProcessor with minimal config (bold, italic, links only) - Update content injector with HTML injection capabilities - Add injectHTMLContent() for safe DOM manipulation - Server now converts **bold**, *italic*, [links](url) to HTML during enhancement Frontend alignment: - Restrict marked.js to match server capabilities - Disable unsupported features (headings, lists, code blocks, tables) - Update turndown rules to prevent unsupported markdown generation - Frontend editor preview now matches server output exactly Server as source of truth: - Build-time markdown→HTML conversion during enhancement - Zero runtime overhead for end users - Consistent formatting between editor preview and final output - Raw markdown stored in database, HTML served to visitors Tested features: - **bold** → <strong>bold</strong> ✅ - *italic* → <em>italic</em> ✅ - [text](url) → <a href="url">text</a> ✅
This commit is contained in:
@@ -14,32 +14,75 @@ export class MarkdownConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure marked for HTML output
|
||||
* Configure marked for HTML output - MINIMAL MODE
|
||||
* Only supports: **bold**, *italic*, and [links](url)
|
||||
* Matches server-side goldmark configuration
|
||||
*/
|
||||
initializeMarked() {
|
||||
marked.setOptions({
|
||||
gfm: true, // GitHub Flavored Markdown
|
||||
breaks: true, // Convert \n to <br>
|
||||
gfm: false, // Disable GFM to match server minimal mode
|
||||
breaks: true, // Convert \n to <br> (matches server)
|
||||
pedantic: false, // Don't be overly strict
|
||||
sanitize: false, // Allow HTML (we control the input)
|
||||
smartLists: true, // Smarter list behavior
|
||||
smartLists: false, // Disable lists (not supported on server)
|
||||
smartypants: false // Don't convert quotes/dashes
|
||||
});
|
||||
|
||||
// Override renderers to restrict to minimal feature set
|
||||
marked.use({
|
||||
renderer: {
|
||||
// Disable headings - treat as plain text
|
||||
heading(text, level) {
|
||||
return text;
|
||||
},
|
||||
// Disable lists - treat as plain text
|
||||
list(body, ordered, start) {
|
||||
return body.replace(/<\/?li>/g, '');
|
||||
},
|
||||
listitem(text) {
|
||||
return text + '\n';
|
||||
},
|
||||
// Disable code blocks - treat as plain text
|
||||
code(code, language) {
|
||||
return code;
|
||||
},
|
||||
blockquote(quote) {
|
||||
return quote; // Disable blockquotes - treat as plain text
|
||||
},
|
||||
// Disable horizontal rules
|
||||
hr() {
|
||||
return '';
|
||||
},
|
||||
// Disable tables
|
||||
table(header, body) {
|
||||
return header + body;
|
||||
},
|
||||
tablecell(content, flags) {
|
||||
return content;
|
||||
},
|
||||
tablerow(content) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure turndown for markdown output
|
||||
* Configure turndown for markdown output - MINIMAL MODE
|
||||
* Only supports: **bold**, *italic*, and [links](url)
|
||||
* Matches server-side goldmark configuration
|
||||
*/
|
||||
initializeTurndown() {
|
||||
this.turndown = new TurndownService({
|
||||
headingStyle: 'atx', // # headers instead of underlines
|
||||
hr: '---', // horizontal rule style
|
||||
bulletListMarker: '-', // bullet list marker
|
||||
codeBlockStyle: 'fenced', // ``` code blocks
|
||||
fence: '```', // fence marker
|
||||
emDelimiter: '*', // emphasis delimiter
|
||||
strongDelimiter: '**', // strong delimiter
|
||||
linkStyle: 'inlined', // [text](url) instead of reference style
|
||||
// Minimal configuration - only basic formatting
|
||||
headingStyle: 'atx', // # headers (but will be disabled)
|
||||
hr: '---', // horizontal rule (but will be disabled)
|
||||
bulletListMarker: '-', // bullet list (but will be disabled)
|
||||
codeBlockStyle: 'fenced', // code blocks (but will be disabled)
|
||||
fence: '```', // fence marker (but will be disabled)
|
||||
emDelimiter: '*', // *italic* - matches server
|
||||
strongDelimiter: '**', // **bold** - matches server
|
||||
linkStyle: 'inlined', // [text](url) - matches server
|
||||
linkReferenceStyle: 'full' // full reference links
|
||||
});
|
||||
|
||||
@@ -48,7 +91,9 @@ export class MarkdownConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom turndown rules for better HTML → Markdown conversion
|
||||
* Add custom turndown rules - MINIMAL MODE
|
||||
* Only supports: **bold**, *italic*, and [links](url)
|
||||
* Disables all other formatting to match server
|
||||
*/
|
||||
addTurndownRules() {
|
||||
// Handle paragraph spacing properly - ensure double newlines between paragraphs
|
||||
@@ -60,7 +105,7 @@ export class MarkdownConverter {
|
||||
}
|
||||
});
|
||||
|
||||
// Handle bold text in markdown
|
||||
// Handle bold text in markdown - keep this (supported)
|
||||
this.turndown.addRule('bold', {
|
||||
filter: ['strong', 'b'],
|
||||
replacement: function (content) {
|
||||
@@ -69,7 +114,7 @@ export class MarkdownConverter {
|
||||
}
|
||||
});
|
||||
|
||||
// Handle italic text in markdown
|
||||
// Handle italic text in markdown - keep this (supported)
|
||||
this.turndown.addRule('italic', {
|
||||
filter: ['em', 'i'],
|
||||
replacement: function (content) {
|
||||
@@ -77,6 +122,42 @@ export class MarkdownConverter {
|
||||
return '*' + content + '*';
|
||||
}
|
||||
});
|
||||
|
||||
// DISABLE unsupported features - convert to plain text
|
||||
this.turndown.addRule('disableHeadings', {
|
||||
filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
|
||||
replacement: function (content) {
|
||||
return content; // Just return text content, no # markup
|
||||
}
|
||||
});
|
||||
|
||||
this.turndown.addRule('disableLists', {
|
||||
filter: ['ul', 'ol', 'li'],
|
||||
replacement: function (content) {
|
||||
return content; // Just return text content, no list markup
|
||||
}
|
||||
});
|
||||
|
||||
this.turndown.addRule('disableCode', {
|
||||
filter: ['pre', 'code'],
|
||||
replacement: function (content) {
|
||||
return content; // Just return text content, no code markup
|
||||
}
|
||||
});
|
||||
|
||||
this.turndown.addRule('disableBlockquotes', {
|
||||
filter: 'blockquote',
|
||||
replacement: function (content) {
|
||||
return content; // Just return text content, no > markup
|
||||
}
|
||||
});
|
||||
|
||||
this.turndown.addRule('disableHR', {
|
||||
filter: 'hr',
|
||||
replacement: function () {
|
||||
return ''; // Remove horizontal rules entirely
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user