Add support for wysiwyg markdown preview
This commit is contained in:
+30
-18
@@ -68,7 +68,7 @@ export class HopDown {
|
||||
|
||||
this.blockTags = allTags.filter(tag =>
|
||||
defaultBlockNames.has(tag.name) || tag.name === 'macro' ||
|
||||
(!defaultInlineNames.has(tag.name) && !(tag as any).pattern)
|
||||
(!defaultInlineNames.has(tag.name) && !tag.pattern)
|
||||
);
|
||||
|
||||
// Ensure macro block tag runs after fencedCode but before everything else
|
||||
@@ -83,7 +83,7 @@ export class HopDown {
|
||||
});
|
||||
|
||||
this.inlineTags = allTags.filter(tag =>
|
||||
defaultInlineNames.has(tag.name) || (tag as any).pattern
|
||||
defaultInlineNames.has(tag.name) || tag.pattern
|
||||
);
|
||||
|
||||
this.tags = new Map();
|
||||
@@ -113,11 +113,11 @@ export class HopDown {
|
||||
*/
|
||||
private validateInlineTags(): void {
|
||||
const withDelimiters = this.inlineTags
|
||||
.filter(tag => (tag as any).delimiter)
|
||||
.filter(tag => tag.delimiter)
|
||||
.map(tag => ({
|
||||
name: tag.name,
|
||||
delimiter: (tag as any).delimiter as string,
|
||||
precedence: (tag as any).precedence as number ?? 50,
|
||||
delimiter: tag.delimiter as string,
|
||||
precedence: tag.precedence as number ?? 50,
|
||||
}));
|
||||
|
||||
for (let i = 0; i < withDelimiters.length; i++) {
|
||||
@@ -159,6 +159,20 @@ export class HopDown {
|
||||
return this.nodeToMd(container).replace(/\n{3,}/g, '\n\n').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the block tags for external iteration (e.g. speculative rendering).
|
||||
*/
|
||||
getBlockTags(): Tag[] {
|
||||
return this.blockTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the inline tags for external iteration (e.g. speculative rendering).
|
||||
*/
|
||||
getInlineTags(): Tag[] {
|
||||
return this.inlineTags;
|
||||
}
|
||||
|
||||
private processBlocks(md: string): string {
|
||||
const lines = md.replace(/\r\n/g, '\n').split('\n');
|
||||
const output: string[] = [];
|
||||
@@ -186,6 +200,7 @@ export class HopDown {
|
||||
output.push(result.html);
|
||||
index = result.end;
|
||||
} else {
|
||||
|
||||
output.push(tag.toHTML(token, this.makeConverter()));
|
||||
index += token.consumed;
|
||||
}
|
||||
@@ -216,13 +231,11 @@ export class HopDown {
|
||||
|
||||
// Pass 1: extract links and non-recursive tags into placeholders before escaping
|
||||
for (const tag of sorted) {
|
||||
const recursive = (tag as any).recursive ?? true;
|
||||
const recursive = tag.recursive ?? true;
|
||||
|
||||
if (tag.name === 'link') {
|
||||
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, linkText: string, href: string) => {
|
||||
// Process link text: restore earlier placeholders, then run inline on any remaining markdown
|
||||
let inner = linkText;
|
||||
// Check if link text contains placeholders (already-processed content)
|
||||
const hasPlaceholders = /\x00P\d+\x00/.test(inner);
|
||||
if (hasPlaceholders) {
|
||||
inner = inner.replace(/\x00P(\d+)\x00/g, (__, idx: string) => placeholders[parseInt(idx)]);
|
||||
@@ -232,9 +245,10 @@ export class HopDown {
|
||||
placeholders.push('<a href="' + escapeHtml(href) + '">' + inner + '</a>');
|
||||
return '\x00P' + (placeholders.length - 1) + '\x00';
|
||||
});
|
||||
} else if (!recursive && (tag as any).pattern) {
|
||||
const globalPattern = (tag as any).pattern as RegExp;
|
||||
} else if (!recursive && tag.pattern) {
|
||||
const globalPattern = tag.pattern as RegExp;
|
||||
globalPattern.lastIndex = 0;
|
||||
|
||||
text = text.replace(globalPattern, (_, content: string) => {
|
||||
placeholders.push(tag.toHTML(
|
||||
{ content, raw: '', consumed: 0 },
|
||||
@@ -247,21 +261,20 @@ export class HopDown {
|
||||
|
||||
text = escapeHtml(text);
|
||||
|
||||
// Pass 2: apply recursive tags in precedence order (longest delimiter first).
|
||||
// Content matched here is already HTML-escaped and has had earlier
|
||||
// passes applied, so we wrap directly without re-processing.
|
||||
// Pass 2: apply recursive tags in precedence order.
|
||||
// Content is already HTML-escaped from pass 1, so we wrap directly
|
||||
// without re-processing through convert.inline().
|
||||
for (const tag of sorted) {
|
||||
const recursive = (tag as any).recursive ?? true;
|
||||
const recursive = tag.recursive ?? true;
|
||||
if (tag.name === 'link' || !recursive) {
|
||||
continue;
|
||||
}
|
||||
const globalPattern = (tag as any).pattern as RegExp | undefined;
|
||||
const globalPattern = tag.pattern as RegExp | undefined;
|
||||
if (globalPattern) {
|
||||
globalPattern.lastIndex = 0;
|
||||
text = text.replace(globalPattern, (_, content: string) => {
|
||||
// Restore any placeholders in the captured content
|
||||
const restored = content.replace(/\x00P(\d+)\x00/g, (__, idx: string) => placeholders[parseInt(idx)]);
|
||||
const htmlTag = (tag as any).name === 'boldItalic'
|
||||
const htmlTag = tag.name === 'boldItalic'
|
||||
? null
|
||||
: ((tag.selector as string) || '').split(',')[0].toLowerCase();
|
||||
if (tag.name === 'boldItalic') {
|
||||
@@ -272,7 +285,6 @@ export class HopDown {
|
||||
}
|
||||
}
|
||||
|
||||
// Restore placeholders
|
||||
text = text.replace(/\x00P(\d+)\x00/g, (_, index: string) => placeholders[parseInt(index)]);
|
||||
return text;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user