Skip to content

Commit 2f82204

Browse files
committed
Better support for custom tags
1 parent 822606d commit 2f82204

6 files changed

Lines changed: 183 additions & 32 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,17 @@ npm run dev
1414
```
1515

1616
This is a Lancer project. Read more at [lancer.studio](https://lancer.studio)
17+
18+
## Custom Tags
19+
20+
When writing custom tags such as `<aside>`, etc. in markdown content, be sure to surrounding newlines like this:
21+
22+
```html
23+
Some content
24+
25+
<aside>
26+
27+
My aside content
28+
29+
</aside>
30+
```

client/docs/core-concepts/installation.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
Congrats on making it here, Solidity developer and/or EVM enthusiast. It’s time to level up your fundamental, low-level, and intricate knowledge of the beloved platform you build on day after day.
88

9-
In this course you will learn **binary** and **opcodes**. You will learn **stack** and **memory manipulation**. You will learn how to think in **32-byte words**, how to **wrangle bits**, and how to **emulate data types** with raw 0’s and 1’s.
9+
<aside x y="20">
1010

11-
You will gain the ability to *pierce through* Solidity code, seeing and understanding the opcodes underneath, allowing you to make conscious and strategic decisions on **optimizing gas costs** of your smart contract functions.
11+
In summary, this course will enhance your development skills,
1212

13-
You will gain the power to **write smart contracts without Solidity**, and – like a martial art – hope to never see the day when you need to use it.
13+
*particularly* if you are a Solidity developer.
1414

15-
In summary, this course will enhance your development skills, *particularly* if you are a Solidity developer.
15+
</aside>
1616

1717
## Why learn the EVM?
1818

client/styles/global.css

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,9 @@ body {
3434
@apply text-gray-600 hover:cursor-default;
3535
}
3636

37-
.ChapterLink.-coming-soon span.title {
38-
cursor: not-allowed;
37+
.no-y-content-margin > :first-child {
38+
margin-top: 0;
3939
}
40-
.ChapterLink a.title {
41-
@apply text-gray-700 dark:text-zinc-100 font-light;
42-
}
43-
.ChapterLink span.title {
44-
text-decoration: underline;
45-
}
46-
.ChapterLink span.coming-soon {
47-
font-size: 80%;
48-
@apply text-gray-600 dark:text-zinc-300;
40+
.no-y-content-margin > :last-child {
41+
margin-bottom: 0;
4942
}

lib/docs.js

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,34 @@
11
import markdownit from 'markdown-it'
2+
import { HtmlBlockPlugin } from './markdown/html-block-plugin.mjs'
3+
import { CustomTagsPlugin } from './markdown/custom-tags-plugin.mjs'
24

3-
export * from "./docs-chapters.mjs";
4-
5-
const renderer = markdownit()
6-
renderer.use(md => {
7-
const defaultRender = md.renderer.rules.html_block || function(tokens, idx, options, env, self) {
8-
return self.renderToken(tokens, idx, options);
9-
};
10-
11-
md.renderer.rules.html_block = function(tokens, idx, options, env, self) {
12-
const token = tokens[idx];
13-
if (token.content.startsWith('<chapter>') && token.content.endsWith('</chapter>')) {
14-
const title = token.content.slice(9, -10).trim();
15-
return `<h1 class="chapter">${title}</h1>`; // Custom transformation
16-
}
17-
return defaultRender(tokens, idx, options, env, self);
18-
};
19-
})
5+
export * from "./docs-chapters.mjs"
206

217
export function renderDocsContent(source) {
228
return renderer.render(source)
239
}
10+
11+
//
12+
// Define custom behavior here
13+
//
14+
const CUSTOM_TAGS = {
15+
aside(body, attrs) {
16+
return `
17+
<aside>
18+
<div class="AsideContent">
19+
<!-- Heroicon name: information-circle -->
20+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
21+
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
22+
</svg>
23+
<div class="no-y-content-margin">
24+
${renderer.render(body)}
25+
</div>
26+
</div>
27+
</aside>
28+
`
29+
},
30+
}
31+
32+
const renderer = markdownit({ html: true })
33+
renderer.use(HtmlBlockPlugin())
34+
renderer.use(CustomTagsPlugin(CUSTOM_TAGS))
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { HTML_OPEN_TAG_RE } from './html-block-plugin.mjs'
2+
3+
export function CustomTagsPlugin(customTags) {
4+
return md => {
5+
const defaultRender = md.renderer.rules.html_block || function(tokens, idx, options, env, self) {
6+
return self.renderToken(tokens, idx, options)
7+
}
8+
9+
md.renderer.rules.html_block = function(tokens, idx, options, env, self) {
10+
const token = tokens[idx]
11+
const match = token.content.match(HTML_OPEN_TAG_RE)
12+
const tag = match?.[1]
13+
const attrsRaw = match?.[2]
14+
15+
if (!match || !tag || !customTags[tag]) {
16+
return defaultRender(tokens, idx, options, env, self)
17+
}
18+
19+
if (new RegExp(`</${tag}>\n?`).test(token.content)) {
20+
// Closing tag present, thus full content is here
21+
const attrs = parseAttrs(attrsRaw)
22+
const content = token.content.slice(match[0].length, -(`</${tag}>\n`.length))
23+
return customTags[tag](content, attrs)
24+
}
25+
else {
26+
console.warn('No closing tag found for', tag)
27+
return defaultRender(tokens, idx, options, env, self)
28+
}
29+
}
30+
}
31+
}
32+
33+
34+
function parseAttrs(str) {
35+
const attrs = {}
36+
console.log("bruh", str.trim().split(/\s+/))
37+
for (let row of str.trim().split(/\s+/)) {
38+
console.log("ROW", row)
39+
if (/\=/.test(row)) {
40+
let [key, value] = row.split('=')
41+
if (
42+
value.startsWith(`"`) && value.endsWith(`"`) ||
43+
value.startsWith(`'`) && value.endsWith(`'`)
44+
) {
45+
value = value.slice(1, -1)
46+
}
47+
attrs[key] = /^\d+$/.test(value) ? parseInt(value) : value
48+
}
49+
else {
50+
attrs[row] = true
51+
}
52+
}
53+
return attrs
54+
}

lib/markdown/html-block-plugin.mjs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
2+
// Taken & modified from markdown-it source
3+
const attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*'
4+
5+
const unquoted = '[^"\'=<>`\\x00-\\x20]+'
6+
const single_quoted = "'[^']*'"
7+
const double_quoted = '"[^"]*"'
8+
9+
const attr_value = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')'
10+
11+
const attribute = '(?:\\s+' + attr_name + '(?:\\s*=\\s*' + attr_value + ')?)'
12+
13+
const open_tag = '<([A-Za-z][A-Za-z0-9\\-]*)(' + attribute + '*)\\s*>'
14+
15+
export const HTML_OPEN_TAG_RE = new RegExp('^' + open_tag)
16+
17+
/**
18+
* Plugin for markdown-it to support HTML tags that span over multiple lines
19+
*/
20+
export function HtmlBlockPlugin() {
21+
return md => {
22+
md.core.ruler.push('html_block_plugin', function(state) {
23+
const tokens = []
24+
// console.log("TOKENS", state.tokens.map((t, i) => [i, t.type, t.content]))
25+
for (let i = 0; i < state.tokens.length; i++) {
26+
if (state.tokens[i].type !== 'html_block') {
27+
tokens.push(state.tokens[i])
28+
continue
29+
}
30+
// const tag = state.tokens[i].content.match(/^<([a-z-]+)>\n?(.*)/mi)?.[1]
31+
const match = state.tokens[i].content.match(HTML_OPEN_TAG_RE)
32+
33+
if (!match) {
34+
tokens.push(state.tokens[i])
35+
continue
36+
}
37+
const [, tag, attrs] = match
38+
console.log("MATCH", tag, attrs)
39+
40+
const endTag = `</${tag}>\n`
41+
42+
let endIdx = i + 1
43+
while (endIdx < state.tokens.length && state.tokens[endIdx].content !== endTag) {
44+
endIdx++
45+
}
46+
if (endIdx < state.tokens.length && state.tokens[endIdx].content === endTag) {
47+
// console.log("END", i, endIdx, state.tokens[endIdx])
48+
const body = collectContent(state, i, endIdx)
49+
// console.log("->>", body)
50+
const token = new state.Token('html_block', '', 0)
51+
token.block = true
52+
token.level = state.level
53+
token.content = `<${tag}${attrs}>${body}</${tag}>`
54+
tokens.push(token)
55+
i = endIdx
56+
continue
57+
}
58+
else {
59+
console.warn('No closing tag found for <chapter>')
60+
}
61+
}
62+
// console.log("TOKENS", tokens.map((t, i) => [i, t.type, t.content]))
63+
state.tokens = tokens
64+
})
65+
}
66+
}
67+
68+
function collectContent(state, start, end) {
69+
let content = [];
70+
for (let i = start + 1; i < end; i++) {
71+
const token = state.tokens[i];
72+
if (token.type === 'inline') {
73+
content.push(token.content);
74+
} else if (token.type === 'html_block') {
75+
content.push(token.content)
76+
}
77+
}
78+
return content.join('\n\n').trim()
79+
}

0 commit comments

Comments
 (0)