Add support for wysiwyg markdown preview
This commit is contained in:
+104
-1
@@ -64,7 +64,7 @@ function not(name, actual, sub) {
|
||||
}
|
||||
}
|
||||
|
||||
function section(n) { /* silent */ }
|
||||
function section(n) { console.log(' ' + n); }
|
||||
|
||||
// ── 1. Inline formatting ────────────────────────────────
|
||||
section('1. Inline Formatting → HTML');
|
||||
@@ -497,6 +497,109 @@ has('macro: unknown block renders error', MH('@bogus(args\ncontent\n)'), 'ribbit
|
||||
eq('macro: npc round-trip', mrt('@npc(Goblin King)'), '@npc(Goblin King)');
|
||||
eq('macro: user round-trip', mrt('hello @user world'), 'hello @user world');
|
||||
|
||||
// ── 25. Preview CSS (via .ribbit-editing pseudo-elements) ───
|
||||
// Preview styling is handled by CSS ::before/::after on .ribbit-editing,
|
||||
// not by JS. We verify the converter output is clean HTML without syntax spans.
|
||||
not('preview: no syntax spans in toHTML',
|
||||
H('**bold**'), 'ribbit-syntax');
|
||||
not('preview: no syntax spans in heading',
|
||||
H('## Title'), 'ribbit-syntax');
|
||||
|
||||
// ── 26. openPattern — unclosed delimiter detection ──────
|
||||
var inlineTags = hopdown.getInlineTags();
|
||||
function findTag(name) {
|
||||
return inlineTags.find(function(t) { return t.name === name; });
|
||||
}
|
||||
|
||||
var boldTag = findTag('bold');
|
||||
var italicTag = findTag('italic');
|
||||
var codeTag = findTag('code');
|
||||
var boldItalicTag = findTag('boldItalic');
|
||||
|
||||
eq('openPattern: bold has pattern', String(!!boldTag.openPattern), 'true');
|
||||
eq('openPattern: italic has pattern', String(!!italicTag.openPattern), 'true');
|
||||
eq('openPattern: code has pattern', String(!!codeTag.openPattern), 'true');
|
||||
|
||||
// Unclosed bold matches
|
||||
eq('openPattern: unclosed ** odd count',
|
||||
String((('hello **world').match(/\*\*/g) || []).length % 2 === 1), 'true');
|
||||
// Closed bold — even count
|
||||
eq('openPattern: closed ** even count',
|
||||
String((('hello **world**').match(/\*\*/g) || []).length % 2 === 1), 'false');
|
||||
// Unclosed italic
|
||||
eq('openPattern: unclosed * odd count',
|
||||
String((('hello *world').match(/\*/g) || []).length % 2 === 1), 'true');
|
||||
// Unclosed code
|
||||
eq('openPattern: unclosed ` odd count',
|
||||
String((('hello `world').match(/`/g) || []).length % 2 === 1), 'true');
|
||||
|
||||
// ── 27. Speculative patching ────────────────────────────
|
||||
function specPatch(md, cursorLine, cursorOffset) {
|
||||
var lines = md.split('\n');
|
||||
var sorted = inlineTags.slice().sort(function(a, b) {
|
||||
return ((a).precedence || 50) - ((b).precedence || 50);
|
||||
});
|
||||
for (var i = 0; i < sorted.length; i++) {
|
||||
var tag = sorted[i];
|
||||
if (tag.openPattern && tag.delimiter) {
|
||||
var before = lines[cursorLine].slice(0, cursorOffset);
|
||||
var escaped = tag.delimiter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
var re = new RegExp(escaped, 'g');
|
||||
var count = (before.match(re) || []).length;
|
||||
if (count % 2 === 1) {
|
||||
lines[cursorLine] = lines[cursorLine] + tag.delimiter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hopdown.toHTML(lines.join('\n'));
|
||||
}
|
||||
|
||||
has('speculate: unclosed bold',
|
||||
specPatch('hello **world', 0, 13), '<strong>world</strong>');
|
||||
has('speculate: unclosed italic',
|
||||
specPatch('hello *world', 0, 12), '<em>world</em>');
|
||||
has('speculate: unclosed code',
|
||||
specPatch('hello `world', 0, 12), '<code>world</code>');
|
||||
has('speculate: unclosed bold+italic',
|
||||
specPatch('hello ***world', 0, 14), '<em><strong>world</strong></em>');
|
||||
|
||||
// Already closed — no double closing
|
||||
eq('speculate: closed bold unchanged',
|
||||
specPatch('hello **world**', 0, 15), '<p>hello <strong>world</strong></p>');
|
||||
eq('speculate: closed italic unchanged',
|
||||
specPatch('hello *world*', 0, 13), '<p>hello <em>world</em></p>');
|
||||
|
||||
// Only cursor line patched
|
||||
has('speculate: multiline patches cursor only',
|
||||
specPatch('normal\nhello **world', 1, 13), '<strong>world</strong>');
|
||||
not('speculate: other line untouched',
|
||||
specPatch('normal\nhello **world', 1, 13), '<strong>normal</strong>');
|
||||
|
||||
// No unclosed delimiter — no change
|
||||
eq('speculate: no delimiter no-op',
|
||||
specPatch('hello world', 0, 11), '<p>hello world</p>');
|
||||
|
||||
// ** wins over * (precedence)
|
||||
has('speculate: ** wins over *',
|
||||
specPatch('hello **world', 0, 13), '<strong>');
|
||||
not('speculate: ** not italic',
|
||||
specPatch('hello **world', 0, 13), '<em>world</em>');
|
||||
|
||||
// Delimiter with no content — speculation appends but nothing to format
|
||||
eq('speculate: bare delimiter no content',
|
||||
specPatch('hello **', 0, 8), '<p>hello <em>*</em>*</p>');
|
||||
|
||||
// Even count — all closed
|
||||
eq('speculate: even count no-op',
|
||||
specPatch('**a** **b**', 0, 11), '<p><strong>a</strong> <strong>b</strong></p>');
|
||||
|
||||
// Block tags need no speculation
|
||||
eq('speculate: list works as-is',
|
||||
H('- '), '<ul><li></li></ul>');
|
||||
has('speculate: blockquote works as-is',
|
||||
H('> '), '<blockquote>');
|
||||
|
||||
// ── Results ─────────────────────────────────────────────
|
||||
const total = passed + failed;
|
||||
console.log(`\n${passed}/${total} passed (${Math.round(100 * passed / total)}%) — ${failed} failed`);
|
||||
|
||||
Reference in New Issue
Block a user