This repository was archived by the owner on Feb 20, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcustom-blocks.js
More file actions
156 lines (133 loc) · 4.61 KB
/
custom-blocks.js
File metadata and controls
156 lines (133 loc) · 4.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
const spaceSeparated = require('space-separated-tokens')
function escapeRegExp (str) {
return str.replace(/[-[]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')
}
const C_NEWLINE = '\n'
const C_FENCE = '|'
function compilerFactory (nodeType) {
let text
let title
return {
blockHeading (node) {
title = this.all(node).join('')
return ''
},
blockBody (node) {
text = this.all(node).map(s => s.replace(/\n/g, '\n| ')).join('\n|\n| ')
return text
},
block (node) {
text = ''
title = ''
this.all(node)
if (title) {
return `[[${nodeType} | ${title}]]\n| ${text}`
} else {
return `[[${nodeType}]]\n| ${text}`
}
},
}
}
module.exports = function blockPlugin (availableBlocks = {}) {
const pattern = Object
.keys(availableBlocks)
.map(escapeRegExp)
.join('|')
if (!pattern) {
throw new Error('remark-custom-blocks needs to be passed a configuration object as option')
}
const regex = new RegExp(`\\[\\[(${pattern})(?: *\\| *(.*))?\\]\\]\n`)
function blockTokenizer (eat, value, silent) {
const now = eat.now()
const keep = regex.exec(value)
if (!keep) return
if (keep.index !== 0) return
const [eaten, blockType, blockTitle] = keep
/* istanbul ignore if - never used (yet) */
if (silent) return true
const linesToEat = []
const content = []
let idx = 0
while ((idx = value.indexOf(C_NEWLINE)) !== -1) {
const next = value.indexOf(C_NEWLINE, idx + 1)
// either slice until next NEWLINE or slice until end of string
const lineToEat = next !== -1 ? value.slice(idx + 1, next) : value.slice(idx + 1)
if (lineToEat[0] !== C_FENCE) break
// remove leading `FENCE ` or leading `FENCE`
const line = lineToEat.slice(lineToEat.startsWith(`${C_FENCE} `) ? 2 : 1)
linesToEat.push(lineToEat)
content.push(line)
value = value.slice(idx + 1)
}
const contentString = content.join(C_NEWLINE)
const stringToEat = eaten + linesToEat.join(C_NEWLINE)
const potentialBlock = availableBlocks[blockType]
const titleAllowed = potentialBlock.title &&
['optional', 'required'].includes(potentialBlock.title)
const titleRequired = potentialBlock.title && potentialBlock.title === 'required'
if (titleRequired && !blockTitle) return
if (!titleAllowed && blockTitle) return
const add = eat(stringToEat)
const exit = this.enterBlock()
const contents = {
type: `${blockType}CustomBlockBody`,
data: {
hName: 'div',
hProperties: {
className: 'custom-block-body',
},
},
children: this.tokenizeBlock(contentString, now),
}
exit()
const blockChildren = [contents]
if (titleAllowed && blockTitle) {
const titleNode = {
type: `${blockType}CustomBlockHeading`,
data: {
hName: potentialBlock.details ? 'summary' : 'div',
hProperties: {
className: 'custom-block-heading',
},
},
children: this.tokenizeInline(blockTitle, now),
}
blockChildren.unshift(titleNode)
}
const classList = spaceSeparated.parse(potentialBlock.classes || '')
return add({
type: `${blockType}CustomBlock`,
children: blockChildren,
data: {
hName: potentialBlock.details ? 'details' : 'div',
hProperties: {
className: ['custom-block', ...classList],
},
},
})
}
const Parser = this.Parser
// Inject blockTokenizer
const blockTokenizers = Parser.prototype.blockTokenizers
const blockMethods = Parser.prototype.blockMethods
blockTokenizers.customBlocks = blockTokenizer
blockMethods.splice(blockMethods.indexOf('fencedCode') + 1, 0, 'customBlocks')
const Compiler = this.Compiler
if (Compiler) {
const visitors = Compiler.prototype.visitors
if (!visitors) return
Object.keys(availableBlocks).forEach(key => {
const compiler = compilerFactory(key)
visitors[`${key}CustomBlock`] = compiler.block
visitors[`${key}CustomBlockHeading`] = compiler.blockHeading
visitors[`${key}CustomBlockBody`] = compiler.blockBody
})
}
// Inject into interrupt rules
const interruptParagraph = Parser.prototype.interruptParagraph
const interruptList = Parser.prototype.interruptList
const interruptBlockquote = Parser.prototype.interruptBlockquote
interruptParagraph.splice(interruptParagraph.indexOf('fencedCode') + 1, 0, ['customBlocks'])
interruptList.splice(interruptList.indexOf('fencedCode') + 1, 0, ['customBlocks'])
interruptBlockquote.splice(interruptBlockquote.indexOf('fencedCode') + 1, 0, ['customBlocks'])
}