From bb3e58abd041f8b36cc182394d2f6add25a72064 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Tue, 16 Apr 2024 14:13:53 +0100 Subject: [PATCH] Split out a reusable runInlineScripts utility function and handle attributes correctly --- client/src/controllers/TeleportController.ts | 12 ++--------- .../src/entrypoints/admin/telepath/widgets.js | 7 ++----- client/src/utils/runInlineScripts.ts | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 client/src/utils/runInlineScripts.ts diff --git a/client/src/controllers/TeleportController.ts b/client/src/controllers/TeleportController.ts index 3c1221f239..a9b478dfd4 100644 --- a/client/src/controllers/TeleportController.ts +++ b/client/src/controllers/TeleportController.ts @@ -1,4 +1,5 @@ import { Controller } from '@hotwired/stimulus'; +import { runInlineScripts } from '../utils/runInlineScripts'; /** * Allows the controlled element's content to be copied and appended @@ -101,16 +102,7 @@ export class TeleportController extends Controller { // and copy the attributes and innerHTML over. This is necessary when we're // teleporting a template that contains legacy init code, e.g. initDateChooser. // Only do this for inline scripts, as that's what we're expecting. - templateFragment - .querySelectorAll('script:not([src], [type])') - .forEach((script) => { - const newScript = document.createElement('script'); - Array.from(script.attributes).forEach((key) => - newScript.setAttribute(key.nodeName, key.nodeValue || ''), - ); - newScript.innerHTML = script.innerHTML; - script.replaceWith(newScript); - }); + runInlineScripts(templateFragment); return templateFragment; } diff --git a/client/src/entrypoints/admin/telepath/widgets.js b/client/src/entrypoints/admin/telepath/widgets.js index 0604dbab05..12ebeac416 100644 --- a/client/src/entrypoints/admin/telepath/widgets.js +++ b/client/src/entrypoints/admin/telepath/widgets.js @@ -1,4 +1,5 @@ import { gettext } from '../../../utils/gettext'; +import { runInlineScripts } from '../../../utils/runInlineScripts'; class BoundWidget { constructor( @@ -79,11 +80,7 @@ class Widget { placeholder.replaceWith(dom); /* execute any scripts in the new element */ - dom.querySelectorAll('script').forEach((script) => { - const newScript = document.createElement('script'); - newScript.text = script.text; - script.replaceWith(newScript); - }); + runInlineScripts(dom); // Add any extra attributes we received to the HTML of the widget if (typeof options?.attributes === 'object') { diff --git a/client/src/utils/runInlineScripts.ts b/client/src/utils/runInlineScripts.ts new file mode 100644 index 0000000000..902bf44afe --- /dev/null +++ b/client/src/utils/runInlineScripts.ts @@ -0,0 +1,20 @@ +/** + * Runs any inline scripts contained within the given DOM element or fragment. + */ +const runInlineScripts = (element: HTMLElement | DocumentFragment) => { + const scripts = element.querySelectorAll( + 'script:not([src])', + ) as NodeListOf; + scripts.forEach((script) => { + if (!script.type || script.type === 'application/javascript') { + const newScript = document.createElement('script'); + Array.from(script.attributes).forEach((key) => + newScript.setAttribute(key.nodeName, key.nodeValue || ''), + ); + newScript.text = script.text; + script.replaceWith(newScript); + } + }); +}; + +export { runInlineScripts };