Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 51 additions & 33 deletions src/core/basepattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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() {
Expand All @@ -110,7 +128,7 @@ class BasePattern {
dom: this.el,
action: action,
...options,
}
};
this.el.dispatchEvent(events.update_event(options));
}

Expand Down
74 changes: 48 additions & 26 deletions src/core/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}
},

Expand Down Expand Up @@ -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.
Expand Down
31 changes: 25 additions & 6 deletions src/pat/inject/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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(/\<title\>(.*)\<\/title\>/);
let clean_html = html
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
.replace(/<head\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/head>/gi, "")
.replace(/<html([^>]*?)>/gi, "")
.replace(/<\/html([^>]*?)>/gi, "")
.replace(/<body([^>]*?)>/gi, '<div id="__original_body">')
.replace(/<\/body([^>]*?)>/gi, "</div>");

for (const tag of cfg.removeTags || []) {
const re = RegExp(String.raw`<${tag}\b[^<]*(?:(?!<\/${tag}>)<[^<]*)*<\/${tag}>`, "gi")
clean_html = clean_html.replace(re, "");
}

if (title && title.length == 2) {
clean_html = title[0] + clean_html;
}
Expand Down Expand Up @@ -1122,7 +1141,7 @@ const inject = {
sources(cfgs, data) {
const sources = cfgs.map((cfg) => cfg.source);
sources.push("title");
const result = this._sourcesFromHtml(data, cfgs[0].url, sources);
const result = this._sourcesFromHtml(data, cfgs[0].url, sources, cfgs[0]);
return result;
},
},
Expand Down
Loading