Skip to content

Commit 83e5f7c

Browse files
authored
feat: simple markdown component (#378)
* feat: simple markdown component Signed-off-by: Philippe Martin <[email protected]> * refactor: use svelte 5 Signed-off-by: Philippe Martin <[email protected]> --------- Signed-off-by: Philippe Martin <[email protected]>
1 parent 65b2fc4 commit 83e5f7c

File tree

4 files changed

+340
-3
lines changed

4 files changed

+340
-3
lines changed

packages/webview/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@xterm/xterm": "^5.5.0",
5050
"humanize-duration": "^3.33.0",
5151
"jsdom": "^27.0.0",
52+
"micromark": "^4.0.2",
5253
"monaco-editor": "^0.54.0",
5354
"prettier": "^3.6.1",
5455
"prettier-plugin-svelte": "^3.3.3",
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**********************************************************************
2+
* Copyright (C) 2023-2025 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
***********************************************************************/
18+
19+
import '@testing-library/jest-dom/vitest';
20+
21+
import { render, screen } from '@testing-library/svelte';
22+
import { expect, test } from 'vitest';
23+
24+
import Markdown from './Markdown.svelte';
25+
26+
test('Expect to have bold', async () => {
27+
const component = render(Markdown, { markdown: '**bold**' });
28+
const markdownContent = screen.getByRole('region', { name: 'markdown-content' });
29+
expect(markdownContent).toBeInTheDocument();
30+
expect(markdownContent).toContainHTML('<strong>bold</strong>');
31+
32+
await component.rerender({ markdown: '**bold2**' });
33+
expect(markdownContent).toContainHTML('<strong>bold2</strong>');
34+
});
35+
36+
test('Expect to have italic', async () => {
37+
const component = render(Markdown, { markdown: '_italic_' });
38+
const markdownContent = screen.getByRole('region', { name: 'markdown-content' });
39+
expect(markdownContent).toBeInTheDocument();
40+
expect(markdownContent).toContainHTML('<em>italic</em>');
41+
42+
await component.rerender({ markdown: '_italic2_' });
43+
expect(markdownContent).toContainHTML('<em>italic2</em>');
44+
});
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<!-- The markdown rendered has it's own style that you'll have to customize / check against podman desktop
2+
UI guidelines -->
3+
<style lang="postcss">
4+
.markdown > :global(p) {
5+
line-height: normal;
6+
padding-bottom: 8px;
7+
margin-bottom: 8px;
8+
}
9+
10+
.markdown > :global(h1),
11+
:global(h2),
12+
:global(h3),
13+
:global(h4),
14+
:global(h5) {
15+
font-size: revert;
16+
line-height: normal;
17+
font-weight: revert;
18+
border-bottom: 1px solid #444;
19+
margin-bottom: 20px;
20+
}
21+
22+
.markdown > :global(ul) {
23+
line-height: normal;
24+
list-style: revert;
25+
margin: revert;
26+
padding: revert;
27+
}
28+
29+
.markdown > :global(b),
30+
:global(strong) {
31+
font-weight: 600;
32+
}
33+
.markdown > :global(blockquote) {
34+
opacity: 0.8;
35+
line-height: normal;
36+
}
37+
.markdown :global(a) {
38+
color: var(--pd-link);
39+
text-decoration: none;
40+
border-radius: 4px;
41+
}
42+
.markdown :global(a):hover {
43+
background-color: var(--pd-link-hover-bg);
44+
}
45+
</style>
46+
47+
<script lang="ts">
48+
import { micromark } from 'micromark';
49+
import { onMount, type Snippet } from 'svelte';
50+
51+
let text = $state('');
52+
let html = $state('');
53+
54+
// Optional attribute to specify the markdown to use
55+
// the user can use: <Markdown>**bold</Markdown> or <Markdown markdown="**bold**" /> syntax
56+
interface Props {
57+
markdown?: string;
58+
children?: Snippet;
59+
}
60+
let { markdown = '', children }: Props = $props();
61+
62+
// Render the markdown or the html+micromark markdown reactively
63+
$effect(() => {
64+
markdown
65+
? // eslint-disable-next-line sonarjs/no-nested-assignment
66+
(html = micromark(markdown))
67+
: undefined;
68+
});
69+
70+
onMount(() => {
71+
if (markdown) {
72+
text = markdown;
73+
}
74+
html = micromark(text);
75+
});
76+
</script>
77+
78+
<!-- Placeholder to grab the content if people are using <Markdown>**bold</Markdown> -->
79+
<span contenteditable="false" bind:textContent={text} class="hidden">
80+
{@render children?.()}
81+
</span>
82+
83+
<section class="markdown" aria-label="markdown-content">
84+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
85+
{@html html}
86+
</section>

0 commit comments

Comments
 (0)