Compare commits

..

14 Commits

Author SHA1 Message Date
evilchili
57361f8c55 wip 2026-07-10 10:45:16 -07:00
evilchili
a2519a1282 fix nesting 2026-07-05 15:56:39 -07:00
evilchili
f27378dc04 wip -- disambiguation works, tests passing 2026-07-05 14:09:27 -07:00
evilchili
9f03721f86 wip toolbar 2026-06-20 13:47:13 -07:00
evilchili
0758105e92 wip tests 2026-06-20 11:41:21 -07:00
evilchili
fe405cb393 WIP 2026-06-19 17:11:10 -07:00
evilchili
d23c3e7a67 bugfix 2026-05-29 14:09:35 -07:00
evilchili
59436a7d39 WIP fixing tests 2026-05-28 23:54:44 -07:00
evilchili
781af3cc1e wip 2026-05-15 20:31:27 -07:00
evilchili
38af541c5d wip 2026-05-15 16:52:04 -07:00
evilchili
80c34a1483 fix dev server 2026-05-15 15:18:31 -07:00
evilchili
c61b8e2f8b wip; tests passing 2026-05-15 14:34:20 -07:00
evilchili
9748d12ede wip 2026-05-15 12:27:22 -07:00
evilchili
2bbb0ba25f wip 2026-05-15 11:57:10 -07:00
3 changed files with 17 additions and 19 deletions

View File

@ -34,7 +34,7 @@
*/ */
.md-delim { .md-delim {
display:inline; display:none;
opacity: 0.3; opacity: 0.3;
font-size: 0.85em; font-size: 0.85em;
font-weight: normal; font-weight: normal;

View File

@ -173,10 +173,8 @@ const BLOCK_RULES: BlockRule[] = [
* disambiguateAsteriskRuns('**bold *italic***') * disambiguateAsteriskRuns('**bold *italic***')
* // '**bold *italic*\u200C**' * // '**bold *italic*\u200C**'
*/ */
const SENTINEL = '\u200C';
function disambiguateAsteriskRuns(line: string): string { function disambiguateAsteriskRuns(line: string): string {
const SENTINEL = '\u200C';
const ASTERISK_RUN = /\*{1,3}/g; const ASTERISK_RUN = /\*{1,3}/g;
const stack: ('*' | '**' | '***')[] = []; const stack: ('*' | '**' | '***')[] = [];
let result = ''; let result = '';
@ -386,8 +384,6 @@ export class RibbitEditor extends Ribbit {
return block; return block;
} }
//console.log(`Parsing line '${line}'`);
for (const rule of BLOCK_RULES) { for (const rule of BLOCK_RULES) {
const prefixLength = rule.prefixLength(line); const prefixLength = rule.prefixLength(line);
if (prefixLength === null) { if (prefixLength === null) {
@ -447,7 +443,6 @@ export class RibbitEditor extends Ribbit {
#parseInline(text: string): DocumentFragment { #parseInline(text: string): DocumentFragment {
var t = text; var t = text;
text = disambiguateAsteriskRuns(text); text = disambiguateAsteriskRuns(text);
//console.log(`${t} => ${text}`);
const classes: Record<string, string> = { const classes: Record<string, string> = {
'**': 'md-bold', '**': 'md-bold',
'*': 'md-italic', '*': 'md-italic',
@ -455,12 +450,18 @@ export class RibbitEditor extends Ribbit {
'`': 'md-code', '`': 'md-code',
}; };
// ***bold italic***
// ***italic* bold**
// **bold *italic***
// ***bold** italic* x
// *italic **bold*** x
const INLINE_PATTERN = new RegExp( const INLINE_PATTERN = new RegExp(
'\\[(?<linkLabel>[^\\]]+)\\]\\((?<linkHref>[^)]+)\\)' + '\\[(?<linkLabel>[^\\]]+)\\]\\((?<linkHref>[^)]+)\\)' +
'|' + '(?<![*])' + '|' +
'(?<delimiter>${SENTINEL}\\*{2}|\\*{2}|${SENTINEL}\\*|\\*|~~|`)' + '(?<delimiter>\\*{2}|\\*|~~|`)' +
'(?<content>.*)' + '(?<content>[^*].+?)' +
`(?<closer>(?:${SENTINEL}\\k<delimiter>|\\k<delimiter>)(?![*]))`, '(?<closer>\\k<delimiter>(?!\\*))',
'g' 'g'
); );
@ -469,7 +470,7 @@ export class RibbitEditor extends Ribbit {
let match: RegExpExecArray | null; let match: RegExpExecArray | null;
while ((match = INLINE_PATTERN.exec(text)) !== null) { while ((match = INLINE_PATTERN.exec(text)) !== null) {
// console.log(`DELIM: ${match.groups!.delimiter} CONTENT: ${match.groups!.content} CLOSER: ${match.groups!.closer}`) console.log(`DELIM: ${match.groups!.delimiter} CONTENT: ${match.groups!.content} CLOSER: ${match.groups!.closer}`);
if (match.index > lastIndex) { if (match.index > lastIndex) {
fragment.appendChild(document.createTextNode(text.slice(lastIndex, match.index))); fragment.appendChild(document.createTextNode(text.slice(lastIndex, match.index)));
} }
@ -482,7 +483,7 @@ export class RibbitEditor extends Ribbit {
fragment.appendChild(document.createTextNode(text.slice(lastIndex))); fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
} }
// console.log(`'${t}' => '${text.replace('\u200C', '|')}`, fragment.children); console.log(`'${t}' => '${text.replace('\u200C', '|')}`, fragment.children);
return fragment; return fragment;
} }
@ -502,8 +503,6 @@ export class RibbitEditor extends Ribbit {
const caretOffset = this.#getCaretOffset(block); const caretOffset = this.#getCaretOffset(block);
const lineText = normalizeLineText(block.textContent!); const lineText = normalizeLineText(block.textContent!);
console.log(`'${block.textContent}' normalized to '${lineText}'`);
const newBlock = this.#buildBlock(lineText); const newBlock = this.#buildBlock(lineText);
block.className = newBlock.className; block.className = newBlock.className;
block.innerHTML = ''; block.innerHTML = '';
@ -518,6 +517,7 @@ export class RibbitEditor extends Ribbit {
lastChild.textContent = lastChild.textContent!.replace(/ $/, '\u00A0'); lastChild.textContent = lastChild.textContent!.replace(/ $/, '\u00A0');
} }
} }
*/
if (lineText.endsWith(' ')) { if (lineText.endsWith(' ')) {
// Find the last text node in the block (may be nested inside spans) // Find the last text node in the block (may be nested inside spans)
const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT); const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT);
@ -529,17 +529,13 @@ export class RibbitEditor extends Ribbit {
if (lastTextNode) { if (lastTextNode) {
lastTextNode.textContent = lastTextNode.textContent!.replace(/ $/, '\u00A0'); lastTextNode.textContent = lastTextNode.textContent!.replace(/ $/, '\u00A0');
} }
console.log(`updating lastTextNode.textContent to '${lastTextNode!.textContent}'`);
} }
*/
const prefixSpan = block.firstElementChild; const prefixSpan = block.firstElementChild;
const prefixLen = (prefixSpan?.classList.contains(DELIM_CLASS) || const prefixLen = (prefixSpan?.classList.contains(DELIM_CLASS) ||
prefixSpan?.classList.contains(LIST_PREFIX_CLASS)) prefixSpan?.classList.contains(LIST_PREFIX_CLASS))
? prefixSpan.textContent!.length : 0; ? prefixSpan.textContent!.length : 0;
console.log(`${block.textContent!}: ${caretOffset} <= ${prefixLen} && ${prefixSpan} ?`);
if (caretOffset <= prefixLen && prefixSpan) { if (caretOffset <= prefixLen && prefixSpan) {
const sel = window.getSelection()!; const sel = window.getSelection()!;
const range = document.createRange(); const range = document.createRange();

View File

@ -325,6 +325,8 @@ async function runTests() {
const markdown = await getMarkdown(); const markdown = await getMarkdown();
const html = await getHTML(); const html = await getHTML();
console.log(`MARKDOWN: ${markdown}`);
console.log(`HTML: ${html}`);
assert( assert(
markdown === testCase.markdown, markdown === testCase.markdown,
`Round-trip failed.\nExpected: "${testCase.markdown}"\nGot: "${markdown}"` `Round-trip failed.\nExpected: "${testCase.markdown}"\nGot: "${markdown}"`