summaryrefslogtreecommitdiffstatshomepage
path: root/core/misc
diff options
context:
space:
mode:
Diffstat (limited to 'core/misc')
-rw-r--r--core/misc/cspell/dictionary.txt2
-rw-r--r--core/misc/dialog/off-canvas/css/button.pcss.css2
-rw-r--r--core/misc/dialog/off-canvas/css/tabledrag.pcss.css6
-rw-r--r--core/misc/dialog/off-canvas/css/wrapper.css2
-rw-r--r--core/misc/dialog/off-canvas/css/wrapper.pcss.css2
-rw-r--r--core/misc/drupal.js5
-rw-r--r--core/misc/htmx/htmx-assets.js174
-rw-r--r--core/misc/htmx/htmx-behaviors.js41
-rw-r--r--core/misc/tabledrag.js6
-rw-r--r--core/misc/timezone.js1
10 files changed, 226 insertions, 15 deletions
diff --git a/core/misc/cspell/dictionary.txt b/core/misc/cspell/dictionary.txt
index 24999a781acb..69b32dfa7187 100644
--- a/core/misc/cspell/dictionary.txt
+++ b/core/misc/cspell/dictionary.txt
@@ -427,6 +427,8 @@ rowspans
rtsp
ruleset
sameorigin
+sandboxed
+sandboxing
savepoints
sayre
schemaapi
diff --git a/core/misc/dialog/off-canvas/css/button.pcss.css b/core/misc/dialog/off-canvas/css/button.pcss.css
index 50288fc1e01e..42a9eba3d168 100644
--- a/core/misc/dialog/off-canvas/css/button.pcss.css
+++ b/core/misc/dialog/off-canvas/css/button.pcss.css
@@ -104,7 +104,7 @@
}
}
- @nest .no-touchevents & .button--small {
+ .no-touchevents & .button--small {
padding: 2px 1em;
font-size: 13px;
}
diff --git a/core/misc/dialog/off-canvas/css/tabledrag.pcss.css b/core/misc/dialog/off-canvas/css/tabledrag.pcss.css
index 9bce191ba33f..3fadc12cddfb 100644
--- a/core/misc/dialog/off-canvas/css/tabledrag.pcss.css
+++ b/core/misc/dialog/off-canvas/css/tabledrag.pcss.css
@@ -86,16 +86,16 @@
width: 5px;
}
- @nest .touchevents & .draggable td {
+ .touchevents & .draggable td {
padding: 0 10px;
}
- @nest .touchevents & .draggable .menu-item__link {
+ .touchevents & .draggable .menu-item__link {
display: inline-block;
padding: 10px 0;
}
- @nest .touchevents & a.tabledrag-handle {
+ .touchevents & a.tabledrag-handle {
width: 40px;
height: 44px;
}
diff --git a/core/misc/dialog/off-canvas/css/wrapper.css b/core/misc/dialog/off-canvas/css/wrapper.css
index 706d5a0e9d48..c5dbc71f1300 100644
--- a/core/misc/dialog/off-canvas/css/wrapper.css
+++ b/core/misc/dialog/off-canvas/css/wrapper.css
@@ -11,7 +11,7 @@
* @internal
*/
#drupal-off-canvas-wrapper {
- --off-canvas-wrapper-box-shadow: 0 0 0.25rem 2px rgba(0, 0, 0, 0.3);
+ --off-canvas-wrapper-box-shadow: 0 0 0.25rem 2px rgb(0, 0, 0, 0.3);
--off-canvas-wrapper-border-color: #2d2d2d;
--off-canvas-wrapper-border-width: 1px;
diff --git a/core/misc/dialog/off-canvas/css/wrapper.pcss.css b/core/misc/dialog/off-canvas/css/wrapper.pcss.css
index c16a7e652df1..c16b30a7d0bb 100644
--- a/core/misc/dialog/off-canvas/css/wrapper.pcss.css
+++ b/core/misc/dialog/off-canvas/css/wrapper.pcss.css
@@ -5,7 +5,7 @@
* @internal
*/
#drupal-off-canvas-wrapper {
- --off-canvas-wrapper-box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.3);
+ --off-canvas-wrapper-box-shadow: 0 0 4px 2px rgb(0, 0, 0, 0.3);
--off-canvas-wrapper-border-color: #2d2d2d;
--off-canvas-wrapper-border-width: 1px;
diff --git a/core/misc/drupal.js b/core/misc/drupal.js
index 416c4f415a59..641c461a802e 100644
--- a/core/misc/drupal.js
+++ b/core/misc/drupal.js
@@ -404,7 +404,6 @@ window.Drupal = { behaviors: {}, locale: {} };
*
* @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js
* @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript
- * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53
*/
Drupal.url.toAbsolute = function (url) {
const urlParsingNode = document.createElement('a');
@@ -419,9 +418,7 @@ window.Drupal = { behaviors: {}, locale: {} };
urlParsingNode.setAttribute('href', url);
- // IE <= 7 normalizes the URL when assigned to the anchor node similar to
- // the other browsers.
- return urlParsingNode.cloneNode(false).href;
+ return urlParsingNode.href;
};
/**
diff --git a/core/misc/htmx/htmx-assets.js b/core/misc/htmx/htmx-assets.js
new file mode 100644
index 000000000000..bc5f00df4327
--- /dev/null
+++ b/core/misc/htmx/htmx-assets.js
@@ -0,0 +1,174 @@
+/**
+ * @file
+ * Adds assets the current page requires.
+ *
+ * This script fires a custom `htmx:drupal:load` event when the request has
+ * settled and all script and css files have been successfully loaded on the
+ * page.
+ */
+
+(function (Drupal, drupalSettings, loadjs, htmx) {
+ // Disable htmx loading of script tags since we're handling it.
+ htmx.config.allowScriptTags = false;
+
+ /**
+ * Used to hold the loadjs promise.
+ *
+ * It's declared in htmx:beforeSwap and checked in htmx:afterSettle to trigger
+ * the custom htmx:drupal:load event.
+ *
+ * @type {WeakMap<XMLHttpRequest, Promise>}
+ */
+ const requestAssetsLoaded = new WeakMap();
+
+ /**
+ * Helper function to merge two objects recursively.
+ *
+ * @param current
+ * The object to receive the merged values.
+ * @param sources
+ * The objects to merge into current.
+ *
+ * @return object
+ * The merged object.
+ *
+ * @see https://youmightnotneedjquery.com/#deep_extend
+ */
+ function mergeSettings(current, ...sources) {
+ if (!current) {
+ return {};
+ }
+
+ sources
+ .filter((obj) => Boolean(obj))
+ .forEach((obj) => {
+ Object.entries(obj).forEach(([key, value]) => {
+ switch (Object.prototype.toString.call(value)) {
+ case '[object Object]':
+ current[key] = current[key] || {};
+ current[key] = mergeSettings(current[key], value);
+ break;
+
+ case '[object Array]':
+ current[key] = mergeSettings(new Array(value.length), value);
+ break;
+
+ default:
+ current[key] = value;
+ }
+ });
+ });
+
+ return current;
+ }
+
+ /**
+ * Send the current ajax page state with each request.
+ *
+ * @param configRequestEvent
+ * HTMX event for request configuration.
+ *
+ * @see system_js_settings_alter()
+ * @see \Drupal\Core\Render\HtmlResponseAttachmentsProcessor::processAttachments
+ * @see https://htmx.org/api/#on
+ * @see https://htmx.org/events/#htmx:configRequest
+ */
+ htmx.on('htmx:configRequest', ({ detail }) => {
+ const url = new URL(detail.path, document.location.href);
+ if (Drupal.url.isLocal(url.toString())) {
+ // Allow Drupal to return new JavaScript and CSS files to load without
+ // returning the ones already loaded.
+ // @see \Drupal\Core\StackMiddleWare\AjaxPageState
+ // @see \Drupal\Core\Theme\AjaxBasePageNegotiator
+ // @see \Drupal\Core\Asset\LibraryDependencyResolverInterface::getMinimalRepresentativeSubset()
+ // @see system_js_settings_alter()
+ const pageState = drupalSettings.ajaxPageState;
+ detail.parameters['ajax_page_state[theme]'] = pageState.theme;
+ detail.parameters['ajax_page_state[theme_token]'] = pageState.theme_token;
+ detail.parameters['ajax_page_state[libraries]'] = pageState.libraries;
+ }
+ });
+
+ // @see https://htmx.org/events/#htmx:beforeSwap
+ htmx.on('htmx:beforeSwap', ({ detail }) => {
+ // Custom event to detach behaviors.
+ htmx.trigger(detail.elt, 'htmx:drupal:unload');
+
+ // We need to parse the response to find all the assets to load.
+ // htmx cleans up too many things to be able to rely on their dom fragment.
+ let responseHTML = Document.parseHTMLUnsafe(detail.serverResponse);
+
+ // Update drupalSettings
+ // Use direct child elements to harden against XSS exploits when CSP is on.
+ const settingsElement = responseHTML.querySelector(
+ ':is(head, body) > script[type="application/json"][data-drupal-selector="drupal-settings-json"]',
+ );
+ if (settingsElement !== null) {
+ mergeSettings(drupalSettings, JSON.parse(settingsElement.textContent));
+ }
+
+ // Load all assets files. We sent ajax_page_state in the request so this is only the diff with the current page.
+ const assetsTags = responseHTML.querySelectorAll(
+ 'link[rel="stylesheet"][href], script[src]',
+ );
+ const bundleIds = Array.from(assetsTags)
+ .filter(({ href, src }) => !loadjs.isDefined(href ?? src))
+ .map(({ href, src, type, attributes }) => {
+ const bundleId = href ?? src;
+ let prefix = 'css!';
+ if (src) {
+ prefix = type === 'module' ? 'module!' : 'js!';
+ }
+
+ loadjs(prefix + bundleId, bundleId, {
+ // JS files are loaded in order, so this needs to be false when 'src'
+ // is defined.
+ async: !src,
+ // Copy asset tag attributes to the new element.
+ before(path, element) {
+ // This allows all attributes to be added, like defer, async and
+ // crossorigin.
+ Object.values(attributes).forEach((attr) => {
+ element.setAttribute(attr.name, attr.value);
+ });
+ },
+ });
+
+ return bundleId;
+ });
+
+ // Helps with memory management.
+ responseHTML = null;
+
+ // Nothing to load, we resolve the promise right away.
+ let assetsLoaded = Promise.resolve();
+ // If there are assets to load, use loadjs to manage this process.
+ if (bundleIds.length) {
+ // Trigger the event once all the dependencies have loaded.
+ assetsLoaded = new Promise((resolve, reject) => {
+ loadjs.ready(bundleIds, {
+ success: resolve,
+ error(depsNotFound) {
+ const message = Drupal.t(
+ `The following files could not be loaded: @dependencies`,
+ { '@dependencies': depsNotFound.join(', ') },
+ );
+ reject(message);
+ },
+ });
+ });
+ }
+
+ requestAssetsLoaded.set(detail.xhr, assetsLoaded);
+ });
+
+ // Trigger the Drupal processing once all assets have been loaded.
+ // @see https://htmx.org/events/#htmx:afterSettle
+ htmx.on('htmx:afterSettle', ({ detail }) => {
+ requestAssetsLoaded.get(detail.xhr).then(() => {
+ htmx.trigger(detail.elt, 'htmx:drupal:load');
+ // This should be automatic but don't wait for the garbage collector.
+ requestAssetsLoaded.delete(detail.xhr);
+ });
+ });
+})(Drupal, drupalSettings, loadjs, htmx);
diff --git a/core/misc/htmx/htmx-behaviors.js b/core/misc/htmx/htmx-behaviors.js
new file mode 100644
index 000000000000..da3ea2eb9952
--- /dev/null
+++ b/core/misc/htmx/htmx-behaviors.js
@@ -0,0 +1,41 @@
+/**
+ * @file
+ * Connect Drupal.behaviors to htmx inserted content.
+ */
+(function (Drupal, htmx, drupalSettings) {
+ // Flag used to prevent running htmx initialization twice on elements we know
+ // have already been processed.
+ let attachFromHtmx = false;
+
+ // This is a custom event that triggers once the htmx request settled and
+ // all JS and CSS assets have been loaded successfully.
+ // @see https://htmx.org/api/#on
+ // @see htmx-assets.js
+ htmx.on('htmx:drupal:load', ({ detail }) => {
+ attachFromHtmx = true;
+ Drupal.attachBehaviors(detail.elt, drupalSettings);
+ attachFromHtmx = false;
+ });
+
+ // When htmx removes elements from the DOM, make sure they're detached first.
+ // This event is currently an alias of htmx:beforeSwap
+ htmx.on('htmx:drupal:unload', ({ detail }) => {
+ Drupal.detachBehaviors(detail.elt, drupalSettings, 'unload');
+ });
+
+ /**
+ * Initialize HTMX library on content added by Drupal Ajax Framework.
+ *
+ * @type {Drupal~behavior}
+ *
+ * @prop {Drupal~behaviorAttach} attach
+ * Initialize htmx behavior.
+ */
+ Drupal.behaviors.htmx = {
+ attach(context) {
+ if (!attachFromHtmx && context !== document) {
+ htmx.process(context);
+ }
+ },
+ };
+})(Drupal, htmx, drupalSettings);
diff --git a/core/misc/tabledrag.js b/core/misc/tabledrag.js
index b9f32bd54138..5786ebd9431a 100644
--- a/core/misc/tabledrag.js
+++ b/core/misc/tabledrag.js
@@ -1064,8 +1064,7 @@
const nextRow = $nextRow.get(0);
sourceRow = changedRow;
if (
- previousRow &&
- previousRow.matches('.draggable') &&
+ previousRow?.matches('.draggable') &&
$previousRow.find(`.${group}`).length
) {
if (this.indentEnabled) {
@@ -1079,8 +1078,7 @@
sourceRow = previousRow;
}
} else if (
- nextRow &&
- nextRow.matches('.draggable') &&
+ nextRow?.matches('.draggable') &&
$nextRow.find(`.${group}`).length
) {
if (this.indentEnabled) {
diff --git a/core/misc/timezone.js b/core/misc/timezone.js
index a8c1a5f7ac58..97f5e5d05a8c 100644
--- a/core/misc/timezone.js
+++ b/core/misc/timezone.js
@@ -68,7 +68,6 @@
$.ajax({
async: false,
url: Drupal.url(path),
- data: { date: dateString },
dataType: 'json',
success(data) {
if (data) {