Skip to content

Commit e619ab0

Browse files
committed
Add remark
1 parent 85b3fde commit e619ab0

7 files changed

Lines changed: 437 additions & 0 deletions

File tree

remark/prism-diff-highlight.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// https://github.com/PrismJS/prism/blob/master/plugins/diff-highlight/prism-diff-highlight.js
2+
module.exports = (Prism) => {
3+
var LANGUAGE_REGEX = /diff-([\w-]+)/i
4+
var HTML_TAG = /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/gi
5+
//this will match a line plus the line break while ignoring the line breaks HTML tags may contain.
6+
var HTML_LINE = RegExp(
7+
/(?:__|[^\r\n<])*(?:\r\n?|\n|(?:__|[^\r\n<])(?![^\r\n]))/.source.replace(/__/g, function () {
8+
return HTML_TAG.source
9+
}),
10+
'gi'
11+
)
12+
13+
var PREFIXES = Prism.languages.diff.PREFIXES
14+
15+
Prism.hooks.add('before-sanity-check', function (env) {
16+
var lang = env.language
17+
if (LANGUAGE_REGEX.test(lang) && !env.grammar) {
18+
env.grammar = Prism.languages[lang] = Prism.languages['diff']
19+
}
20+
})
21+
Prism.hooks.add('before-tokenize', function (env) {
22+
var lang = env.language
23+
if (LANGUAGE_REGEX.test(lang) && !Prism.languages[lang]) {
24+
Prism.languages[lang] = Prism.languages['diff']
25+
}
26+
})
27+
28+
Prism.hooks.add('wrap', function (env) {
29+
var diffLanguage, diffGrammar
30+
31+
if (env.language !== 'diff') {
32+
var langMatch = LANGUAGE_REGEX.exec(env.language)
33+
if (!langMatch) {
34+
return // not a language specific diff
35+
}
36+
37+
diffLanguage = langMatch[1]
38+
diffGrammar = Prism.languages[diffLanguage]
39+
}
40+
41+
// one of the diff tokens without any nested tokens
42+
if (env.type in PREFIXES) {
43+
/** @type {string} */
44+
var content = env.content.replace(HTML_TAG, '') // remove all HTML tags
45+
46+
/** @type {string} */
47+
var decoded = content.replace(/&lt;/g, '<').replace(/&amp;/g, '&')
48+
49+
// remove any one-character prefix
50+
var code = decoded.replace(/(^|[\r\n])./g, '$1')
51+
52+
// highlight, if possible
53+
var highlighted
54+
if (diffGrammar) {
55+
highlighted = Prism.highlight(code, diffGrammar, diffLanguage)
56+
} else {
57+
highlighted = Prism.util.encode(code)
58+
}
59+
60+
// get the HTML source of the prefix token
61+
var prefixToken = new Prism.Token('prefix', PREFIXES[env.type], [/\w+/.exec(env.type)[0]])
62+
var prefix = Prism.Token.stringify(prefixToken, env.language)
63+
64+
// add prefix
65+
var lines = [],
66+
m
67+
HTML_LINE.lastIndex = 0
68+
while ((m = HTML_LINE.exec(highlighted))) {
69+
lines.push(prefix + m[0])
70+
}
71+
if (/(?:^|[\r\n]).$/.test(decoded)) {
72+
// because both "+a\n+" and "+a\n" will map to "a\n" after the line prefixes are removed
73+
lines.push(prefix)
74+
}
75+
env.content = lines.join('')
76+
77+
if (diffGrammar) {
78+
env.classes.push('language-' + diffLanguage)
79+
}
80+
}
81+
})
82+
}

remark/utils.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
const Prism = require('prismjs')
2+
const loadLanguages = require('prismjs/components/')
3+
loadLanguages()
4+
require('./prism-diff-highlight')(Prism)
5+
6+
const HTML_TAG = /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/gi
7+
const PSEUDO_CLASSES = [
8+
'active',
9+
'any-link',
10+
'blank',
11+
'checked',
12+
'current',
13+
'default',
14+
'defined',
15+
'dir',
16+
'disabled',
17+
'drop',
18+
'empty',
19+
'enabled',
20+
'first',
21+
'first-child',
22+
'first-of-type',
23+
'fullscreen',
24+
'future',
25+
'focus',
26+
'focus-visible',
27+
'focus-within',
28+
'has',
29+
'host',
30+
'host',
31+
'host-context',
32+
'hover',
33+
'indeterminate',
34+
'in-range',
35+
'invalid',
36+
'is',
37+
'lang',
38+
'last-child',
39+
'last-of-type',
40+
'left',
41+
'link',
42+
'local-link',
43+
'not',
44+
'nth-child',
45+
'nth-col',
46+
'nth-last-child',
47+
'nth-last-col',
48+
'nth-last-of-type',
49+
'nth-of-type',
50+
'only-child',
51+
'only-of-type',
52+
'optional',
53+
'out-of-range',
54+
'past',
55+
'picture-in-picture',
56+
'placeholder-shown',
57+
'read-only',
58+
'read-write',
59+
'required',
60+
'right',
61+
'root',
62+
'scope',
63+
'state',
64+
'target',
65+
'target-within',
66+
'user-invalid',
67+
'valid',
68+
'visited',
69+
'where',
70+
]
71+
72+
Prism.hooks.add('wrap', (env) => {
73+
if (env.type === 'atrule') {
74+
const content = env.content.replace(HTML_TAG, '')
75+
if (content.startsWith('@apply')) {
76+
env.classes.push('atapply')
77+
}
78+
} else if (env.type === 'pseudo-class') {
79+
if (!new RegExp(`^::?(${PSEUDO_CLASSES.join('|')})`).test(env.content)) {
80+
env.classes = env.classes.filter((x) => x !== 'pseudo-class')
81+
}
82+
}
83+
})
84+
85+
module.exports.addImport = function addImport(tree, mod, name) {
86+
tree.children.unshift({
87+
type: 'import',
88+
value: `import { ${name} as _${name} } from '${mod}'`,
89+
})
90+
return `_${name}`
91+
}
92+
93+
module.exports.addDefaultImport = function addImport(tree, mod, name) {
94+
tree.children.unshift({
95+
type: 'import',
96+
value: `import _${name} from '${mod}'`,
97+
})
98+
return `_${name}`
99+
}
100+
101+
module.exports.addExport = function addExport(tree, name, value) {
102+
tree.children.push({
103+
type: 'export',
104+
value: `export const ${name} = ${JSON.stringify(value)}`,
105+
})
106+
}
107+
108+
module.exports.highlightCode = function highlightCode(code, prismLanguage) {
109+
const isDiff = prismLanguage.startsWith('diff-')
110+
const language = isDiff ? prismLanguage.substr(5) : prismLanguage
111+
const grammar = Prism.languages[isDiff ? 'diff' : language]
112+
if (!grammar) {
113+
console.warn(`Unrecognised language: ${prismLanguage}`)
114+
return Prism.util.encode(code)
115+
}
116+
let highlighted = Prism.highlight(code, grammar, prismLanguage)
117+
118+
return language === 'html'
119+
? highlighted.replace(
120+
/\*\*(.*?)\*\*/g,
121+
(_, text) => `<span class="code-highlight bg-code-highlight">${text}</span>`
122+
)
123+
: highlighted
124+
}

remark/withCodeSamples.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const visit = require('unist-util-visit')
2+
const redent = require('redent')
3+
const { addImport, highlightCode } = require('./utils')
4+
5+
module.exports = () => {
6+
return (tree) => {
7+
let component
8+
let resizableComponent
9+
10+
visit(tree, 'code', (node) => {
11+
if (node.lang !== 'html') return
12+
let hasPreview = false
13+
let previewClassName
14+
let previewCode
15+
let previewSrc
16+
let snippet = node.value
17+
.replace(
18+
/<template\s+(?:class="([^"]*)"\s+)?preview(?:\s+class="([^"]*)")?>(.*?)<\/template>/is,
19+
(m, class1, class2, content) => {
20+
hasPreview = true
21+
previewClassName = class1 || class2
22+
previewCode = content
23+
return ''
24+
}
25+
)
26+
.trim()
27+
if (!hasPreview) {
28+
snippet = node.value
29+
.replace(
30+
/<iframe\s+(?:src="([^"]*)"\s+)?preview(?:\s+src="([^"]*)")?>\s*<\/iframe>/is,
31+
(m, src1, src2) => {
32+
hasPreview = true
33+
previewSrc = src1 || src2
34+
return ''
35+
}
36+
)
37+
.trim()
38+
}
39+
if (!hasPreview) return
40+
if (!snippet) snippet = previewCode
41+
42+
snippet = highlightCode(redent(snippet).trim(), 'html')
43+
44+
const meta = node.meta ? node.meta.trim().split(/\s+/) : []
45+
const resizable = meta.find((x) => /^resizable(:|$)/.test(x))
46+
const color = meta.find((x) => !/^resizable(:|$)/.test(x))
47+
48+
if (resizable && !resizableComponent) {
49+
resizableComponent = addImport(tree, '@/components/CodeSample', 'ResizableCodeSample')
50+
} else if (!resizable && !component) {
51+
component = addImport(tree, '@/components/CodeSample', 'CodeSample')
52+
}
53+
54+
node.type = 'jsx'
55+
node.value = `
56+
<${resizable ? resizableComponent : component}
57+
preview={${JSON.stringify(previewCode)}}
58+
src={${JSON.stringify(previewSrc)}}
59+
snippet={${JSON.stringify(snippet)}}
60+
previewClassName={${JSON.stringify(previewClassName)}}
61+
color={${JSON.stringify(color)}}
62+
min={${JSON.stringify(resizable === 'resizable:min')}}
63+
/>
64+
`.trim()
65+
})
66+
}
67+
}

remark/withNextLinks.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const { addDefaultImport } = require('./utils')
2+
3+
module.exports.withNextLinks = () => {
4+
return (tree) => {
5+
const component = addDefaultImport(tree, 'next/link', 'Link')
6+
7+
function walk(root) {
8+
if (!root.children) return
9+
let i = 0
10+
while (i < root.children.length) {
11+
let node = root.children[i]
12+
if (node.type === 'link' && node.url.startsWith('/')) {
13+
root.children = [
14+
...root.children.slice(0, i),
15+
{ type: 'jsx', value: `<${component} href="${node.url}" passHref><a>` },
16+
...node.children,
17+
{ type: 'jsx', value: `</a></${component}>` },
18+
...root.children.slice(i + 1),
19+
]
20+
i += node.children.length + 2
21+
} else {
22+
i += 1
23+
}
24+
walk(node)
25+
}
26+
}
27+
walk(tree)
28+
}
29+
}

remark/withProse.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const proseComponents = ['Heading']
2+
3+
const isJsNode = (node) => {
4+
return (
5+
['jsx', 'import', 'export'].includes(node.type) &&
6+
!/^<[a-z]+(>|\s)/.test(node.value) &&
7+
!new RegExp(`^<(${proseComponents.join('|')})(>|\\s)`).test(node.value)
8+
)
9+
}
10+
11+
module.exports.withProse = () => {
12+
return (tree) => {
13+
let insideProse = false
14+
tree.children = tree.children.flatMap((node, i) => {
15+
if (insideProse && isJsNode(node)) {
16+
insideProse = false
17+
return [{ type: 'jsx', value: '</div>' }, node]
18+
}
19+
if (!insideProse && !isJsNode(node)) {
20+
insideProse = true
21+
return [
22+
{ type: 'jsx', value: '<div className="prose">' },
23+
node,
24+
...(i === tree.children.length - 1 ? [{ type: 'jsx', value: '</div>' }] : []),
25+
]
26+
}
27+
if (i === tree.children.length - 1 && insideProse) {
28+
return [node, { type: 'jsx', value: '</div>' }]
29+
}
30+
return [node]
31+
})
32+
}
33+
}

remark/withSyntaxHighlighting.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const visit = require('unist-util-visit')
2+
const { highlightCode } = require('./utils')
3+
4+
const colors = {
5+
amber: 'bg-amber-500',
6+
emerald: 'bg-emerald-500',
7+
fuchsia: 'bg-fuchsia-400',
8+
indigo: 'bg-indigo-400',
9+
sky: 'bg-sky-500',
10+
purple: 'bg-purple-400',
11+
rose: 'bg-rose-400',
12+
}
13+
14+
module.exports.withSyntaxHighlighting = () => {
15+
return (tree) => {
16+
visit(tree, 'code', (node) => {
17+
if (node.lang !== null) {
18+
node.type = 'html'
19+
node.value = [
20+
`<div class="my-6 rounded-xl overflow-hidden ${colors[node.meta] || 'bg-gray-800'}">`,
21+
`<pre class="language-${node.lang} ${
22+
colors[node.meta] ? 'bg-black bg-opacity-75' : ''
23+
}">`,
24+
`<code class="language-${node.lang}">`,
25+
highlightCode(node.value, node.lang),
26+
'</code>',
27+
'</pre>',
28+
'</div>',
29+
]
30+
.filter(Boolean)
31+
.join('')
32+
}
33+
})
34+
}
35+
}

0 commit comments

Comments
 (0)