You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

484 lines
15 KiB

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