19
|
1 |
/******/ (function() { // webpackBootstrap |
|
2 |
/******/ "use strict"; |
|
3 |
/******/ // The require scope |
|
4 |
/******/ var __webpack_require__ = {}; |
|
5 |
/******/ |
|
6 |
/************************************************************************/ |
|
7 |
/******/ /* webpack/runtime/define property getters */ |
|
8 |
/******/ !function() { |
|
9 |
/******/ // define getter functions for harmony exports |
|
10 |
/******/ __webpack_require__.d = function(exports, definition) { |
|
11 |
/******/ for(var key in definition) { |
|
12 |
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { |
|
13 |
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); |
|
14 |
/******/ } |
|
15 |
/******/ } |
9
|
16 |
/******/ }; |
19
|
17 |
/******/ }(); |
|
18 |
/******/ |
|
19 |
/******/ /* webpack/runtime/hasOwnProperty shorthand */ |
|
20 |
/******/ !function() { |
|
21 |
/******/ __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } |
|
22 |
/******/ }(); |
|
23 |
/******/ |
|
24 |
/******/ /* webpack/runtime/make namespace object */ |
|
25 |
/******/ !function() { |
|
26 |
/******/ // define __esModule on exports |
|
27 |
/******/ __webpack_require__.r = function(exports) { |
|
28 |
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { |
|
29 |
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); |
|
30 |
/******/ } |
|
31 |
/******/ Object.defineProperty(exports, '__esModule', { value: true }); |
|
32 |
/******/ }; |
|
33 |
/******/ }(); |
|
34 |
/******/ |
9
|
35 |
/************************************************************************/ |
19
|
36 |
var __webpack_exports__ = {}; |
9
|
37 |
__webpack_require__.r(__webpack_exports__); |
19
|
38 |
/* harmony export */ __webpack_require__.d(__webpack_exports__, { |
|
39 |
/* harmony export */ "parse": function() { return /* binding */ parse; } |
|
40 |
/* harmony export */ }); |
18
|
41 |
let document; |
|
42 |
let offset; |
|
43 |
let output; |
|
44 |
let stack; |
9
|
45 |
/** |
|
46 |
* Matches block comment delimiters |
|
47 |
* |
|
48 |
* While most of this pattern is straightforward the attribute parsing |
|
49 |
* incorporates a tricks to make sure we don't choke on specific input |
|
50 |
* |
|
51 |
* - since JavaScript has no possessive quantifier or atomic grouping |
|
52 |
* we are emulating it with a trick |
|
53 |
* |
|
54 |
* we want a possessive quantifier or atomic group to prevent backtracking |
|
55 |
* on the `}`s should we fail to match the remainder of the pattern |
|
56 |
* |
|
57 |
* we can emulate this with a positive lookahead and back reference |
|
58 |
* (a++)*c === ((?=(a+))\1)*c |
|
59 |
* |
|
60 |
* let's examine an example: |
|
61 |
* - /(a+)*c/.test('aaaaaaaaaaaaad') fails after over 49,000 steps |
|
62 |
* - /(a++)*c/.test('aaaaaaaaaaaaad') fails after 85 steps |
|
63 |
* - /(?>a+)*c/.test('aaaaaaaaaaaaad') fails after 126 steps |
|
64 |
* |
|
65 |
* this is because the possessive `++` and the atomic group `(?>)` |
|
66 |
* tell the engine that all those `a`s belong together as a single group |
|
67 |
* and so it won't split it up when stepping backwards to try and match |
|
68 |
* |
|
69 |
* if we use /((?=(a+))\1)*c/ then we get the same behavior as the atomic group |
|
70 |
* or possessive and prevent the backtracking because the `a+` is matched but |
|
71 |
* not captured. thus, we find the long string of `a`s and remember it, then |
|
72 |
* reference it as a whole unit inside our pattern |
|
73 |
* |
16
|
74 |
* @see http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead |
|
75 |
* @see http://blog.stevenlevithan.com/archives/mimic-atomic-groups |
|
76 |
* @see https://javascript.info/regexp-infinite-backtracking-problem |
9
|
77 |
* |
|
78 |
* once browsers reliably support atomic grouping or possessive |
|
79 |
* quantifiers natively we should remove this trick and simplify |
|
80 |
* |
16
|
81 |
* @type {RegExp} |
9
|
82 |
* |
|
83 |
* @since 3.8.0 |
|
84 |
* @since 4.6.1 added optimization to prevent backtracking on attribute parsing |
|
85 |
*/ |
|
86 |
|
18
|
87 |
const tokenizer = /<!--\s+(\/)?wp:([a-z][a-z0-9_-]*\/)?([a-z][a-z0-9_-]*)\s+({(?:(?=([^}]+|}+(?=})|(?!}\s+\/?-->)[^])*)\5|[^]*?)}\s+)?(\/)?-->/g; |
9
|
88 |
|
|
89 |
function Block(blockName, attrs, innerBlocks, innerHTML, innerContent) { |
|
90 |
return { |
18
|
91 |
blockName, |
|
92 |
attrs, |
|
93 |
innerBlocks, |
|
94 |
innerHTML, |
|
95 |
innerContent |
9
|
96 |
}; |
|
97 |
} |
|
98 |
|
|
99 |
function Freeform(innerHTML) { |
|
100 |
return Block(null, {}, [], innerHTML, [innerHTML]); |
|
101 |
} |
|
102 |
|
|
103 |
function Frame(block, tokenStart, tokenLength, prevOffset, leadingHtmlStart) { |
|
104 |
return { |
18
|
105 |
block, |
|
106 |
tokenStart, |
|
107 |
tokenLength, |
9
|
108 |
prevOffset: prevOffset || tokenStart + tokenLength, |
18
|
109 |
leadingHtmlStart |
9
|
110 |
}; |
|
111 |
} |
|
112 |
/** |
|
113 |
* Parser function, that converts input HTML into a block based structure. |
|
114 |
* |
|
115 |
* @param {string} doc The HTML document to parse. |
|
116 |
* |
|
117 |
* @example |
|
118 |
* Input post: |
|
119 |
* ```html |
|
120 |
* <!-- wp:columns {"columns":3} --> |
|
121 |
* <div class="wp-block-columns has-3-columns"><!-- wp:column --> |
|
122 |
* <div class="wp-block-column"><!-- wp:paragraph --> |
|
123 |
* <p>Left</p> |
|
124 |
* <!-- /wp:paragraph --></div> |
|
125 |
* <!-- /wp:column --> |
|
126 |
* |
|
127 |
* <!-- wp:column --> |
|
128 |
* <div class="wp-block-column"><!-- wp:paragraph --> |
|
129 |
* <p><strong>Middle</strong></p> |
|
130 |
* <!-- /wp:paragraph --></div> |
|
131 |
* <!-- /wp:column --> |
|
132 |
* |
|
133 |
* <!-- wp:column --> |
|
134 |
* <div class="wp-block-column"></div> |
|
135 |
* <!-- /wp:column --></div> |
|
136 |
* <!-- /wp:columns --> |
|
137 |
* ``` |
|
138 |
* |
|
139 |
* Parsing code: |
|
140 |
* ```js |
|
141 |
* import { parse } from '@wordpress/block-serialization-default-parser'; |
|
142 |
* |
|
143 |
* parse( post ) === [ |
|
144 |
* { |
|
145 |
* blockName: "core/columns", |
|
146 |
* attrs: { |
|
147 |
* columns: 3 |
|
148 |
* }, |
|
149 |
* innerBlocks: [ |
|
150 |
* { |
|
151 |
* blockName: "core/column", |
|
152 |
* attrs: null, |
|
153 |
* innerBlocks: [ |
|
154 |
* { |
|
155 |
* blockName: "core/paragraph", |
|
156 |
* attrs: null, |
|
157 |
* innerBlocks: [], |
|
158 |
* innerHTML: "\n<p>Left</p>\n" |
|
159 |
* } |
|
160 |
* ], |
|
161 |
* innerHTML: '\n<div class="wp-block-column"></div>\n' |
|
162 |
* }, |
|
163 |
* { |
|
164 |
* blockName: "core/column", |
|
165 |
* attrs: null, |
|
166 |
* innerBlocks: [ |
|
167 |
* { |
|
168 |
* blockName: "core/paragraph", |
|
169 |
* attrs: null, |
|
170 |
* innerBlocks: [], |
|
171 |
* innerHTML: "\n<p><strong>Middle</strong></p>\n" |
|
172 |
* } |
|
173 |
* ], |
|
174 |
* innerHTML: '\n<div class="wp-block-column"></div>\n' |
|
175 |
* }, |
|
176 |
* { |
|
177 |
* blockName: "core/column", |
|
178 |
* attrs: null, |
|
179 |
* innerBlocks: [], |
|
180 |
* innerHTML: '\n<div class="wp-block-column"></div>\n' |
|
181 |
* } |
|
182 |
* ], |
|
183 |
* innerHTML: '\n<div class="wp-block-columns has-3-columns">\n\n\n\n</div>\n' |
|
184 |
* } |
|
185 |
* ]; |
|
186 |
* ``` |
|
187 |
* @return {Array} A block-based representation of the input HTML. |
|
188 |
*/ |
|
189 |
|
|
190 |
|
18
|
191 |
const parse = doc => { |
9
|
192 |
document = doc; |
|
193 |
offset = 0; |
|
194 |
output = []; |
|
195 |
stack = []; |
|
196 |
tokenizer.lastIndex = 0; |
|
197 |
|
|
198 |
do {// twiddle our thumbs |
|
199 |
} while (proceed()); |
|
200 |
|
|
201 |
return output; |
|
202 |
}; |
|
203 |
|
|
204 |
function proceed() { |
18
|
205 |
const next = nextToken(); |
|
206 |
const [tokenType, blockName, attrs, startOffset, tokenLength] = next; |
19
|
207 |
const stackDepth = stack.length; // We may have some HTML soup before the next block. |
9
|
208 |
|
18
|
209 |
const leadingHtmlStart = startOffset > offset ? offset : null; |
9
|
210 |
|
|
211 |
switch (tokenType) { |
|
212 |
case 'no-more-tokens': |
19
|
213 |
// If not in a block then flush output. |
9
|
214 |
if (0 === stackDepth) { |
|
215 |
addFreeform(); |
|
216 |
return false; |
|
217 |
} // Otherwise we have a problem |
|
218 |
// This is an error |
|
219 |
// we have options |
|
220 |
// - treat it all as freeform text |
|
221 |
// - assume an implicit closer (easiest when not nesting) |
19
|
222 |
// For the easy case we'll assume an implicit closer. |
9
|
223 |
|
|
224 |
|
|
225 |
if (1 === stackDepth) { |
|
226 |
addBlockFromStack(); |
|
227 |
return false; |
19
|
228 |
} // For the nested case where it's more difficult we'll |
9
|
229 |
// have to assume that multiple closers are missing |
19
|
230 |
// and so we'll collapse the whole stack piecewise. |
9
|
231 |
|
|
232 |
|
|
233 |
while (0 < stack.length) { |
|
234 |
addBlockFromStack(); |
|
235 |
} |
|
236 |
|
|
237 |
return false; |
|
238 |
|
|
239 |
case 'void-block': |
|
240 |
// easy case is if we stumbled upon a void block |
19
|
241 |
// in the top-level of the document. |
9
|
242 |
if (0 === stackDepth) { |
|
243 |
if (null !== leadingHtmlStart) { |
|
244 |
output.push(Freeform(document.substr(leadingHtmlStart, startOffset - leadingHtmlStart))); |
|
245 |
} |
|
246 |
|
|
247 |
output.push(Block(blockName, attrs, [], '', [])); |
|
248 |
offset = startOffset + tokenLength; |
|
249 |
return true; |
19
|
250 |
} // Otherwise we found an inner block. |
9
|
251 |
|
|
252 |
|
|
253 |
addInnerBlock(Block(blockName, attrs, [], '', []), startOffset, tokenLength); |
|
254 |
offset = startOffset + tokenLength; |
|
255 |
return true; |
|
256 |
|
|
257 |
case 'block-opener': |
19
|
258 |
// Track all newly-opened blocks on the stack. |
9
|
259 |
stack.push(Frame(Block(blockName, attrs, [], '', []), startOffset, tokenLength, startOffset + tokenLength, leadingHtmlStart)); |
|
260 |
offset = startOffset + tokenLength; |
|
261 |
return true; |
|
262 |
|
|
263 |
case 'block-closer': |
19
|
264 |
// If we're missing an opener we're in trouble |
|
265 |
// This is an error. |
9
|
266 |
if (0 === stackDepth) { |
19
|
267 |
// We have options |
9
|
268 |
// - assume an implicit opener |
|
269 |
// - assume _this_ is the opener |
19
|
270 |
// - give up and close out the document. |
9
|
271 |
addFreeform(); |
|
272 |
return false; |
19
|
273 |
} // If we're not nesting then this is easy - close the block. |
9
|
274 |
|
|
275 |
|
|
276 |
if (1 === stackDepth) { |
|
277 |
addBlockFromStack(startOffset); |
|
278 |
offset = startOffset + tokenLength; |
|
279 |
return true; |
19
|
280 |
} // Otherwise we're nested and we have to close out the current |
|
281 |
// block and add it as a innerBlock to the parent. |
9
|
282 |
|
|
283 |
|
18
|
284 |
const stackTop = stack.pop(); |
|
285 |
const html = document.substr(stackTop.prevOffset, startOffset - stackTop.prevOffset); |
9
|
286 |
stackTop.block.innerHTML += html; |
|
287 |
stackTop.block.innerContent.push(html); |
|
288 |
stackTop.prevOffset = startOffset + tokenLength; |
|
289 |
addInnerBlock(stackTop.block, stackTop.tokenStart, stackTop.tokenLength, startOffset + tokenLength); |
|
290 |
offset = startOffset + tokenLength; |
|
291 |
return true; |
|
292 |
|
|
293 |
default: |
19
|
294 |
// This is an error. |
9
|
295 |
addFreeform(); |
|
296 |
return false; |
|
297 |
} |
|
298 |
} |
|
299 |
/** |
|
300 |
* Parse JSON if valid, otherwise return null |
|
301 |
* |
|
302 |
* Note that JSON coming from the block comment |
|
303 |
* delimiters is constrained to be an object |
|
304 |
* and cannot be things like `true` or `null` |
|
305 |
* |
|
306 |
* @param {string} input JSON input string to parse |
|
307 |
* @return {Object|null} parsed JSON if valid |
|
308 |
*/ |
|
309 |
|
|
310 |
|
|
311 |
function parseJSON(input) { |
|
312 |
try { |
|
313 |
return JSON.parse(input); |
|
314 |
} catch (e) { |
|
315 |
return null; |
|
316 |
} |
|
317 |
} |
|
318 |
|
|
319 |
function nextToken() { |
19
|
320 |
// Aye the magic |
9
|
321 |
// we're using a single RegExp to tokenize the block comment delimiters |
|
322 |
// we're also using a trick here because the only difference between a |
|
323 |
// block opener and a block closer is the leading `/` before `wp:` (and |
|
324 |
// a closer has no attributes). we can trap them both and process the |
18
|
325 |
// match back in JavaScript to see which one it was. |
19
|
326 |
const matches = tokenizer.exec(document); // We have no more tokens. |
9
|
327 |
|
|
328 |
if (null === matches) { |
|
329 |
return ['no-more-tokens']; |
|
330 |
} |
|
331 |
|
18
|
332 |
const startedAt = matches.index; |
|
333 |
const [match, closerMatch, namespaceMatch, nameMatch, attrsMatch |
19
|
334 |
/* Internal/unused. */ |
18
|
335 |
,, voidMatch] = matches; |
|
336 |
const length = match.length; |
|
337 |
const isCloser = !!closerMatch; |
|
338 |
const isVoid = !!voidMatch; |
|
339 |
const namespace = namespaceMatch || 'core/'; |
|
340 |
const name = namespace + nameMatch; |
|
341 |
const hasAttrs = !!attrsMatch; |
|
342 |
const attrs = hasAttrs ? parseJSON(attrsMatch) : {}; // This state isn't allowed |
19
|
343 |
// This is an error. |
9
|
344 |
|
19
|
345 |
if (isCloser && (isVoid || hasAttrs)) {// We can ignore them since they don't hurt anything |
|
346 |
// we may warn against this at some point or reject it. |
9
|
347 |
} |
|
348 |
|
|
349 |
if (isVoid) { |
|
350 |
return ['void-block', name, attrs, startedAt, length]; |
|
351 |
} |
|
352 |
|
|
353 |
if (isCloser) { |
|
354 |
return ['block-closer', name, null, startedAt, length]; |
|
355 |
} |
|
356 |
|
|
357 |
return ['block-opener', name, attrs, startedAt, length]; |
|
358 |
} |
|
359 |
|
|
360 |
function addFreeform(rawLength) { |
18
|
361 |
const length = rawLength ? rawLength : document.length - offset; |
9
|
362 |
|
|
363 |
if (0 === length) { |
|
364 |
return; |
|
365 |
} |
|
366 |
|
|
367 |
output.push(Freeform(document.substr(offset, length))); |
|
368 |
} |
|
369 |
|
|
370 |
function addInnerBlock(block, tokenStart, tokenLength, lastOffset) { |
18
|
371 |
const parent = stack[stack.length - 1]; |
9
|
372 |
parent.block.innerBlocks.push(block); |
18
|
373 |
const html = document.substr(parent.prevOffset, tokenStart - parent.prevOffset); |
9
|
374 |
|
|
375 |
if (html) { |
|
376 |
parent.block.innerHTML += html; |
|
377 |
parent.block.innerContent.push(html); |
|
378 |
} |
|
379 |
|
|
380 |
parent.block.innerContent.push(null); |
|
381 |
parent.prevOffset = lastOffset ? lastOffset : tokenStart + tokenLength; |
|
382 |
} |
|
383 |
|
|
384 |
function addBlockFromStack(endOffset) { |
18
|
385 |
const { |
|
386 |
block, |
|
387 |
leadingHtmlStart, |
|
388 |
prevOffset, |
|
389 |
tokenStart |
|
390 |
} = stack.pop(); |
|
391 |
const html = endOffset ? document.substr(prevOffset, endOffset - prevOffset) : document.substr(prevOffset); |
9
|
392 |
|
|
393 |
if (html) { |
|
394 |
block.innerHTML += html; |
|
395 |
block.innerContent.push(html); |
|
396 |
} |
|
397 |
|
|
398 |
if (null !== leadingHtmlStart) { |
|
399 |
output.push(Freeform(document.substr(leadingHtmlStart, tokenStart - leadingHtmlStart))); |
|
400 |
} |
|
401 |
|
|
402 |
output.push(block); |
|
403 |
} |
|
404 |
|
19
|
405 |
(window.wp = window.wp || {}).blockSerializationDefaultParser = __webpack_exports__; |
|
406 |
/******/ })() |
|
407 |
; |