Skip to content

Commit

Permalink
fix: make markdown TOC links clickable
Browse files Browse the repository at this point in the history
fixes containers/podman-desktop-internal#250
Signed-off-by: Florent Benoit <[email protected]>
  • Loading branch information
benoitf committed Apr 30, 2024
1 parent 73bc52c commit 4254d44
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test('Expect to have readme with URI', async () => {
const markdownContent = screen.queryByRole('region', { name: 'markdown-content' });
expect(markdownContent).toBeInTheDocument();

expect(markdownContent).toContainHTML('<h1>This is my README</h1>');
expect(markdownContent).toContainHTML('<h1 id="this-is-my-readme">This is my README</h1>');
});

test('Expect to have readme with content', async () => {
Expand All @@ -59,7 +59,7 @@ test('Expect to have readme with content', async () => {
// expect Markdown
const markdownContent = screen.getByRole('region', { name: 'markdown-content' });
expect(markdownContent).toBeInTheDocument();
expect(markdownContent).toContainHTML('<h1>my README</h1>');
expect(markdownContent).toContainHTML('<h1 id="my-readme">my README</h1>');
});

test('Expect empty screen if no content', async () => {
Expand Down
46 changes: 46 additions & 0 deletions packages/renderer/src/lib/markdown/Markdown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,49 @@ describe('Custom warnings', () => {
expect(buttonFailedStatus).toBeDefined();
});
});

describe('jump to TOC section', () => {
test('Expect TOC to be clickable', async () => {
await waitRender({
markdown:
'### Title\n#### Topics\n- [Technology](#technology)\n - [Extension features](#extension-features)\n\n\n\n\n## Technology\nhello world',
});

const markdownContent = screen.getByRole('region', { name: 'markdown-content' });
expect(markdownContent).toBeInTheDocument();

// get all the <li> elements
const allLi = screen.getAllByRole('listitem');
// get the first <li> element
const li = allLi[0];
// get the first <a> element
const technologyLink = li.querySelector('a');
// check if the <a> element is defined

expect(technologyLink).toBeDefined();

// check the title
expect(technologyLink).toHaveTextContent('Technology');

// grab the h2 element
const h2 = screen.getByRole('heading', { name: 'Technology' });
// check if the h2 element is defined
expect(h2).toBeDefined();
// check the title
expect(h2).toHaveTextContent('Technology');

// add the scrollIntoView function to the window object
h2.scrollIntoView = vi.fn();

if (technologyLink) {
await fireEvent.click(technologyLink);
}

// check we scrolled to the right section
expect(h2.scrollIntoView).toHaveBeenCalledWith({
behavior: 'smooth',
block: 'start',
inline: 'nearest',
});
});
});
37 changes: 36 additions & 1 deletion packages/renderer/src/lib/markdown/Markdown.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- The markdown rendered has it's own style that you'll have to customize / check against podman desktop
<!-- The markdown rendered has it's own style that you'll have to customize / check against podman desktop
UI guidelines -->
<style lang="postcss">
.markdown > :global(p) {
Expand Down Expand Up @@ -92,6 +92,41 @@ onMount(() => {
htmlExtensions: [directiveHtml({ button, link, warnings })],
});
// remove href values in each anchor using # for links
// and set the attribute data-pd-jump-in-page
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const links = doc.querySelectorAll('a');
links.forEach(link => {
const currentHref = link.getAttribute('href');
// remove and replace href attribute if matching
if (currentHref?.startsWith('#')) {
// get current value of href
link.removeAttribute('href');
// remove from current href the #
const withoutHashHRef = currentHref.substring(1);
// add an attribute to handle onclick
link.setAttribute('data-pd-jump-in-page', withoutHashHRef);
// add a class for cursor
link.classList.add('cursor-pointer');
}
});
// for all h1/h2/h3/h4/h5/h6, add an id attribute being the name of the attibute all in lowercase without spaces (replaced by -)
const headers = doc.querySelectorAll('h1, h2, h3, h4, h5, h6');
headers.forEach(header => {
const headerText = header.textContent;
const headerId = headerText?.toLowerCase().replace(/\s/g, '-');
if (headerId) {
header.setAttribute('id', headerId);
}
});
html = doc.body.innerHTML;
// We create a click listener in order to execute any internal micromark commands
// We add the clickListener here since we're unable to add it in the directive typescript file.
const clickListener = createListener(inProgressMarkdownCommandExecutionCallback);
Expand Down
20 changes: 20 additions & 0 deletions packages/renderer/src/lib/markdown/micromark-listener-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ export function createListener(
const command = e.target.dataset.command;
const expandable = e.target.dataset.expandable;

// if the user click on a a href link containing data-pd-jump-in-page attribute
if (e.target instanceof HTMLAnchorElement) {
// get a matching attribute ?
const hrefId = e.target.getAttribute('data-pd-jump-in-page');

// get a linked ID
if (hrefId) {
const matchingElement = document.getElementById(hrefId);
if (matchingElement) {
matchingElement.scrollIntoView({
behavior: 'smooth',
block: 'start',
inline: 'nearest',
});

return;
}
}
}

// if the user clicked on the toggle of an expandable section
if (expandable) {
executeExpandableToggle(expandable);
Expand Down

0 comments on commit 4254d44

Please sign in to comment.