Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/__tests__/content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,37 @@ describe('parseDoc', () => {
expect(page.title).toBe('indented-code');
});

it.each([
['four spaces', ' '],
['a tab', '\t'],
])('keeps component attributes indented with %s', (_kind, indent) => {
const page = parseDoc(
'tutorial/indented-attributes.md',
['<PageHero', `${indent}title="Hero"`, `${indent}lede="Lede text"`, '/>'].join('\n'),
);

expect(page.components).toEqual([
{ attributes: { lede: 'Lede text', title: 'Hero' }, name: 'PageHero' },
]);
expect(page.title).toBe('Hero');
expect(page.summary).toBe('Lede text');
});

it('keeps a component nested four spaces under a list item', () => {
const page = parseDoc(
'tutorial/list-component.md',
'1. Step\n\n <Figure caption="Step one" />',
);

expect(page.components).toEqual([{ attributes: { caption: 'Step one' }, name: 'Figure' }]);
});

it('keeps an indented lazy continuation in the summary', () => {
const page = parseDoc('tutorial/lazy.md', 'Intro line one\n continued lazily here');

expect(page.summary).toBe('Intro line one continued lazily here');
});

it('keeps shorter nested fences inside a longer fenced example', () => {
const page = parseDoc(
'tutorial/nested-fence.md',
Expand Down
79 changes: 51 additions & 28 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,22 +132,56 @@ function transitionFence(line: string, current: MarkdownFence | undefined): Fenc
return { delimiter: false, fence: current };
}

function isIndentedCodeLine(line: string): boolean {
const candidate = quoteContainer(line).rest;
if (/^(?: {4}|\t)/.test(candidate)) return true;
const list = /^ {0,3}(?:[-+*]|\d{1,9}[.)])([ \t]+)(.*)$/.exec(candidate);
if (!list) return false;
const padding = list[1] ?? '';
return padding.includes('\t') || padding.length >= 5 || /^(?: {4}|\t)/.test(list[2] ?? '');
function indentWidth(line: string): number {
let width = 0;
for (const character of line) {
if (character === ' ') width += 1;
else if (character === '\t') width += 4 - (width % 4);
else break;
}
return width;
}

function extractTitle(body: string): string | undefined {
function proseLines(body: string): (string | undefined)[] {
let fence: MarkdownFence | undefined;
for (const line of body.split(/\r?\n/)) {
let afterBreak = true;
let inCode = false;
let listIndent = 0;
return body.split(/\r?\n/).map((line) => {
const transition = transitionFence(line, fence);
fence = transition.fence;
if (transition.delimiter || fence) continue;
if (isIndentedCodeLine(line)) continue;
if (transition.delimiter || fence) {
afterBreak = true;
inCode = false;
return undefined;
}
const candidate = quoteContainer(line).rest;
if (candidate.trim() === '') {
afterBreak = true;
return line;
}
const width = indentWidth(candidate);
if (afterBreak && width < listIndent) listIndent = 0;
inCode = width - listIndent >= 4 && (afterBreak || inCode);
afterBreak = false;
if (inCode) return undefined;
const item = /^ {0,3}(?:[-+*]|\d{1,9}[.)])([ \t]+)(.*)$/.exec(candidate);
if (!item) return line;
const padding = item[1] ?? '';
const marker = candidate.length - (item[2] ?? '').length - padding.length;
if (padding.includes('\t') || padding.length >= 5 || /^(?: {4}|\t)/.test(item[2] ?? '')) {
listIndent = marker + 1;
inCode = true;
return undefined;
}
listIndent = marker + padding.length;
return line;
});
}

function extractTitle(body: string): string | undefined {
for (const line of proseLines(body)) {
if (line === undefined) continue;
const match = /^#\s+(.+?)\s*$/.exec(line);
if (match?.[1]) return cleanInline(match[1].replace(/\s+#+\s*$/, ''));
}
Expand All @@ -164,7 +198,6 @@ function truncate(value: string, length: number): string {
function extractSummary(body: string): string {
const paragraphs: string[] = [];
let current: string[] = [];
let fence: MarkdownFence | undefined;
let inContainer = false;
let inTag = false;
let hiddenTag: 'script' | 'style' | undefined;
Expand All @@ -174,14 +207,8 @@ function extractSummary(body: string): string {
current = [];
};

for (const rawLine of body.split(/\r?\n/)) {
const transition = transitionFence(rawLine, fence);
fence = transition.fence;
if (transition.delimiter || fence) {
finishParagraph();
continue;
}
if (isIndentedCodeLine(rawLine)) {
for (const rawLine of proseLines(body)) {
if (rawLine === undefined) {
finishParagraph();
continue;
}
Expand Down Expand Up @@ -251,14 +278,10 @@ function parseAttributes(source: string): Readonly<Record<string, string | true>
}

function componentSource(body: string): string {
const lines: string[] = [];
let fence: MarkdownFence | undefined;
for (const line of body.split(/\r?\n/)) {
const transition = transitionFence(line, fence);
fence = transition.fence;
if (!transition.delimiter && !fence && !isIndentedCodeLine(line)) lines.push(line);
}
return lines.join('\n').replace(/<!--[\s\S]*?-->/g, ' ');
return proseLines(body)
.filter((line) => line !== undefined)
.join('\n')
.replace(/<!--[\s\S]*?-->/g, ' ');
}

function extractComponents(body: string): DocComponent[] {
Expand Down
Loading