Skip to content

Commit d5f12ad

Browse files
authored
Merge pull request #157 from AegisJSProject/feature/parser
Add stand-alone `parse()` to parse as a regular function
2 parents 02372c7 + 273d4d2 commit d5f12ad

File tree

6 files changed

+96
-14
lines changed

6 files changed

+96
-14
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [v0.1.6] - 2025-04-08
11+
12+
### Added
13+
- Add stand-alone `parse()` to parse as a regular function
14+
1015
## [v0.1.5] - 2025-02-20
1116

1217
### Fixed

markdown.js

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,57 @@ export function createStyleSheet(path, { media, base = hljsURL } = {}) {
3737
return link;
3838
}
3939

40+
export function parse(input, {
41+
gfm = true,
42+
breaks = false,
43+
silent = false,
44+
langPrefix = 'hljs language-',
45+
fallbackLang = 'plaintext',
46+
addHeadingIDs = true,
47+
idPrefix = null,
48+
allowElements,
49+
allowAttributes,
50+
allowCustomElements,
51+
allowUnknownMarkup,
52+
allowComments,
53+
} = {}) {
54+
const marked = new Marked(
55+
markedHighlight({
56+
langPrefix,
57+
highlight(code, lang) {
58+
const language = hljs.getLanguage(lang) ? lang : fallbackLang;
59+
return hljs.highlight(code, { language }).value;
60+
}
61+
})
62+
);
63+
64+
const frag = document.createDocumentFragment();
65+
let raw = input.replaceAll(/\\`/g, '`');
66+
67+
if (String.dedent instanceof Function && raw.startsWith('\n') && /\n\t*$/.test(raw)) {
68+
const tmp = [raw];
69+
tmp.raw = [raw];
70+
Object.freeze(tmp);
71+
raw = String.dedent(tmp);
72+
}
73+
74+
const parsed = marked.parse(raw, { gfm, breaks, silent });
75+
const doc = Document.parseHTML(parsed, {
76+
allowElements, allowAttributes, allowCustomElements, allowUnknownMarkup,
77+
allowComments,
78+
});
79+
80+
frag.append(...doc.body.childNodes);
81+
82+
if (addHeadingIDs) {
83+
frag.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {
84+
heading.id = typeof idPrefix === 'string' ? `${idPrefix}-${sluggify(heading.textContent)}` : sluggify(heading.textContent);
85+
});
86+
}
87+
88+
return frag;
89+
}
90+
4091
export function createMDParser({
4192
gfm = true,
4293
breaks = false,
@@ -77,11 +128,14 @@ export function createMDParser({
77128
raw = String.dedent(tmp);
78129
}
79130

80-
frag.setHTML(marked.parse(raw, { gfm, breaks, silent }), {
131+
const parsed = marked.parse(raw, { gfm, breaks, silent });
132+
const doc = Document.parseHTML(parsed, {
81133
allowElements, allowAttributes, allowCustomElements, allowUnknownMarkup,
82134
allowComments,
83135
});
84136

137+
frag.append(...doc.body.childNodes);
138+
85139
if (addHeadingIDs) {
86140
frag.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {
87141
heading.id = typeof idPrefix === 'string' ? `${idPrefix}-${sluggify(heading.textContent)}` : sluggify(heading.textContent);
@@ -107,10 +161,10 @@ export async function getMarkdown(url, {
107161
headers.set('Accept', 'text/markdown');
108162
}
109163

110-
const resp = await fetch(url, { mode, referrerPolicy, headers, ...rest });
164+
const resp = await fetch(url, { mode, referrerPolicy, headers, ...rest }).catch(() => Response.error());
111165

112166
if (! resp.ok) {
113-
throw new DOMException(`${resp.url} [${resp.status} ${resp.statusText}]`, 'NetworkError');
167+
throw new DOMException(`${resp.url} [${resp.status}]`, 'NetworkError');
114168
} else if (! resp.headers.get('Content-Type').startsWith('text/markdown')) {
115169
throw new TypeError(`Invalid Content-Type: ${resp.headers.get('Content-Type')}.`);
116170
} else {

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aegisjsproject/markdown",
3-
"version": "0.1.5",
3+
"version": "0.1.6",
44
"description": "Markdown parser for `@aegisjsproject/core`",
55
"keywords": [
66
"aegis",

test/index.html

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111
<script type="module" referrerpolicy="no-referrer" src="./test/index.js"></script>
1212
</head>
1313
<body>
14-
<header id="header"></header>
14+
<header id="header">
15+
<button type="button" class="btn btn-primary" popovertarget="readme-preview" popovertargetaction="show">Show Readme</button>
16+
<button type="button" class="btn btn-primary" popovertarget="preview" popovertargetaction="show">Show MD Preview</button>
17+
</header>
1518
<main>
1619
<section id="readme">
17-
<md-preview src="../README.md"></md-preview>
20+
<md-preview id="readme-preview" src="../README.md" popover="auto"></md-preview>
1821
</section>
1922
<form name="test">
2023
<fieldset>
@@ -30,7 +33,7 @@
3033
</div>
3134
</form>
3235
<section>
33-
<md-preview id="preview">
36+
<md-preview id="preview" popover="auto">
3437
## A Complex Markdown Example
3538

3639
### Headings

test/index.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
1-
import { md, createStyleSheet, getMarkdown, registerLanguages } from '@aegisjsproject/markdown';
1+
import { md, createStyleSheet, getMarkdown, registerLanguages, parse } from '@aegisjsproject/markdown';
2+
import { css } from '@aegisjsproject/core/parsers/css.js';
23
import javascript from 'highlight.js/languages/javascript.min.js';
3-
import css from 'highlight.js/languages/css.min.js';
4+
import cssLang from 'highlight.js/languages/css.min.js';
45
import xml from 'highlight.js/languages/xml.min.js';
56

6-
registerLanguages({ javascript, css, xml });
7+
const styles = css`:host {
8+
color-scheme: light dark;
9+
padding: 0.8rem;
10+
}
11+
12+
:host(:popover-open) {
13+
max-height: 95dvh;
14+
width: 80%;
15+
border: none;
16+
}
17+
18+
:host(:popover-open)::backdrop {
19+
background-color: rgba(0, 0, 0, 0.7);
20+
backdrop-filter: blur(4px);
21+
}`;
22+
23+
registerLanguages({ javascript, css: cssLang, xml });
724

825
document.head.append(
926
createStyleSheet('github', { media: '(prefers-color-scheme: light)' }),
@@ -23,7 +40,7 @@ customElements.define('md-preview', class HTMLMDPreviewElement extends HTMLEleme
2340
constructor() {
2441
super();
2542

26-
this.#shadow = this.attachShadow({ mode: 'closed' });
43+
this.#shadow = this.attachShadow({ mode: 'open' });
2744
const container = document.createElement('div');
2845
container.id = 'container';
2946
container.part.add('container');
@@ -33,6 +50,8 @@ customElements.define('md-preview', class HTMLMDPreviewElement extends HTMLEleme
3350
createStyleSheet('github-dark', { media: '(prefers-color-scheme: dark)' }),
3451
container,
3552
);
53+
54+
this.#shadow.adoptedStyleSheets = [styles];
3655
}
3756

3857
connectedCallback() {
@@ -66,7 +85,7 @@ customElements.define('md-preview', class HTMLMDPreviewElement extends HTMLEleme
6685

6786
set content(val) {
6887
if (typeof val === 'string' && val.length !== 0) {
69-
this.#shadow.getElementById('container').replaceChildren(md`${val}`);
88+
this.#shadow.getElementById('container').replaceChildren(parse(val));
7089
this.scrollIntoView({ behavior: 'smooth', block: 'start' });
7190
} else {
7291
this.#shadow.getElementById('container').replaceChildren();
@@ -98,6 +117,7 @@ document.forms.test.addEventListener('submit', event => {
98117
event.preventDefault();
99118
const data = new FormData(event.target);
100119
document.getElementById('preview').content = data.get('md');
120+
document.getElementById('preview').showPopover();
101121
});
102122

103123
document.forms.test.addEventListener('reset', () => document.getElementById('preview').clear());

0 commit comments

Comments
 (0)