PageSourceSearch

https://sermon-library-manager.web.app/embed.js

js sermon-library-manager.web.app collected 2026-10-03 12:35:21 UTC 6,261 bytes, 203 lines download raw bytes

1(function () {
2  var ROOT_ID = 'sermon-library-root';
3  var MESSAGE_TYPE = 'SERMON_LIBRARY_EMBED_HEIGHT';
4  var DEFAULT_SRC = 'https://sermon-library-manager.web.app/embed';
5  var DEFAULT_INITIAL_HEIGHT = 900;
6  var DEFAULT_MIN_HEIGHT = 700;
7  var MIN_HEIGHT = 600;
8
9  function clampHeight(height, minHeight) {
10    return Math.max(Math.max(minHeight, MIN_HEIGHT), height);
11  }
12
13  function parsePositiveInt(value, fallback) {
14    var parsed = parseInt(value, 10);
15    return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
16  }
17
18  function createOrigin(url) {
19    try {
20      return new URL(url, window.location.href).origin;
21    } catch (_error) {
22      return window.location.origin;
23    }
24  }
25
26  // Squarespace Fluid Engine uses display:grid + overflow:hidden on its section
27  // containers, which clips the negative-margin full-bleed technique. Instead we
28  // walk up the DOM, strip padding from Squarespace block wrappers, and unlock
29  // overflow so the iframe can genuinely fill the viewport width.
30  function applyFullBleed(root) {
31    if (!root.classList.contains('sermon-library-embed--fullbleed')) {
32      return;
33    }
34
35    var SQS_PADDING_SELECTORS = [
36      'sqs-block-content',
37      'sqs-block',
38      'sqs-block-code',
39      'sqs-layout',
40      'sqs-col-wrappr',
41    ];
42
43    var el = root.parentElement;
44    var depth = 0;
45
46    while (el && el !== document.body && depth < 10) {
47      var classes = el.className || '';
48
49      // Remove padding from known Squarespace wrapper elements
50      var isSqsWrapper = SQS_PADDING_SELECTORS.some(function (cls) {
51        return classes.indexOf(cls) !== -1;
52      });
53
54      if (isSqsWrapper) {
55        el.style.padding = '0';
56        el.style.maxWidth = 'none';
57      }
58
59      // Unlock overflow on any ancestor that would clip us
60      var computed = window.getComputedStyle(el);
61      if (computed.overflow === 'hidden' || computed.overflowX === 'hidden') {
62        el.style.overflow = 'visible';
63        el.style.overflowX = 'visible';
64      }
65
66      el = el.parentElement;
67      depth++;
68    }
69
70    // Now that containers are unlocked, stretch to viewport width
71    root.style.width = '100vw';
72    root.style.position = 'relative';
73    root.style.left = '50%';
74    root.style.transform = 'translateX(-50%)';
75  }
76
77  function init() {
78    var root = document.getElementById(ROOT_ID);
79
80    if (!root) {
81      return;
82    }
83
84    if (root.dataset.sermonLibraryMounted === 'true') {
85      return;
86    }
87
88    applyFullBleed(root);
89
90    var src = root.getAttribute('data-src') || DEFAULT_SRC;
91    var minHeight = parsePositiveInt(root.getAttribute('data-min-height'), DEFAULT_MIN_HEIGHT);
92    var initialHeight = parsePositiveInt(
93      root.getAttribute('data-initial-height'),
94      DEFAULT_INITIAL_HEIGHT,
95    );
96    var expectedOrigin = createOrigin(src);
97
98    // Append a timestamp so every page load bypasses CDN and browser cache
99    // for the iframe shell. JS/CSS bundles inside are still content-hash cached.
100    var sep = src.indexOf('?') !== -1 ? '&' : '?';
101    var iframeSrc = src + sep + '_cb=' + Date.now();
102
103    var iframe = document.createElement('iframe');
104    iframe.src = iframeSrc;
105    iframe.title = 'Restored Church Sermon Library';
106    iframe.loading = 'lazy';
107    iframe.allow = 'autoplay; encrypted-media; picture-in-picture';
108    iframe.allowFullscreen = true;
109    // Disable iframe's own scroll context so the parent page is the sole
110    // scroll container. Without this, mobile browsers create a nested
111    // scroll region inside the iframe.
112    iframe.scrolling = 'no';
113    iframe.style.width = '100%';
114    iframe.style.display = 'block';
115    iframe.style.border = '0';
116    iframe.style.overflow = 'hidden';
117    iframe.style.background = '#0d0d0d';
118    iframe.style.minHeight = minHeight + 'px';
119    iframe.style.height = clampHeight(initialHeight, minHeight) + 'px';
120
121    // Prevent white flash from root element before iframe paints
122    root.style.background = '#0d0d0d';
123    root.style.display = 'block';
124
125    root.innerHTML = '';
126    root.appendChild(iframe);
127    root.dataset.sermonLibraryMounted = 'true';
128
129    function handleMessage(event) {
130      if (event.origin !== expectedOrigin) {
131        return;
132      }
133
134      if (event.source !== iframe.contentWindow) {
135        return;
136      }
137
138      var data = event.data;
139
140      if (!data) {
141        return;
142      }
143
144      if (data.type === MESSAGE_TYPE && typeof data.height === 'number') {
145        iframe.style.height = clampHeight(Math.round(data.height), minHeight) + 'px';
146      }
147
148      // Child app requests scroll-to-player on mobile sermon selection.
149      // Since the parent page owns all scrolling (iframe is auto-height with
150      // no internal scroll context), we scroll the iframe element into view.
151      if (data.type === 'SERMON_LIBRARY_SCROLL_TO_PLAYER') {
152        iframe.scrollIntoView({ behavior: 'smooth', block: 'start' });
153      }
154    }
155
156    window.addEventListener('message', handleMessage);
157
158    // ── Synthetic sticky: send scroll geometry to child ──────────────────────
159    var scrollTicking = false;
160
161    function sendScrollState() {
162      var rect = iframe.getBoundingClientRect();
163      iframe.contentWindow.postMessage(
164        {
165          type: 'SERMON_LIBRARY_SCROLL_UPDATE',
166          iframeTop: rect.top,
167          iframeBottom: rect.bottom,
168          iframeHeight: rect.height,
169          viewportHeight: window.innerHeight,
170        },
171        expectedOrigin,
172      );
173      scrollTicking = false;
174    }
175
176    function scheduleScrollUpdate() {
177      if (!scrollTicking) {
178        scrollTicking = true;
179        window.requestAnimationFrame(sendScrollState);
180      }
181    }
182
183    window.addEventListener('scroll', scheduleScrollUpdate, { passive: true });
184    window.addEventListener('resize', scheduleScrollUpdate, { passive: true });
185
186    iframe.addEventListener(
187      'load',
188      function () {
189        window.requestAnimationFrame(sendScrollState);
190        setTimeout(sendScrollState, 600);
191        setTimeout(sendScrollState, 1800);
192      },
193      { once: true },
194    );
195
196  }
197
198  if (document.readyState === 'loading') {
199    document.addEventListener('DOMContentLoaded', init, { once: true });
200  } else {
201    init();
202  }
203})();

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.