PageSourceSearch

https://tdwi.org/webcasts/2026/09/~/media/tdwi/tdwi%20global/research/researchscripts.js?v=004

js tdwi.org collected 2026-09-24 08:28:29 UTC 6,776 bytes, 163 lines download raw bytes

1/* Webinar Speaker Markup Normalizer
2==================================================*/
3// Some webcast pages author speakers inline in the Content field
4// (<p><strong>Guest Speakers</strong></p> followed by <p><img><strong>Name<br>
5// Title<br>Company</strong></p> + bio paragraphs) instead of the structured
6// #about-presenters section. This rebuilds that inline markup into the same
7// structure so one set of styles (and the bio clamp below) applies everywhere.
8// Registered before the clamp handler so the section exists when it runs.
9(function () {
10    "use strict";
11
12    document.addEventListener("DOMContentLoaded", () => {
13        if (document.getElementById("about-presenters")) return;
14
15        const content = document.querySelector("#webcast .lcsContent");
16        if (!content) return;
17
18        // The CMS nests block elements inside the Content <p>; the browser
19        // splits them into siblings, so everything we need is a child of
20        // .lcsContent. Find the "Guest Speakers" heading paragraph.
21        const heading = Array.from(content.querySelectorAll("p")).find(
22            (p) =>
23                p.querySelector("strong") &&
24                !p.querySelector("img") &&
25                /^guest speakers?$/i.test(p.textContent.trim())
26        );
27        if (!heading) return;
28
29        // Collect everything from the heading to the on-demand marker (or the
30        // first non-content element) — speaker paragraphs start with an <img>.
31        const collected = [];
32        let node = heading.nextElementSibling;
33        while (node && !node.classList.contains("ondemand") && /^(P|UL)$/.test(node.tagName)) {
34            // Skip metadata paragraphs (id ends in StartDate/StartTime, prefix
35            // varies per placeholder) so they don't land in a speaker bio.
36            if (!/(StartDate|StartTime)$/.test(node.id)) collected.push(node);
37            node = node.nextElementSibling;
38        }
39
40        // Group into speakers: an <img> paragraph opens a speaker, following
41        // paragraphs are their bio.
42        const speakers = [];
43        collected.forEach((el) => {
44            if (el.tagName === "P" && el.querySelector("img")) {
45                speakers.push({ intro: el, bio: [] });
46            } else if (speakers.length) {
47                speakers[speakers.length - 1].bio.push(el);
48            }
49        });
50        if (!speakers.length) return;
51
52        const section = document.createElement("section");
53        section.id = "about-presenters";
54
55        const title = document.createElement("p");
56        title.className = "presenters-title";
57        title.textContent = heading.textContent.trim();
58        section.appendChild(title);
59
60        speakers.forEach(({ intro, bio }) => {
61            const wrapper = document.createElement("div");
62            wrapper.className = "presenter-info-wrapper";
63
64            const info = document.createElement("div");
65            info.className = "presenter-info";
66
67            // Name / title / company come from the <strong> block, one per
68            // <br>-separated line.
69            const strong = intro.querySelector("strong");
70            const lines = strong
71                ? strong.innerHTML
72                      .split(/<br\s*\/?>/i)
73                      .map((line) => {
74                          const probe = document.createElement("div");
75                          probe.innerHTML = line;
76                          return probe.textContent.replace(/\s+/g, " ").trim();
77                      })
78                      .filter(Boolean)
79                : [];
80
81            const img = intro.querySelector("img");
82            const photo = document.createElement("img");
83            photo.className = "presenter-photo";
84            photo.src = img.getAttribute("src");
85            photo.alt = img.getAttribute("alt") || lines[0] || "";
86            info.appendChild(photo);
87
88            ["presenter-name", "presenter-title", "presenter-company"].forEach(
89                (cls, i) => {
90                    if (!lines[i]) return;
91                    const p = document.createElement("p");
92                    p.className = cls;
93                    p.textContent = lines[i];
94                    info.appendChild(p);
95                }
96            );
97
98            const bioWrap = document.createElement("div");
99            bioWrap.className = "presenter-bio";
100            bio.forEach((el) => bioWrap.appendChild(el));
101
102            wrapper.appendChild(info);
103            wrapper.appendChild(bioWrap);
104            section.appendChild(wrapper);
105        });
106
107        // Match the structured template: the section sits after the
108        // on-demand marker when present, otherwise where the heading was.
109        const onDemand = content.querySelector("p.ondemand");
110        if (onDemand) {
111            onDemand.after(section);
112        } else {
113            heading.before(section);
114        }
115
116        heading.remove();
117        // Bio paragraphs were moved into the section — only remove what's
118        // still sitting in the original content flow (the intro paragraphs).
119        collected.forEach((el) => {
120            if (!section.contains(el)) el.remove();
121        });
122    });
123})();
124
125/* Webinar Speaker Bios (Inner Pages)
126==================================================*/
127// Clamps long speaker bios (#about-presenters) and adds a quiet
128// "Show more" toggle — only when the bio actually overflows the clamp.
129(function () {
130    "use strict";
131
132    document.addEventListener("DOMContentLoaded", () => {
133        const bios = document.querySelectorAll("#about-presenters .presenter-bio");
134
135        bios.forEach((bio) => {
136            // Idempotent: skip bios already processed (page may load this
137            // script twice — e.g. template include + media-library include)
138            if (bio.nextElementSibling?.classList.contains("presenter-bio-toggle")) return;
139
140            bio.classList.add("is-clamped");
141
142            // No overflow at the clamp height → no toggle needed
143            if (bio.scrollHeight <= bio.clientHeight) {
144                bio.classList.remove("is-clamped");
145                return;
146            }
147
148            const toggle = document.createElement("button");
149            toggle.type = "button"; // Sitecore pages submit on bare <button>
150            toggle.className = "presenter-bio-toggle";
151            toggle.textContent = "Show more";
152            toggle.setAttribute("aria-expanded", "false");
153
154            toggle.addEventListener("click", () => {
155                const expanded = bio.classList.toggle("is-expanded");
156                toggle.textContent = expanded ? "Show less" : "Show more";
157                toggle.setAttribute("aria-expanded", String(expanded));
158            });
159
160            bio.after(toggle);
161        });
162    });
163})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.