PluginProbe
Gutenberg / 18.9.0
Gutenberg v18.9.0
24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 All 403 releases
gutenberg / build / interactivity / router.js

router.js in Gutenberg 18.9.0, at build/interactivity/router.js

595 lines 20.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import * as __WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__ from "@wordpress/interactivity";
2 /******/ var __webpack_modules__ = ({
3
4 /***/ 225:
5 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
6
7 /* harmony export */ __webpack_require__.d(__webpack_exports__, {
8 /* harmony export */ B: () => (/* binding */ updateHead),
9 /* harmony export */ J: () => (/* binding */ fetchHeadAssets)
10 /* harmony export */ });
11 /**
12 * Helper to update only the necessary tags in the head.
13 *
14 * @async
15 * @param newHead The head elements of the new page.
16 */
17 const updateHead = async newHead => {
18 // Helper to get the tag id store in the cache.
19 const getTagId = tag => tag.id || tag.outerHTML;
20
21 // Map incoming head tags by their content.
22 const newHeadMap = new Map();
23 for (const child of newHead) {
24 newHeadMap.set(getTagId(child), child);
25 }
26 const toRemove = [];
27
28 // Detect nodes that should be added or removed.
29 for (const child of document.head.children) {
30 const id = getTagId(child);
31 // Always remove styles and links as they might change.
32 if (child.nodeName === 'LINK' || child.nodeName === 'STYLE') {
33 toRemove.push(child);
34 } else if (newHeadMap.has(id)) {
35 newHeadMap.delete(id);
36 } else if (child.nodeName !== 'SCRIPT' && child.nodeName !== 'META') {
37 toRemove.push(child);
38 }
39 }
40
41 // Prepare new assets.
42 const toAppend = [...newHeadMap.values()];
43
44 // Apply the changes.
45 toRemove.forEach(n => n.remove());
46 document.head.append(...toAppend);
47 };
48
49 /**
50 * Fetches and processes head assets (stylesheets and scripts) from a specified document.
51 *
52 * @async
53 * @param doc The document from which to fetch head assets. It should support standard DOM querying methods.
54 * @param headElements A map of head elements to modify tracking the URLs of already processed assets to avoid duplicates.
55 * @param headElements.tag
56 * @param headElements.text
57 *
58 * @return Returns an array of HTML elements representing the head assets.
59 */
60 const fetchHeadAssets = async (doc, headElements) => {
61 const headTags = [];
62 const assets = [{
63 tagName: 'style',
64 selector: 'link[rel=stylesheet]',
65 attribute: 'href'
66 }, {
67 tagName: 'script',
68 selector: 'script[src]',
69 attribute: 'src'
70 }];
71 for (const asset of assets) {
72 const {
73 tagName,
74 selector,
75 attribute
76 } = asset;
77 const tags = doc.querySelectorAll(selector);
78
79 // Use Promise.all to wait for fetch to complete
80 await Promise.all(Array.from(tags).map(async tag => {
81 const attributeValue = tag.getAttribute(attribute);
82 if (!headElements.has(attributeValue)) {
83 try {
84 const response = await fetch(attributeValue);
85 const text = await response.text();
86 headElements.set(attributeValue, {
87 tag,
88 text
89 });
90 } catch (e) {
91 // eslint-disable-next-line no-console
92 console.error(e);
93 }
94 }
95 const headElement = headElements.get(attributeValue);
96 const element = doc.createElement(tagName);
97 element.innerText = headElement.text;
98 for (const attr of headElement.tag.attributes) {
99 element.setAttribute(attr.name, attr.value);
100 }
101 headTags.push(element);
102 }));
103 }
104 return [doc.querySelector('title'), ...doc.querySelectorAll('style'), ...headTags];
105 };
106
107 /***/ }),
108
109 /***/ 638:
110 /***/ ((module, __webpack_exports__, __webpack_require__) => {
111
112 __webpack_require__.a(module, async (__webpack_handle_async_dependencies__, __webpack_async_result__) => { try {
113 /* harmony export */ __webpack_require__.d(__webpack_exports__, {
114 /* harmony export */ N: () => (/* binding */ actions),
115 /* harmony export */ S: () => (/* binding */ state)
116 /* harmony export */ });
117 /* harmony import */ var _wordpress_interactivity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(998);
118 /* harmony import */ var _head__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(225);
119 var _getConfig$navigation;
120 /**
121 * WordPress dependencies
122 */
123
124
125 /**
126 * Internal dependencies
127 */
128
129 const {
130 directivePrefix,
131 getRegionRootFragment,
132 initialVdom,
133 toVdom,
134 render,
135 parseInitialData,
136 populateInitialData,
137 batch
138 } = (0,_wordpress_interactivity__WEBPACK_IMPORTED_MODULE_0__.privateApis)('I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WordPress.');
139 // Check if the navigation mode is full page or region based.
140 const navigationMode = (_getConfig$navigation = (0,_wordpress_interactivity__WEBPACK_IMPORTED_MODULE_0__.getConfig)('core/router').navigationMode) !== null && _getConfig$navigation !== void 0 ? _getConfig$navigation : 'regionBased';
141
142 // The cache of visited and prefetched pages, stylesheets and scripts.
143 const pages = new Map();
144 const headElements = new Map();
145
146 // Helper to remove domain and hash from the URL. We are only interesting in
147 // caching the path and the query.
148 const getPagePath = url => {
149 const u = new URL(url, window.location.href);
150 return u.pathname + u.search;
151 };
152
153 // Fetch a new page and convert it to a static virtual DOM.
154 const fetchPage = async (url, {
155 html
156 }) => {
157 try {
158 if (!html) {
159 const res = await window.fetch(url);
160 if (res.status !== 200) {
161 return false;
162 }
163 html = await res.text();
164 }
165 const dom = new window.DOMParser().parseFromString(html, 'text/html');
166 return regionsToVdom(dom);
167 } catch (e) {
168 return false;
169 }
170 };
171
172 // Return an object with VDOM trees of those HTML regions marked with a
173 // `router-region` directive.
174 const regionsToVdom = async (dom, {
175 vdom
176 } = {}) => {
177 const regions = {
178 body: undefined
179 };
180 let head;
181 if (true) {
182 if (navigationMode === 'fullPage') {
183 head = await (0,_head__WEBPACK_IMPORTED_MODULE_1__/* .fetchHeadAssets */ .J)(dom, headElements);
184 regions.body = vdom ? vdom.get(document.body) : toVdom(dom.body);
185 }
186 }
187 if (navigationMode === 'regionBased') {
188 const attrName = `data-${directivePrefix}-router-region`;
189 dom.querySelectorAll(`[${attrName}]`).forEach(region => {
190 const id = region.getAttribute(attrName);
191 regions[id] = vdom?.has(region) ? vdom.get(region) : toVdom(region);
192 });
193 }
194 const title = dom.querySelector('title')?.innerText;
195 const initialData = parseInitialData(dom);
196 return {
197 regions,
198 head,
199 title,
200 initialData
201 };
202 };
203
204 // Render all interactive regions contained in the given page.
205 const renderRegions = page => {
206 batch(() => {
207 if (true) {
208 if (navigationMode === 'fullPage') {
209 // Once this code is tested and more mature, the head should be updated for region based navigation as well.
210 (0,_head__WEBPACK_IMPORTED_MODULE_1__/* .updateHead */ .B)(page.head);
211 const fragment = getRegionRootFragment(document.body);
212 render(page.regions.body, fragment);
213 }
214 }
215 if (navigationMode === 'regionBased') {
216 populateInitialData(page.initialData);
217 const attrName = `data-${directivePrefix}-router-region`;
218 document.querySelectorAll(`[${attrName}]`).forEach(region => {
219 const id = region.getAttribute(attrName);
220 const fragment = getRegionRootFragment(region);
221 render(page.regions[id], fragment);
222 });
223 }
224 if (page.title) {
225 document.title = page.title;
226 }
227 });
228 };
229
230 /**
231 * Load the given page forcing a full page reload.
232 *
233 * The function returns a promise that won't resolve, useful to prevent any
234 * potential feedback indicating that the navigation has finished while the new
235 * page is being loaded.
236 *
237 * @param href The page href.
238 * @return Promise that never resolves.
239 */
240 const forcePageReload = href => {
241 window.location.assign(href);
242 return new Promise(() => {});
243 };
244
245 // Listen to the back and forward buttons and restore the page if it's in the
246 // cache.
247 window.addEventListener('popstate', async () => {
248 const pagePath = getPagePath(window.location.href); // Remove hash.
249 const page = pages.has(pagePath) && (await pages.get(pagePath));
250 if (page) {
251 renderRegions(page);
252 // Update the URL in the state.
253 state.url = window.location.href;
254 } else {
255 window.location.reload();
256 }
257 });
258
259 // Initialize the router and cache the initial page using the initial vDOM.
260 // Once this code is tested and more mature, the head should be updated for
261 // region based navigation as well.
262 if (true) {
263 if (navigationMode === 'fullPage') {
264 // Cache the scripts. Has to be called before fetching the assets.
265 [].map.call(document.querySelectorAll('script[src]'), script => {
266 headElements.set(script.getAttribute('src'), {
267 tag: script,
268 text: script.textContent
269 });
270 });
271 await (0,_head__WEBPACK_IMPORTED_MODULE_1__/* .fetchHeadAssets */ .J)(document, headElements);
272 }
273 }
274 pages.set(getPagePath(window.location.href), Promise.resolve(regionsToVdom(document, {
275 vdom: initialVdom
276 })));
277
278 // Check if the link is valid for client-side navigation.
279 const isValidLink = ref => ref && ref instanceof window.HTMLAnchorElement && ref.href && (!ref.target || ref.target === '_self') && ref.origin === window.location.origin && !ref.pathname.startsWith('/wp-admin') && !ref.pathname.startsWith('/wp-login.php') && !ref.getAttribute('href').startsWith('#') && !new URL(ref.href).searchParams.has('_wpnonce');
280
281 // Check if the event is valid for client-side navigation.
282 const isValidEvent = event => event && event.button === 0 &&
283 // Left clicks only.
284 !event.metaKey &&
285 // Open in new tab (Mac).
286 !event.ctrlKey &&
287 // Open in new tab (Windows).
288 !event.altKey &&
289 // Download.
290 !event.shiftKey && !event.defaultPrevented;
291
292 // Variable to store the current navigation.
293 let navigatingTo = '';
294 const {
295 state,
296 actions
297 } = (0,_wordpress_interactivity__WEBPACK_IMPORTED_MODULE_0__.store)('core/router', {
298 state: {
299 url: window.location.href,
300 navigation: {
301 hasStarted: false,
302 hasFinished: false,
303 texts: {
304 loading: '',
305 loaded: ''
306 },
307 message: ''
308 }
309 },
310 actions: {
311 /**
312 * Navigates to the specified page.
313 *
314 * This function normalizes the passed href, fetchs the page HTML if
315 * needed, and updates any interactive regions whose contents have
316 * changed. It also creates a new entry in the browser session history.
317 *
318 * @param href The page href.
319 * @param [options] Options object.
320 * @param [options.force] If true, it forces re-fetching the URL.
321 * @param [options.html] HTML string to be used instead of fetching the requested URL.
322 * @param [options.replace] If true, it replaces the current entry in the browser session history.
323 * @param [options.timeout] Time until the navigation is aborted, in milliseconds. Default is 10000.
324 * @param [options.loadingAnimation] Whether an animation should be shown while navigating. Default to `true`.
325 * @param [options.screenReaderAnnouncement] Whether a message for screen readers should be announced while navigating. Default to `true`.
326 *
327 * @return Promise that resolves once the navigation is completed or aborted.
328 */
329 *navigate(href, options = {}) {
330 const {
331 clientNavigationDisabled
332 } = (0,_wordpress_interactivity__WEBPACK_IMPORTED_MODULE_0__.getConfig)();
333 if (clientNavigationDisabled) {
334 yield forcePageReload(href);
335 }
336 const pagePath = getPagePath(href);
337 const {
338 navigation
339 } = state;
340 const {
341 loadingAnimation = true,
342 screenReaderAnnouncement = true,
343 timeout = 10000
344 } = options;
345 navigatingTo = href;
346 actions.prefetch(pagePath, options);
347
348 // Create a promise that resolves when the specified timeout ends.
349 // The timeout value is 10 seconds by default.
350 const timeoutPromise = new Promise(resolve => setTimeout(resolve, timeout));
351
352 // Don't update the navigation status immediately, wait 400 ms.
353 const loadingTimeout = setTimeout(() => {
354 if (navigatingTo !== href) {
355 return;
356 }
357 if (loadingAnimation) {
358 navigation.hasStarted = true;
359 navigation.hasFinished = false;
360 }
361 if (screenReaderAnnouncement) {
362 navigation.message = navigation.texts.loading;
363 }
364 }, 400);
365 const page = yield Promise.race([pages.get(pagePath), timeoutPromise]);
366
367 // Dismiss loading message if it hasn't been added yet.
368 clearTimeout(loadingTimeout);
369
370 // Once the page is fetched, the destination URL could have changed
371 // (e.g., by clicking another link in the meantime). If so, bail
372 // out, and let the newer execution to update the HTML.
373 if (navigatingTo !== href) {
374 return;
375 }
376 if (page && !page.initialData?.config?.['core/router']?.clientNavigationDisabled) {
377 yield renderRegions(page);
378 window.history[options.replace ? 'replaceState' : 'pushState']({}, '', href);
379
380 // Update the URL in the state.
381 state.url = href;
382
383 // Update the navigation status once the the new page rendering
384 // has been completed.
385 if (loadingAnimation) {
386 navigation.hasStarted = false;
387 navigation.hasFinished = true;
388 }
389 if (screenReaderAnnouncement) {
390 // Announce that the page has been loaded. If the message is the
391 // same, we use a no-break space similar to the @wordpress/a11y
392 // package: https://github.com/WordPress/gutenberg/blob/c395242b8e6ee20f8b06c199e4fc2920d7018af1/packages/a11y/src/filter-message.js#L20-L26
393 navigation.message = navigation.texts.loaded + (navigation.message === navigation.texts.loaded ? '\u00A0' : '');
394 }
395
396 // Scroll to the anchor if exits in the link.
397 const {
398 hash
399 } = new URL(href, window.location.href);
400 if (hash) {
401 document.querySelector(hash)?.scrollIntoView();
402 }
403 } else {
404 yield forcePageReload(href);
405 }
406 },
407 /**
408 * Prefetchs the page with the passed URL.
409 *
410 * The function normalizes the URL and stores internally the fetch
411 * promise, to avoid triggering a second fetch for an ongoing request.
412 *
413 * @param url The page URL.
414 * @param [options] Options object.
415 * @param [options.force] Force fetching the URL again.
416 * @param [options.html] HTML string to be used instead of fetching the requested URL.
417 */
418 prefetch(url, options = {}) {
419 const {
420 clientNavigationDisabled
421 } = (0,_wordpress_interactivity__WEBPACK_IMPORTED_MODULE_0__.getConfig)();
422 if (clientNavigationDisabled) {
423 return;
424 }
425 const pagePath = getPagePath(url);
426 if (options.force || !pages.has(pagePath)) {
427 pages.set(pagePath, fetchPage(pagePath, {
428 html: options.html
429 }));
430 }
431 }
432 }
433 });
434
435 // Add click and prefetch to all links.
436 if (true) {
437 if (navigationMode === 'fullPage') {
438 // Navigate on click.
439 document.addEventListener('click', function (event) {
440 const ref = event.target.closest('a');
441 if (isValidLink(ref) && isValidEvent(event)) {
442 event.preventDefault();
443 actions.navigate(ref.href);
444 }
445 }, true);
446 // Prefetch on hover.
447 document.addEventListener('mouseenter', function (event) {
448 if (event.target?.nodeName === 'A') {
449 const ref = event.target.closest('a');
450 if (isValidLink(ref) && isValidEvent(event)) {
451 actions.prefetch(ref.href);
452 }
453 }
454 }, true);
455 }
456 }
457 __webpack_async_result__();
458 } catch(e) { __webpack_async_result__(e); } }, 1);
459
460 /***/ }),
461
462 /***/ 998:
463 /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
464
465 var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }
466 var y = x => () => x
467 module.exports = x({ ["getConfig"]: () => __WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getConfig, ["privateApis"]: () => __WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.privateApis, ["store"]: () => __WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.store });
468
469 /***/ })
470
471 /******/ });
472 /************************************************************************/
473 /******/ // The module cache
474 /******/ var __webpack_module_cache__ = {};
475 /******/
476 /******/ // The require function
477 /******/ function __webpack_require__(moduleId) {
478 /******/ // Check if module is in cache
479 /******/ var cachedModule = __webpack_module_cache__[moduleId];
480 /******/ if (cachedModule !== undefined) {
481 /******/ return cachedModule.exports;
482 /******/ }
483 /******/ // Create a new module (and put it into the cache)
484 /******/ var module = __webpack_module_cache__[moduleId] = {
485 /******/ // no module.id needed
486 /******/ // no module.loaded needed
487 /******/ exports: {}
488 /******/ };
489 /******/
490 /******/ // Execute the module function
491 /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
492 /******/
493 /******/ // Return the exports of the module
494 /******/ return module.exports;
495 /******/ }
496 /******/
497 /************************************************************************/
498 /******/ /* webpack/runtime/async module */
499 /******/ (() => {
500 /******/ var webpackQueues = typeof Symbol === "function" ? Symbol("webpack queues") : "__webpack_queues__";
501 /******/ var webpackExports = typeof Symbol === "function" ? Symbol("webpack exports") : "__webpack_exports__";
502 /******/ var webpackError = typeof Symbol === "function" ? Symbol("webpack error") : "__webpack_error__";
503 /******/ var resolveQueue = (queue) => {
504 /******/ if(queue && queue.d < 1) {
505 /******/ queue.d = 1;
506 /******/ queue.forEach((fn) => (fn.r--));
507 /******/ queue.forEach((fn) => (fn.r-- ? fn.r++ : fn()));
508 /******/ }
509 /******/ }
510 /******/ var wrapDeps = (deps) => (deps.map((dep) => {
511 /******/ if(dep !== null && typeof dep === "object") {
512 /******/ if(dep[webpackQueues]) return dep;
513 /******/ if(dep.then) {
514 /******/ var queue = [];
515 /******/ queue.d = 0;
516 /******/ dep.then((r) => {
517 /******/ obj[webpackExports] = r;
518 /******/ resolveQueue(queue);
519 /******/ }, (e) => {
520 /******/ obj[webpackError] = e;
521 /******/ resolveQueue(queue);
522 /******/ });
523 /******/ var obj = {};
524 /******/ obj[webpackQueues] = (fn) => (fn(queue));
525 /******/ return obj;
526 /******/ }
527 /******/ }
528 /******/ var ret = {};
529 /******/ ret[webpackQueues] = x => {};
530 /******/ ret[webpackExports] = dep;
531 /******/ return ret;
532 /******/ }));
533 /******/ __webpack_require__.a = (module, body, hasAwait) => {
534 /******/ var queue;
535 /******/ hasAwait && ((queue = []).d = -1);
536 /******/ var depQueues = new Set();
537 /******/ var exports = module.exports;
538 /******/ var currentDeps;
539 /******/ var outerResolve;
540 /******/ var reject;
541 /******/ var promise = new Promise((resolve, rej) => {
542 /******/ reject = rej;
543 /******/ outerResolve = resolve;
544 /******/ });
545 /******/ promise[webpackExports] = exports;
546 /******/ promise[webpackQueues] = (fn) => (queue && fn(queue), depQueues.forEach(fn), promise["catch"](x => {}));
547 /******/ module.exports = promise;
548 /******/ body((deps) => {
549 /******/ currentDeps = wrapDeps(deps);
550 /******/ var fn;
551 /******/ var getResult = () => (currentDeps.map((d) => {
552 /******/ if(d[webpackError]) throw d[webpackError];
553 /******/ return d[webpackExports];
554 /******/ }))
555 /******/ var promise = new Promise((resolve) => {
556 /******/ fn = () => (resolve(getResult));
557 /******/ fn.r = 0;
558 /******/ var fnQueue = (q) => (q !== queue && !depQueues.has(q) && (depQueues.add(q), q && !q.d && (fn.r++, q.push(fn))));
559 /******/ currentDeps.map((dep) => (dep[webpackQueues](fnQueue)));
560 /******/ });
561 /******/ return fn.r ? promise : getResult();
562 /******/ }, (err) => ((err ? reject(promise[webpackError] = err) : outerResolve(exports)), resolveQueue(queue)));
563 /******/ queue && queue.d < 0 && (queue.d = 0);
564 /******/ };
565 /******/ })();
566 /******/
567 /******/ /* webpack/runtime/define property getters */
568 /******/ (() => {
569 /******/ // define getter functions for harmony exports
570 /******/ __webpack_require__.d = (exports, definition) => {
571 /******/ for(var key in definition) {
572 /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
573 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
574 /******/ }
575 /******/ }
576 /******/ };
577 /******/ })();
578 /******/
579 /******/ /* webpack/runtime/hasOwnProperty shorthand */
580 /******/ (() => {
581 /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
582 /******/ })();
583 /******/
584 /************************************************************************/
585 /******/
586 /******/ // startup
587 /******/ // Load entry module and return exports
588 /******/ // This entry module used 'module' so it can't be inlined
589 /******/ var __webpack_exports__ = __webpack_require__(638);
590 /******/ __webpack_exports__ = await __webpack_exports__;
591 /******/ var __webpack_exports__actions = __webpack_exports__.N;
592 /******/ var __webpack_exports__state = __webpack_exports__.S;
593 /******/ export { __webpack_exports__actions as actions, __webpack_exports__state as state };
594 /******/
595