diff --git a/src/core/basepattern.js b/src/core/basepattern.js index 733e556d9..d935c1402 100644 --- a/src/core/basepattern.js +++ b/src/core/basepattern.js @@ -21,6 +21,7 @@ class BasePattern { parser_group_options = true; parser_multiple = undefined; parser_inherit = true; + init_lazy = false; constructor(el, options = {}) { // Make static variables available on instance. @@ -59,45 +60,62 @@ class BasePattern { // Both limitations are gone in next tick. // window.setTimeout(async () => { - if (typeof this.el[`pattern-${this.name}`] !== "undefined") { - // Do not reinstantiate - log.debug(`Not reinstatiating the pattern ${this.name}.`, this.el); - - // Notify that not instantiated - this.el.dispatchEvent( - new Event(`not-init.${this.name}.patterns`, { - bubbles: true, - cancelable: false, - }) - ); - return; + if (this.init_lazy === true) { + const observer = new IntersectionObserver(async (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + await this.pattern_init(options); + observer.unobserve(entry.target); + } + } + }); + + observer.observe(this.el); + } else { + await this.pattern_init(options); } + }, 0); + } - // Create the options object by parsing the element and using the - // optional options as default. - this.options = - this.parser?.parse( - this.el, - options, - this.parser_multiple, - this.parser_inherit, - this.parser_group_options - ) ?? options; - - // Store pattern instance on element - this.el[`pattern-${this.name}`] = this; - - // Initialize the pattern - await this.init(); + async pattern_init(options) { + if (typeof this.el[`pattern-${this.name}`] !== "undefined") { + // Do not reinstantiate + log.debug(`Not reinstatiating the pattern ${this.name}.`, this.el); - // Notify that now ready + // Notify that not instantiated this.el.dispatchEvent( - new Event(`init.${this.name}.patterns`, { + new Event(`not-init.${this.name}.patterns`, { bubbles: true, - cancelable: true, + cancelable: false, }) ); - }, 0); + return; + } + + // Create the options object by parsing the element and using the + // optional options as default. + this.options = + this.parser?.parse( + this.el, + options, + this.parser_multiple, + this.parser_inherit, + this.parser_group_options + ) ?? options; + + // Store pattern instance on element + this.el[`pattern-${this.name}`] = this; + + // Initialize the pattern + await this.init(); + + // Notify that now ready + this.el.dispatchEvent( + new Event(`init.${this.name}.patterns`, { + bubbles: true, + cancelable: true, + }) + ); } async init() { @@ -110,7 +128,7 @@ class BasePattern { dom: this.el, action: action, ...options, - } + }; this.el.dispatchEvent(events.update_event(options)); } diff --git a/src/core/registry.js b/src/core/registry.js index aef82d554..95aa1d48a 100644 --- a/src/core/registry.js +++ b/src/core/registry.js @@ -60,7 +60,13 @@ const registry = { // registration just registers a pattern. Once init is called, // the DOM is scanned. After that registering a new pattern // results in rescanning the DOM only for this pattern. - init() { + init(patterns) { + // Extend this registries patterns object. + // This is a way to inject lazy loading patterns. + if (typeof patterns === "object") { + this.patterns = { ...this.patterns, ...patterns }; + } + dom.document_ready(() => { if (window.__patternslib_registry_initialized) { // Do not reinitialize a already initialized registry. @@ -108,27 +114,46 @@ const registry = { /* Initialize the pattern with the provided name and in the context * of the passed in DOM element. */ - const $el = $(el); const pattern = registry.patterns[name]; - const plog = logging.getLogger(`pat.${name}`); if (el.matches(pattern.trigger)) { - plog.debug("Initialising.", el); - try { - if (pattern.init) { - // old style initialisation - pattern.init($el, null, trigger); - } else { - // class based pattern initialisation - new pattern($el, null, trigger); - } + if (pattern.importer) { + const observer = new IntersectionObserver(async (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + const _pattern = await pattern.importer(); + this.patterns[name] = _pattern; + this.initPattern__initializer(_pattern, name, el, trigger); + observer.unobserve(el); + } + } + }); - plog.debug("done."); - } catch (e) { - if (dont_catch) { - throw e; - } - plog.error("Caught error:", e); + observer.observe(el); + } else { + this.initPattern__initializer(pattern, name, el, trigger); + } + } + }, + + initPattern__initializer(pattern, name, el, trigger) { + const $el = $(el); + const logger = logging.getLogger(`pat.${name}`); + logger.debug("Initialising.", el); + try { + if (pattern.init) { + // old style initialisation + pattern.init($el, null, trigger); + } else { + // class based pattern initialisation + new pattern($el, null, trigger); + } + + logger.debug("done."); + } catch (e) { + if (dont_catch) { + throw e; } + logger.error("Caught error:", e); } }, @@ -177,20 +202,17 @@ const registry = { // Clean up selectors: // - Remove whitespace, // - Remove trailing commas, - // - Join to selecto string. - const selector_string = selectors.map( - (selector) => selector.trim().replace(/,$/, "") - ).join(","); + // - Join to selector string. + const selector_string = selectors + .map((selector) => selector.trim().replace(/,$/, "")) + .join(","); // Exit, if no selector. if (!selector_string) { return; } - let matches = dom.querySelectorAllAndMe( - content, - selector_string - ); + let matches = dom.querySelectorAllAndMe(content, selector_string); matches = matches.filter((el) => { // Filter out patterns: // - with class ``.disable-patterns`` or wrapped within. diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index 0e75b6873..cfc96cb4c 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -38,6 +38,7 @@ parser.addArgument("class"); // Add a class to the injected content. parser.addArgument("history", "none", ["none", "record"]); parser.addArgument("push-marker"); parser.addArgument("scroll"); +parser.addArgument("remove-tags", "script", [], true); // Note: this should not be here but the parser would bail on unknown // parameters and expand/collapsible need to pass the url to us. @@ -826,14 +827,28 @@ const inject = { elementbefore: "before", }[cfg.action]; - // Inject the content HERE! target[method](...source_nodes); + if (! cfg.removeTags?.includes("script")) { + // Find and execute scripts + for (const node of source_nodes) { + const scripts = node.querySelectorAll?.("script") || []; + for (const script of scripts) { + const new_script = document.createElement("script"); + for (const attr of [...script.attributes]) { + new_script.setAttribute(attr.name, attr.value) + } + new_script.textContent = script.textContent; + script.replaceWith(new_script); + } + } + } + return true; }, - _sourcesFromHtml(html, url, sources) { - const $html = this._parseRawHtml(html, url); + _sourcesFromHtml(html, url, sources, cfg) { + const $html = this._parseRawHtml(html, url, cfg); return sources.map((source) => { if (source === "body") { source = "#__original_body"; @@ -971,17 +986,21 @@ const inject = { return page.innerHTML.trim(); }, - _parseRawHtml(html, url = "") { + _parseRawHtml(html, url = "", cfg = {}) { // remove script tags and head and replace body by a div const title = html.match(/\