Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2864a5e4b8 | |||
| 735bc15011 |
@@ -16,7 +16,7 @@ jobs:
|
|||||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Download pr number
|
- name: Download pr number
|
||||||
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
|
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
|
||||||
with:
|
with:
|
||||||
workflow: ${{ github.event.workflow.id }}
|
workflow: ${{ github.event.workflow.id }}
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
id: pr
|
id: pr
|
||||||
run: echo "id=$(<pr.txt)" >> $GITHUB_OUTPUT
|
run: echo "id=$(<pr.txt)" >> $GITHUB_OUTPUT
|
||||||
- name: Download artifact
|
- name: Download artifact
|
||||||
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
|
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
|
||||||
with:
|
with:
|
||||||
workflow: ${{ github.event.workflow.id }}
|
workflow: ${{ github.event.workflow.id }}
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
|
|||||||
@@ -255,67 +255,10 @@ const parseCodeBlockNode = (node: Element): CodeBlockElement[] | ParagraphElemen
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
const parseListNode = (
|
||||||
const parseListMarkdown = (
|
|
||||||
node: Element,
|
node: Element,
|
||||||
processText: ProcessTextCallback,
|
processText: ProcessTextCallback
|
||||||
depth = 0
|
): OrderedListElement[] | UnorderedListElement[] | ParagraphElement[] => {
|
||||||
): ParagraphElement[] => {
|
|
||||||
const md = isTag(node) && node.name === 'ul' ? '*' : '-';
|
|
||||||
const prefix = node.attribs['data-md'] ?? md;
|
|
||||||
const [starOrHyphen] = prefix.match(/^\*|-$/) ?? [];
|
|
||||||
const [digitOrChar] = prefix.match(/^[\da-zA-Z]/) ?? [];
|
|
||||||
|
|
||||||
const digit = digitOrChar ? parseInt(digitOrChar, 10) : undefined;
|
|
||||||
|
|
||||||
const lines: ParagraphElement[] = [];
|
|
||||||
let lineNo = digit === undefined || Number.isNaN(digit) ? digitOrChar ?? 1 : digit;
|
|
||||||
const pushLine = (line: InlineElement[]) => {
|
|
||||||
lines.push({
|
|
||||||
type: BlockType.Paragraph,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
text: `${Array(depth + 1).join(' ')}${starOrHyphen ? `${starOrHyphen} ` : `${lineNo}. `}`,
|
|
||||||
},
|
|
||||||
...line,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
if (typeof lineNo === 'string') {
|
|
||||||
lineNo = String.fromCharCode(lineNo.charCodeAt(0) + 1);
|
|
||||||
} else {
|
|
||||||
lineNo += 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
node.children.forEach((child) => {
|
|
||||||
if (isText(child)) {
|
|
||||||
pushLine([{ text: processText(child.data) }]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTag(child)) {
|
|
||||||
if (child.name === 'ul' || child.name === 'ol') {
|
|
||||||
lines.push(...parseListMarkdown(child, processText, depth + 1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (child.name === 'li') {
|
|
||||||
child.children.forEach((c) => {
|
|
||||||
if (isTag(c) && (c.name === 'ul' || c.name === 'ol')) {
|
|
||||||
lines.push(...parseListMarkdown(c, processText, depth + 1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pushLine(getInlineElement(c, processText));
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pushLine(getInlineElement(child, processText));
|
|
||||||
});
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
};
|
|
||||||
const parseListLines = (children: ChildNode[], processText: ProcessTextCallback) => {
|
|
||||||
const listLines: Array<InlineElement[]> = [];
|
const listLines: Array<InlineElement[]> = [];
|
||||||
let lineHolder: InlineElement[] = [];
|
let lineHolder: InlineElement[] = [];
|
||||||
|
|
||||||
@@ -326,7 +269,7 @@ const parseListLines = (children: ChildNode[], processText: ProcessTextCallback)
|
|||||||
lineHolder = [];
|
lineHolder = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
children.forEach((child) => {
|
node.children.forEach((child) => {
|
||||||
if (isText(child)) {
|
if (isText(child)) {
|
||||||
lineHolder.push({ text: processText(child.data) });
|
lineHolder.push({ text: processText(child.data) });
|
||||||
return;
|
return;
|
||||||
@@ -349,23 +292,24 @@ const parseListLines = (children: ChildNode[], processText: ProcessTextCallback)
|
|||||||
});
|
});
|
||||||
appendLine();
|
appendLine();
|
||||||
|
|
||||||
return listLines;
|
const mdSequence = node.attribs['data-md'];
|
||||||
};
|
if (mdSequence !== undefined) {
|
||||||
const parseListNode = (
|
const prefix = mdSequence || '-';
|
||||||
node: Element,
|
const [starOrHyphen] = prefix.match(/^\*|-$/) ?? [];
|
||||||
processText: ProcessTextCallback
|
return listLines.map((lineChildren) => ({
|
||||||
): OrderedListElement[] | UnorderedListElement[] | ParagraphElement[] => {
|
type: BlockType.Paragraph,
|
||||||
if (node.attribs['data-md'] !== undefined) {
|
children: [
|
||||||
return parseListMarkdown(node, processText);
|
{ text: `${starOrHyphen ? `${starOrHyphen} ` : `${prefix}. `} ` },
|
||||||
|
...lineChildren,
|
||||||
|
],
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = parseListLines(node.childNodes, processText);
|
|
||||||
|
|
||||||
if (node.name === 'ol') {
|
if (node.name === 'ol') {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: BlockType.OrderedList,
|
type: BlockType.OrderedList,
|
||||||
children: lines.map((lineChildren) => ({
|
children: listLines.map((lineChildren) => ({
|
||||||
type: BlockType.ListItem,
|
type: BlockType.ListItem,
|
||||||
children: lineChildren,
|
children: lineChildren,
|
||||||
})),
|
})),
|
||||||
@@ -376,7 +320,7 @@ const parseListNode = (
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: BlockType.UnorderedList,
|
type: BlockType.UnorderedList,
|
||||||
children: lines.map((lineChildren) => ({
|
children: listLines.map((lineChildren) => ({
|
||||||
type: BlockType.ListItem,
|
type: BlockType.ListItem,
|
||||||
children: lineChildren,
|
children: lineChildren,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { replaceMatch } from '../internal';
|
import { replaceMatch } from '../internal';
|
||||||
import { BlockQuoteRule, CodeBlockRule, ESC_BLOCK_SEQ, HeadingRule, ListRule } from './rules';
|
import {
|
||||||
|
BlockQuoteRule,
|
||||||
|
CodeBlockRule,
|
||||||
|
ESC_BLOCK_SEQ,
|
||||||
|
HeadingRule,
|
||||||
|
OrderedListRule,
|
||||||
|
UnorderedListRule,
|
||||||
|
} from './rules';
|
||||||
import { runBlockRule } from './runner';
|
import { runBlockRule } from './runner';
|
||||||
import { BlockMDParser } from './type';
|
import { BlockMDParser } from './type';
|
||||||
|
|
||||||
@@ -16,7 +23,8 @@ export const parseBlockMD: BlockMDParser = (text, parseInline) => {
|
|||||||
|
|
||||||
if (!result) result = runBlockRule(text, CodeBlockRule, parseBlockMD, parseInline);
|
if (!result) result = runBlockRule(text, CodeBlockRule, parseBlockMD, parseInline);
|
||||||
if (!result) result = runBlockRule(text, BlockQuoteRule, parseBlockMD, parseInline);
|
if (!result) result = runBlockRule(text, BlockQuoteRule, parseBlockMD, parseInline);
|
||||||
if (!result) result = runBlockRule(text, ListRule, parseBlockMD, parseInline);
|
if (!result) result = runBlockRule(text, OrderedListRule, parseBlockMD, parseInline);
|
||||||
|
if (!result) result = runBlockRule(text, UnorderedListRule, parseBlockMD, parseInline);
|
||||||
if (!result) result = runBlockRule(text, HeadingRule, parseBlockMD, parseInline);
|
if (!result) result = runBlockRule(text, HeadingRule, parseBlockMD, parseInline);
|
||||||
|
|
||||||
// replace \n with <br/> because want to preserve empty lines
|
// replace \n with <br/> because want to preserve empty lines
|
||||||
|
|||||||
@@ -10,22 +10,18 @@ export const HeadingRule: BlockMDRule = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// opening fence: 3 or more backticks
|
const CODEBLOCK_MD_1 = '```';
|
||||||
// capture the exact fence length in group 1
|
const CODEBLOCK_REG_1 = /^`{3}(\S*)\n((?:.*\n)+?)`{3} *(?!.)\n?/m;
|
||||||
// optional info string in group 2
|
|
||||||
// code content in group 3
|
|
||||||
// closing fence must match the exact same fence sequence via \1
|
|
||||||
const CODEBLOCK_REG_1 = /^(`{3,})(?!`)(\S*)\n((?:.*\n)+?)\1 *(?!.)\n?/m;
|
|
||||||
export const CodeBlockRule: BlockMDRule = {
|
export const CodeBlockRule: BlockMDRule = {
|
||||||
match: (text) => text.match(CODEBLOCK_REG_1),
|
match: (text) => text.match(CODEBLOCK_REG_1),
|
||||||
html: (match) => {
|
html: (match) => {
|
||||||
const [, fence, g1, g2] = match;
|
const [, g1, g2] = match;
|
||||||
// use last identifier after dot, e.g. for "example.json" gets us "json" as language code.
|
// use last identifier after dot, e.g. for "example.json" gets us "json" as language code.
|
||||||
const langCode = g1 ? g1.substring(g1.lastIndexOf('.') + 1) : null;
|
const langCode = g1 ? g1.substring(g1.lastIndexOf('.') + 1) : null;
|
||||||
const filename = g1 !== langCode ? g1 : null;
|
const filename = g1 !== langCode ? g1 : null;
|
||||||
const classNameAtt = langCode ? ` class="language-${langCode}"` : '';
|
const classNameAtt = langCode ? ` class="language-${langCode}"` : '';
|
||||||
const filenameAtt = filename ? ` data-label="${filename}"` : '';
|
const filenameAtt = filename ? ` data-label="${filename}"` : '';
|
||||||
return `<pre data-md="${fence}"><code${classNameAtt}${filenameAtt}>${g2}</code></pre>`;
|
return `<pre data-md="${CODEBLOCK_MD_1}"><code${classNameAtt}${filenameAtt}>${g2}</code></pre>`;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,146 +48,55 @@ export const BlockQuoteRule: BlockMDRule = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ORDERED_LIST_MD_1 = '-';
|
const ORDERED_LIST_MD_1 = '-';
|
||||||
const UNORDERED_LIST_MD_1 = '*';
|
const O_LIST_ITEM_PREFIX = /^(-|[\da-zA-Z]\.) */;
|
||||||
const LIST_ITEM_REG = /^( *)([-*]|[\da-zA-Z]\.) +(.+)$/;
|
const O_LIST_START = /^([\d])\./;
|
||||||
type ListType = 'ol' | 'ul';
|
const O_LIST_TYPE = /^([aAiI])\./;
|
||||||
|
const O_LIST_TRAILING_NEWLINE = /\n$/;
|
||||||
|
const ORDERED_LIST_REG_1 = /(^(?:-|[\da-zA-Z]\.) +.+\n?)+/m;
|
||||||
|
export const OrderedListRule: BlockMDRule = {
|
||||||
|
match: (text) => text.match(ORDERED_LIST_REG_1),
|
||||||
|
html: (match, parseInline) => {
|
||||||
|
const [listText] = match;
|
||||||
|
const [, listStart] = listText.match(O_LIST_START) ?? [];
|
||||||
|
const [, listType] = listText.match(O_LIST_TYPE) ?? [];
|
||||||
|
|
||||||
function getListType(marker: string): ListType {
|
const lines = listText
|
||||||
return marker === '*' ? 'ul' : 'ol';
|
.replace(O_LIST_TRAILING_NEWLINE, '')
|
||||||
}
|
|
||||||
|
|
||||||
function getOrderedMeta(marker: string) {
|
|
||||||
const startMatch = marker.match(/^(\d)\./);
|
|
||||||
const typeMatch = marker.match(/^([aAiI])\./);
|
|
||||||
|
|
||||||
return {
|
|
||||||
start: startMatch?.[1],
|
|
||||||
type: typeMatch?.[1],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ParsedLine {
|
|
||||||
indent: number;
|
|
||||||
marker: string;
|
|
||||||
content: string;
|
|
||||||
listType: ListType;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseLines(text: string): ParsedLine[] {
|
|
||||||
return text
|
|
||||||
.replace(/\n$/, '')
|
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((line) => {
|
.map((lineText) => {
|
||||||
const match = line.match(LIST_ITEM_REG);
|
const line = lineText.replace(O_LIST_ITEM_PREFIX, '');
|
||||||
|
const txt = parseInline ? parseInline(line) : line;
|
||||||
if (!match) return null;
|
return `<li><p>${txt}</p></li>`;
|
||||||
|
|
||||||
const [, spaces, marker, content] = match;
|
|
||||||
|
|
||||||
return {
|
|
||||||
indent: spaces.length,
|
|
||||||
marker,
|
|
||||||
content,
|
|
||||||
listType: getListType(marker),
|
|
||||||
};
|
|
||||||
})
|
})
|
||||||
.filter(Boolean) as ParsedLine[];
|
.join('');
|
||||||
}
|
|
||||||
|
|
||||||
function openList(line: ParsedLine) {
|
const dataMdAtt = `data-md="${listType || listStart || ORDERED_LIST_MD_1}"`;
|
||||||
if (line.listType === 'ul') {
|
const startAtt = listStart ? ` start="${listStart}"` : '';
|
||||||
return `<ul data-md="${UNORDERED_LIST_MD_1}">`;
|
const typeAtt = listType ? ` type="${listType}"` : '';
|
||||||
}
|
return `<ol ${dataMdAtt}${startAtt}${typeAtt}>${lines}</ol>`;
|
||||||
const { type, start } = getOrderedMeta(line.marker);
|
},
|
||||||
const dataMdAtt = `data-md="${type || start || ORDERED_LIST_MD_1}"`;
|
};
|
||||||
const startAtt = start ? ` start="${start}"` : '';
|
|
||||||
const typeAtt = type ? ` type="${type}"` : '';
|
|
||||||
return `<ol ${dataMdAtt}${startAtt}${typeAtt}>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeList(listType: ListType) {
|
const UNORDERED_LIST_MD_1 = '*';
|
||||||
return listType === 'ul' ? '</ul>' : '</ol>';
|
const U_LIST_ITEM_PREFIX = /^\* */;
|
||||||
}
|
const U_LIST_TRAILING_NEWLINE = /\n$/;
|
||||||
|
const UNORDERED_LIST_REG_1 = /(^\* +.+\n?)+/m;
|
||||||
function buildList(lines: ParsedLine[], parseInline?: (s: string) => string): string {
|
export const UnorderedListRule: BlockMDRule = {
|
||||||
let html = '';
|
match: (text) => text.match(UNORDERED_LIST_REG_1),
|
||||||
|
|
||||||
const stack: ('ul' | 'ol')[] = [];
|
|
||||||
|
|
||||||
lines.forEach((line, index) => {
|
|
||||||
const prev = lines[index - 1];
|
|
||||||
const next = lines[index + 1];
|
|
||||||
|
|
||||||
const content = parseInline ? parseInline(line.content) : line.content;
|
|
||||||
|
|
||||||
// FIRST ITEM
|
|
||||||
if (!prev) {
|
|
||||||
html += openList(line);
|
|
||||||
stack.push(line.listType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// DEEPER INDENT > open nested list
|
|
||||||
else if (line.indent > prev.indent) {
|
|
||||||
html += openList(line);
|
|
||||||
stack.push(line.listType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAME LEVEL
|
|
||||||
else if (line.indent === prev.indent) {
|
|
||||||
html += '</li>';
|
|
||||||
|
|
||||||
// different list type
|
|
||||||
if (line.listType !== prev.listType) {
|
|
||||||
html += closeList(stack.pop()!);
|
|
||||||
|
|
||||||
html += openList(line);
|
|
||||||
stack.push(line.listType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GOING BACK UP
|
|
||||||
else if (line.indent < prev.indent) {
|
|
||||||
html += '</li>';
|
|
||||||
|
|
||||||
while (stack.length > line.indent + 1) {
|
|
||||||
html += closeList(stack.pop()!);
|
|
||||||
html += '</li>';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.listType !== stack[stack.length - 1]) {
|
|
||||||
html += closeList(stack.pop()!);
|
|
||||||
|
|
||||||
html += openList(line);
|
|
||||||
stack.push(line.listType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html += `<li><p>${content}</p>`;
|
|
||||||
|
|
||||||
// LAST ITEM cleanup
|
|
||||||
if (!next) {
|
|
||||||
html += '</li>';
|
|
||||||
|
|
||||||
while (stack.length) {
|
|
||||||
html += closeList(stack.pop()!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LIST_REG_1 = /^(?: *(?:[-*]|[\da-zA-Z]\.) +.+\n?)+/m;
|
|
||||||
export const ListRule: BlockMDRule = {
|
|
||||||
match: (text) => text.match(LIST_REG_1),
|
|
||||||
html: (match, parseInline) => {
|
html: (match, parseInline) => {
|
||||||
const [listText] = match;
|
const [listText] = match;
|
||||||
|
|
||||||
const lines = parseLines(listText);
|
const lines = listText
|
||||||
|
.replace(U_LIST_TRAILING_NEWLINE, '')
|
||||||
|
.split('\n')
|
||||||
|
.map((lineText) => {
|
||||||
|
const line = lineText.replace(U_LIST_ITEM_PREFIX, '');
|
||||||
|
const txt = parseInline ? parseInline(line) : line;
|
||||||
|
return `<li><p>${txt}</p></li>`;
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
|
||||||
const html = buildList(lines, parseInline);
|
return `<ul data-md="${UNORDERED_LIST_MD_1}">${lines}</ul>`;
|
||||||
|
|
||||||
return html;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -120,23 +120,12 @@ export const CodeBlockBottomShadow = style({
|
|||||||
background: `linear-gradient(to top, #00000022, #00000000)`,
|
background: `linear-gradient(to top, #00000022, #00000000)`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const BaseList = style({});
|
|
||||||
export const List = style([
|
export const List = style([
|
||||||
BaseList,
|
|
||||||
DefaultReset,
|
DefaultReset,
|
||||||
MarginSpaced,
|
MarginSpaced,
|
||||||
{
|
{
|
||||||
padding: `0 ${config.space.S100}`,
|
padding: `0 ${config.space.S100}`,
|
||||||
paddingLeft: config.space.S600,
|
paddingLeft: config.space.S600,
|
||||||
selectors: {
|
|
||||||
'& &': {
|
|
||||||
marginTop: config.space.S200,
|
|
||||||
marginBottom: config.space.S200,
|
|
||||||
},
|
|
||||||
'li:last-child &': {
|
|
||||||
marginBottom: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user