Compare commits
14 Commits
95f6ffcf86
...
57361f8c55
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57361f8c55 | ||
|
|
a2519a1282 | ||
|
|
f27378dc04 | ||
|
|
9f03721f86 | ||
|
|
0758105e92 | ||
|
|
fe405cb393 | ||
|
|
d23c3e7a67 | ||
|
|
59436a7d39 | ||
|
|
781af3cc1e | ||
|
|
38af541c5d | ||
|
|
80c34a1483 | ||
|
|
c61b8e2f8b | ||
|
|
9748d12ede | ||
|
|
2bbb0ba25f |
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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}"`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user