UNPKG

8.96 kBJavaScriptView Raw
1'use strict';
2
3function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
4
5function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
6
7var parseAttr = require('md-attr-parser');
8
9var htmlElemAttr = require('html-element-attributes');
10
11var supportedElements = ['link', 'atxHeading', 'strong', 'emphasis', 'deletion', 'code', 'setextHeading', 'fencedCode'];
12var blockElements = ['atxHeading', 'setextHeading'];
13var particularElements = ['fencedCode'];
14var particularTokenize = {};
15
16var DOMEventHandler = require('./dom-event-handler.js');
17/* Table convertion between type and HTML tagName */
18
19
20var convTypeTag = {
21 image: 'img',
22 link: 'a',
23 heading: 'h1',
24 strong: 'strong',
25 emphasis: 'em',
26 delete: 's',
27 inlineCode: 'code',
28 code: 'code',
29 '*': '*'
30};
31/* This function is a generic function that transform
32 * the tokenize function a node type to a version that understand
33 * attributes.
34 *
35 * The tokenizer function of strong will tokenize **STRONG STRING**
36 * this function extand it to tokenize **STRONG STRING**{list=of attributes}
37 *
38 * - The prefix is '\n' for block node and '' for inline one
39 *
40 * The syntax is for atxHeading ::
41 * ## HEAD TITLE
42 * {attributes}
43 *
44 * Attributes are on the next line.
45 *
46 * - The old parser is the old function user to tokenize
47 * - The config is the configuration of this plugin
48 *
49 */
50
51function tokenizeGenerator(prefix, oldParser, config) {
52 function token(eat, value, silent) {
53 // This we call the old tokenize
54 var self = this;
55 var eaten = oldParser.call(self, eat, value, silent);
56 var index = 0;
57 var parsedAttr;
58 var length = value.length;
59
60 if (!eaten || !eaten.position) {
61 return undefined;
62 }
63
64 var type = convTypeTag[eaten.type];
65 index = eaten.position.end.offset - eaten.position.start.offset; // Then we check for attributes
66
67 if (index + prefix.length < length && value.charAt(index + prefix.length) === '{') {
68 // If any, parse it
69 parsedAttr = parseAttr(value, index + prefix.length, config.mdAttrConfig);
70 } // If parsed configure the node
71
72
73 if (parsedAttr) {
74 if (config.scope && config.scope !== 'none') {
75 var filtredProp = filterAttributes(parsedAttr.prop, config, type);
76
77 if (filtredProp !== {}) {
78 if (eaten.data) {
79 eaten.data.hProperties = filtredProp;
80 } else {
81 eaten.data = {
82 hProperties: filtredProp
83 };
84 }
85 }
86 }
87
88 eaten = eat(prefix + parsedAttr.eaten)(eaten);
89 }
90
91 return eaten;
92 } // Return the new tokenizer function
93
94
95 return token;
96} // A generic function to parse attributes
97
98
99function filterAttributes(prop, config, type) {
100 var scope = config.scope;
101 var extend = config.extend;
102 var allowDangerousDOMEventHandlers = config.allowDangerousDOMEventHandlers;
103 var specific = htmlElemAttr;
104
105 var extendTag = function (extend) {
106 var t = {};
107 Object.getOwnPropertyNames(extend).forEach(function (p) {
108 t[convTypeTag[p]] = extend[p];
109 });
110 return t;
111 }(extend); // Delete empty key/class/id attributes
112
113
114 Object.getOwnPropertyNames(prop).forEach(function (p) {
115 if (p !== 'key' && p !== 'class' && p !== 'id') {
116 prop[p] = prop[p] || '';
117 }
118 });
119
120 var isDangerous = function isDangerous(p) {
121 return DOMEventHandler.indexOf(p) >= 0;
122 };
123
124 var isSpecific = function isSpecific(p) {
125 return type in specific && specific[type].indexOf(p) >= 0;
126 };
127
128 var isGlobal = function isGlobal(p) {
129 return htmlElemAttr['*'].indexOf(p) >= 0 || p.match(/^aria-[a-z]{3,24}$/);
130 };
131
132 var inScope = function inScope(_) {
133 return false;
134 }; // Function used to `or combine` two other function.
135
136
137 var orFunc = function orFunc(fun, fun2) {
138 return function (x) {
139 return fun(x) || fun2(x);
140 };
141 }; // Respect the scope configuration
142
143
144 switch (scope) {
145 case 'none':
146 // Plugin is disabled
147 break;
148
149 case 'permissive':
150 case 'every':
151 if (allowDangerousDOMEventHandlers) {
152 inScope = function inScope(_) {
153 return true;
154 };
155 } else {
156 inScope = function inScope(x) {
157 return !isDangerous(x);
158 };
159 }
160
161 break;
162
163 case 'extended':
164 default:
165 inScope = function inScope(p) {
166 return extendTag && type in extendTag && extendTag[type].indexOf(p) >= 0;
167 };
168
169 inScope = orFunc(inScope, function (p) {
170 return '*' in extendTag && extendTag['*'].indexOf(p) >= 0;
171 });
172 // Or if it in the specific scope, fallthrough
173
174 case 'specific':
175 inScope = orFunc(inScope, isSpecific);
176 // Or if it in the global scope fallthrough
177
178 case 'global':
179 inScope = orFunc(inScope, isGlobal);
180
181 if (allowDangerousDOMEventHandlers) {
182 // If allowed add dangerous attributes to global scope
183 inScope = orFunc(inScope, isDangerous);
184 }
185
186 } // If an attributes isn't in the scope, delete it
187
188
189 Object.getOwnPropertyNames(prop).forEach(function (p) {
190 if (!inScope(p)) {
191 delete prop[p];
192 }
193 });
194 return prop;
195}
196/* This is a special modification of the function tokenizeGenerator
197 * to parse the fencedCode info string and the fallback
198 * customAttr parser
199 *
200 * It's only temporary
201 */
202
203
204function tokenizeFencedCode(oldParser, config) {
205 var prefix = '\n';
206
207 function token(eat, value, silent) {
208 // This we call the old tokenize
209 var self = this;
210 var eaten = oldParser.call(self, eat, value, silent);
211 var parsedAttr;
212 var parsedByCustomAttr = false;
213
214 if (!eaten || !eaten.position) {
215 return undefined;
216 }
217
218 var type = convTypeTag[eaten.type]; // First, parse the info string
219 // which is the 'lang' attributes of 'eaten'.
220
221 if (eaten.lang) {
222 // Then the meta
223 if (eaten.meta) {
224 parsedAttr = parseAttr(eaten.meta);
225 } else {
226 // If it's an old version, we can still find from the attributes
227 // from 'value' ¯\_(ツ)_/¯
228 // Bad hack, will be deleted soon
229 parsedAttr = parseAttr(value, value.indexOf(' '));
230 }
231 } // If parsed configure the node
232
233
234 if (parsedAttr) {
235 if (config.scope && config.scope !== 'none') {
236 var filtredProp = filterAttributes(parsedAttr.prop, config, type);
237
238 if (filtredProp !== {}) {
239 if (eaten.data) {
240 eaten.data.hProperties = _objectSpread({}, eaten.data.hProperties, filtredProp);
241 } else {
242 eaten.data = {
243 hProperties: filtredProp
244 };
245 }
246 }
247 }
248
249 if (parsedByCustomAttr) {
250 eaten = eat(prefix + parsedAttr.eaten)(eaten);
251 }
252 }
253
254 return eaten;
255 } // Return the new tokenizer function
256
257
258 return token;
259}
260
261particularTokenize.fencedCode = tokenizeFencedCode;
262remarkAttr.SUPPORTED_ELEMENTS = supportedElements;
263module.exports = remarkAttr;
264/* Function that is exported */
265
266function remarkAttr(userConfig) {
267 var parser = this.Parser;
268 var defaultConfig = {
269 allowDangerousDOMEventHandlers: false,
270 elements: supportedElements,
271 extend: {},
272 scope: 'extended',
273 mdAttrConfig: undefined
274 };
275
276 var config = _objectSpread({}, defaultConfig, userConfig);
277
278 if (!isRemarkParser(parser)) {
279 throw new Error('Missing parser to attach `remark-attr` [link] (to)');
280 }
281
282 var tokenizers = parser.prototype.inlineTokenizers;
283 var tokenizersBlock = parser.prototype.blockTokenizers; // For each elements, replace the old tokenizer by the new one
284
285 config.elements.forEach(function (elem) {
286 if (supportedElements.indexOf(elem) >= 0) {
287 if (blockElements.indexOf(elem) >= 0) {
288 var oldElem = tokenizersBlock[elem];
289 tokenizersBlock[elem] = tokenizeGenerator('\n', oldElem, config);
290 } else if (particularElements.indexOf(elem) >= 0) {
291 var _oldElem = tokenizersBlock[elem];
292 tokenizersBlock[elem] = particularTokenize[elem](_oldElem, config);
293 } else {
294 var _oldElem2 = tokenizers[elem];
295 var elemTokenize = tokenizeGenerator('', _oldElem2, config);
296 elemTokenize.locator = tokenizers[elem].locator;
297 tokenizers[elem] = elemTokenize;
298 }
299 }
300 });
301}
302
303function isRemarkParser(parser) {
304 return Boolean(parser && parser.prototype && parser.prototype.inlineTokenizers && parser.prototype.inlineTokenizers.link && parser.prototype.inlineTokenizers.link.locator);
305}
\No newline at end of file