Skip to content

Commit dae949c

Browse files
committed
feat(pat-inject): More metadata updates for history:record injections.
When a URL manipulating injection with the history:record setting is done, we also want a bunch of other data be updated. That includes: - title - canonical link - base URL We already had support for the title tag in the head section of the document. This PR adds support for ``<link rel="canonical" href="..." />`` and `<base href="...">` tags.
1 parent 5ef9434 commit dae949c

File tree

2 files changed

+620
-68
lines changed

2 files changed

+620
-68
lines changed

src/pat/inject/inject.js

Lines changed: 100 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,16 @@ const inject = {
463463
return $target;
464464
},
465465

466-
_performInjection(target, $el, $sources, cfg, trigger, $title) {
466+
_performInjection(
467+
target,
468+
$el,
469+
$sources,
470+
cfg,
471+
trigger,
472+
$title_tag,
473+
$canonical_tag,
474+
$base_tag
475+
) {
467476
/* Called after the XHR has succeeded and we have a new $sources
468477
* element to inject.
469478
*/
@@ -472,7 +481,9 @@ const inject = {
472481
const method = cfg.sourceMod === "content" ? "innerHTML" : "outerHTML";
473482
// There might be multiple sources, so we need to loop over them.
474483
// Access them with "innerHTML" or "outerHTML" depending on the sourceMod.
475-
const sources_string = [...$sources].map(source => source[method]).join("\n");
484+
const sources_string = [...$sources]
485+
.map((source) => source[method])
486+
.join("\n");
476487
wrapper.innerHTML = sources_string;
477488

478489
for (const img of wrapper.content.querySelectorAll("img")) {
@@ -494,13 +505,13 @@ const inject = {
494505
// Now the injection actually happens.
495506
if (this._inject(trigger, source_nodes, target, cfg)) {
496507
// Update history
497-
this._update_history(cfg, trigger, $title);
508+
this._update_history(cfg, trigger, $title_tag, $canonical_tag, $base_tag);
498509
// Post-injection
499510
this._afterInjection($el, cfg.$created_target || $(source_nodes), cfg);
500511
}
501512
},
502513

503-
_update_history(cfg, trigger, $title) {
514+
_update_history(cfg, trigger, $title_tag, $canonical_tag, $base_tag) {
504515
// History support. if subform is submitted, append form params
505516
if (cfg.history !== "record" || !history?.pushState) {
506517
return;
@@ -510,16 +521,45 @@ const inject = {
510521
const glue = url.indexOf("?") > -1 ? "&" : "?";
511522
url = `${url}${glue}${cfg.params}`;
512523
}
513-
history.pushState({ url: url }, "", url);
524+
525+
// We have a URL changing injection. We also need to update other data:
526+
// - title
527+
// - canonical link
528+
// - base url
529+
514530
// Also inject title element if we have one
515-
if ($title?.length) {
531+
if ($title_tag?.length) {
516532
const title_el = document.querySelector("title");
517533
if (title_el) {
518-
this._inject(trigger, $title, title_el, {
534+
this._inject(trigger, $title_tag, title_el, {
535+
action: "element",
536+
});
537+
}
538+
}
539+
540+
// Also inject canonical element if we have one
541+
if ($canonical_tag?.length) {
542+
const canonical_el = document.querySelector("link[rel=canonical]");
543+
if (canonical_el) {
544+
this._inject(trigger, $canonical_tag, canonical_el, {
519545
action: "element",
520546
});
521547
}
522548
}
549+
550+
// Also inject base element if we have one
551+
if ($base_tag?.length) {
552+
const base_el = document.querySelector("base");
553+
if (base_el) {
554+
this._inject(trigger, $base_tag, base_el, {
555+
action: "element",
556+
});
557+
}
558+
}
559+
560+
// At last position - other patterns can react on already changed title,
561+
// canonical or base.
562+
history.pushState({ url: url }, "", url);
523563
},
524564

525565
_afterInjection($el, $injected, cfg) {
@@ -592,16 +632,21 @@ const inject = {
592632
data,
593633
ev,
594634
]);
595-
/* pick the title source for dedicated handling later
596-
Title - if present - is always appended at the end. */
597-
let $title;
598-
if (
599-
sources$ &&
600-
sources$[sources$.length - 1] &&
601-
sources$[sources$.length - 1][0] &&
602-
sources$[sources$.length - 1][0].nodeName === "TITLE"
603-
) {
604-
$title = sources$[sources$.length - 1];
635+
636+
const extra_sources = sources$.filter((source) => source instanceof Array);
637+
let $title_tag;
638+
let $canonical_tag;
639+
let $base_tag;
640+
for (const extra_source of extra_sources) {
641+
if (extra_source[0] === "head_title") {
642+
$title_tag = extra_source[1];
643+
}
644+
if (extra_source[0] === "head_canonical") {
645+
$canonical_tag = extra_source[1];
646+
}
647+
if (extra_source[0] === "head_base") {
648+
$base_tag = extra_source[1];
649+
}
605650
}
606651

607652
for (const [idx1, cfg] of cfgs.entries()) {
@@ -614,7 +659,9 @@ const inject = {
614659
sources$[idx1],
615660
cfg,
616661
ev.target,
617-
$title
662+
$title_tag,
663+
$canonical_tag,
664+
$base_tag
618665
);
619666
}
620667
}
@@ -834,19 +881,33 @@ const inject = {
834881

835882
_sourcesFromHtml(html, url, sources) {
836883
const $html = this._parseRawHtml(html, url);
837-
return sources.map((source) => {
884+
const $sources = sources.map((source) => {
885+
// Special cases for automatic title and canonical manipulation for
886+
// history:records injections.
887+
888+
if (source === "head_title") {
889+
return ["head_title", $html.find("title")];
890+
}
891+
if (source === "head_canonical") {
892+
return ["head_canonical", $html.find("link[rel=canonical]")];
893+
}
894+
if (source === "head_base") {
895+
return ["head_base", $html.find("base")];
896+
}
897+
898+
// Special case for body
838899
if (source === "body") {
839900
source = "#__original_body";
840901
}
902+
903+
// Special case for "none";
841904
if (source === "none") {
842905
return $("<!-- -->");
843906
}
844907
const $source = $html.find(source);
845908

846909
if ($source.length === 0) {
847-
if (source != "title") {
848-
log.warn("No source elements for selector:", source, $html);
849-
}
910+
log.warn("No source elements for selector:", source, $html);
850911
}
851912

852913
$source.find('a[href^="#"]').each((idx, el_) => {
@@ -868,6 +929,7 @@ const inject = {
868929
});
869930
return $source;
870931
});
932+
return $sources;
871933
},
872934

873935
_rebaseAttrs: {
@@ -973,7 +1035,11 @@ const inject = {
9731035

9741036
_parseRawHtml(html, url = "") {
9751037
// remove script tags and head and replace body by a div
976-
const title = html.match(/\<title\>(.*)\<\/title\>/);
1038+
const title_tag = html.match(/\<title\>(.*)\<\/title\>/);
1039+
const canonical_tag = html.match(
1040+
/<link\b[^>]*\brel\s*=\s*["']canonical["'][^>]*>/i
1041+
);
1042+
const base_tag = html.match(/<base\b[^>]*>/i);
9771043
let clean_html = html
9781044
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
9791045
.replace(/<head\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/head>/gi, "")
@@ -982,8 +1048,14 @@ const inject = {
9821048
.replace(/<body([^>]*?)>/gi, '<div id="__original_body">')
9831049
.replace(/<\/body([^>]*?)>/gi, "</div>");
9841050

985-
if (title && title.length == 2) {
986-
clean_html = title[0] + clean_html;
1051+
if (title_tag && title_tag.length == 2) {
1052+
clean_html = title_tag[0] + clean_html;
1053+
}
1054+
if (canonical_tag) {
1055+
clean_html = canonical_tag[0] + clean_html;
1056+
}
1057+
if (base_tag) {
1058+
clean_html = base_tag[0] + clean_html;
9871059
}
9881060
try {
9891061
clean_html = this._rebaseHTML(url, clean_html);
@@ -1121,7 +1193,9 @@ const inject = {
11211193
html: {
11221194
sources(cfgs, data) {
11231195
const sources = cfgs.map((cfg) => cfg.source);
1124-
sources.push("title");
1196+
sources.push("head_title");
1197+
sources.push("head_canonical");
1198+
sources.push("head_base");
11251199
const result = this._sourcesFromHtml(data, cfgs[0].url, sources);
11261200
return result;
11271201
},

0 commit comments

Comments
 (0)