-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathindex.js
190 lines (157 loc) · 5.46 KB
/
index.js
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
"use strict";
/*
remark-shortcodes
A remark plugin to allow custom block-level Wordpress/Hugo-like
shortcodes to be parsed as part of the Remark Markdown AST. Note
that this plugin does not apply any transformations, but simply
parses the shortcodes and adds the relevant data to the AST. The
start & end blocks are configurable, but the inner shortcode
including the name and the key="value" pairs must be as expected.
To apply transformations from the shortcodes to HTML or other
formats, please see the example in the README.
e.g
// with no attributes
[[ ShortcodeNameA ]]
// with multiple attributes
[[ ShortcodeNameC a="b" c="d" ]]
// with custom start/end Blocks
{{% ShortcodeNameD a="1" c="2" %}}
// An example node format for a shortcode with attributes
{
type: 'shortcode',
identifier: 'ShortcodeNameC',
attributes: {
a: '1',
c: '2'
}
}
With thanks to official remark plugins:
https://github.com/wooorm/remark-breaks/blob/master/index.js
https://github.com/wooorm/remark-gemoji/blob/master/index.js
*/
module.exports = shortcodes;
function shortcodes(options) {
var startBlock = (options || {}).startBlock || "[[";
var endBlock = (options || {}).endBlock || "]]";
var inlineMode = (options || {}).inlineMode || false;
if (isRemarkParser(this.Parser)) {
var parser = this.Parser.prototype;
var tokenizers = inlineMode ? parser.inlineTokenizers : parser.blockTokenizers;
var methods = inlineMode ? parser.inlineMethods : parser.blockMethods;
tokenizers.shortcode = shortcodeTokenizer;
methods.splice(methods.indexOf("html"), 0, "shortcode");
}
if (isRemarkCompiler(this.Compiler)) {
var compiler = this.Compiler.prototype;
compiler.visitors.shortcode = shortcodeCompiler;
}
function locator(value, fromIndex) {
return value.indexOf(startBlock, fromIndex);
}
function shortcodeTokenizer(eat, value, silent) {
var entireShortcode;
var innerShortcode;
var parsedShortcode;
var endPosition;
var endBlockPosition;
if (!value.startsWith(startBlock)) return;
endBlockPosition = value.indexOf(endBlock, startBlock.length);
if (endBlockPosition === -1) return;
endPosition = endBlockPosition + endBlock.length;
entireShortcode = value.slice(0, endPosition);
innerShortcode = value.slice(startBlock.length, endBlockPosition);
parsedShortcode = parseShortcode(innerShortcode);
// If there is no parsed data, something fishy is up - return nothing.
if (!parsedShortcode) return;
/* Exit with true in silent mode after successful parse - never used (yet) */
/* istanbul ignore if */
if (silent) {
return true;
}
return eat(entireShortcode)({
type: "shortcode",
identifier: parsedShortcode.identifier,
attributes: parsedShortcode.attributes
});
}
shortcodeTokenizer.locator = locator;
function shortcodeCompiler(node) {
var attributes = "";
var keys = Object.keys(node.attributes || {});
if (keys.length > 0) {
attributes =
" " +
keys
.map(function(key) {
return key + '="' + node.attributes[key] + '"';
})
.join(" ");
}
return startBlock + " " + node.identifier + attributes + " " + endBlock;
}
}
/**
* Parses the inner shortcode to extract shortcode name & key-value attributes.
* @param {string} innerShortcode - Extracted shortcode from between the start & end blocks.
*/
function parseShortcode(innerShortcode) {
var trimmedInnerShortcode = innerShortcode.trim();
// If no shortcode, it was blank between the blocks - return nothing.
if (!trimmedInnerShortcode) return;
// If no whitespace, then shortcode is just name with no attributes.
if (!hasWhiteSpace(trimmedInnerShortcode)) {
return { identifier: trimmedInnerShortcode, attributes: {} };
}
var splitShortcode = trimmedInnerShortcode.match(/^(\S+)\s(.*)/).slice(1);
var shortcodeName = splitShortcode[0];
var attributeString = splitShortcode[1];
var attributes = parseShortcodeAttributes(attributeString);
// If no attributes parsed, something went wrong - return nothing.
if (!attributes) return;
return {
identifier: shortcodeName,
attributes: attributes
};
}
/**
* Discovers if a string contains any whitespace.
* @param {string} s - the string to test for whitespace.
*/
function hasWhiteSpace(s) {
return /\s/g.test(s);
}
/**
* Parses the key/value attributes from the attribute string.
* @param {string} attributeString - e.g 'a="b" c=2 e="3"'
*/
function parseShortcodeAttributes(attributeString) {
var attributes = {};
var attrMatch = attributeString
.trim()
.match(/(?:[\w-]*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^}\s]+))/g);
if (attrMatch) {
attrMatch.map(function(item) {
var split = item.split("=");
var key = split.shift().trim();
// Strip surrounding quotes from value, if they exist.
var val = split
.join("=")
.trim()
.replace(/^"(.*)"$/, "$1");
attributes[key] = val;
});
}
return attributes;
}
function isRemarkParser(parser) {
return Boolean(
parser &&
parser.prototype &&
parser.prototype.inlineTokenizers &&
parser.prototype.inlineTokenizers.break &&
parser.prototype.inlineTokenizers.break.locator
);
}
function isRemarkCompiler(compiler) {
return Boolean(compiler && compiler.prototype);
}