PageSourceSearch

https://ravkooktorah.org/dist/xWk_OQ0atv.js

js ravkooktorah.org collected 2026-10-02 04:14:04 UTC 9,902 bytes, 356 lines download raw bytes

1// Thank you to https://github.com/daviddarnes/heading-anchors
2// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
3
4let globalInstanceIndex = 0;
5
6class HeadingAnchors extends HTMLElement {
7	static register(tagName = "heading-anchors", registry = window.customElements) {
8		if(registry && !registry.get(tagName)) {
9			registry.define(tagName, this);
10		}
11	}
12
13	static attributes = {
14		exclude: "data-ha-exclude",
15		prefix: "prefix",
16		content: "content",
17	}
18
19	static classes = {
20		anchor: "ha",
21		placeholder: "ha-placeholder",
22		srOnly: "ha-visualhide",
23	}
24
25	static defaultSelector = "h2,h3,h4,h5,h6";
26
27	static css = `
28.${HeadingAnchors.classes.srOnly} {
29	clip: rect(0 0 0 0);
30	height: 1px;
31	overflow: hidden;
32	position: absolute;
33	width: 1px;
34}
35.${HeadingAnchors.classes.anchor} {
36	position: absolute;
37	left: var(--ha_offsetx);
38	top: var(--ha_offsety);
39	text-decoration: none;
40	opacity: 0;
41}
42.${HeadingAnchors.classes.placeholder} {
43	opacity: .3;
44}
45.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
46	opacity: 1;
47}
48.${HeadingAnchors.classes.anchor},
49.${HeadingAnchors.classes.placeholder} {
50	display: inline-block;
51	padding: 0 .25em;
52
53	/* Disable selection of visually hidden label */
54	-webkit-user-select: none;
55	user-select: none;
56}
57
58@supports (anchor-name: none) {
59	.${HeadingAnchors.classes.anchor} {
60		position: absolute;
61		left: anchor(left);
62		top: anchor(top);
63	}
64}`;
65
66	get supports() {
67		return "replaceSync" in CSSStyleSheet.prototype;
68	}
69
70	get supportsAnchorPosition() {
71		return CSS.supports("anchor-name: none");
72	}
73
74	constructor() {
75		super();
76
77		if(!this.supports) {
78			return;
79		}
80
81		let sheet = new CSSStyleSheet();
82		sheet.replaceSync(HeadingAnchors.css);
83		document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
84
85		this.headingStyles = {};
86		this.instanceIndex = globalInstanceIndex++;
87	}
88
89	connectedCallback() {
90		if (!this.supports) {
91			return;
92		}
93
94		this.headings.forEach((heading, index) => {
95			if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
96				let anchor = this.getAnchorElement(heading);
97				let placeholder = this.getPlaceholderElement();
98
99				// Prefers anchor position approach for better accessibility
100				// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
101				if(this.supportsAnchorPosition) {
102					let anchorName = `--ha_${this.instanceIndex}_${index}`;
103					placeholder.style.setProperty("anchor-name", anchorName);
104					anchor.style.positionAnchor = anchorName;
105				}
106
107				heading.appendChild(placeholder);
108				heading.after(anchor);
109			}
110		});
111	}
112
113	// Polyfill-only
114	positionAnchorFromPlaceholder(placeholder) {
115		if(!placeholder) {
116			return;
117		}
118
119		let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
120		if(!heading.nextElementSibling) {
121			return;
122		}
123
124		// TODO next element could be more defensive
125		this.positionAnchor(heading.nextElementSibling);
126	}
127
128	// Polyfill-only
129	positionAnchor(anchor) {
130		if(!anchor || !anchor.previousElementSibling) {
131			return;
132		}
133
134		// TODO previous element could be more defensive
135		let heading = anchor.previousElementSibling;
136		this.setFontProp(heading, anchor);
137
138		if(this.supportsAnchorPosition) {
139			// quit early
140			return;
141		}
142
143		let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
144		if(placeholder) {
145			anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
146			anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
147		}
148	}
149
150	setFontProp(heading, anchor) {
151		let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
152		if(placeholder) {
153			let style = getComputedStyle(placeholder);
154			let props = ["font-weight", "font-size", "line-height", "font-family"];
155			let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
156			anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
157			let vars = style.getPropertyValue("font-variation-settings");
158			if(vars) {
159				anchor.style.setProperty("font-variation-settings", vars);
160			}
161		}
162	}
163
164	getAccessibleTextPrefix() {
165		// Useful for i18n
166		return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
167	}
168
169	getContent() {
170		if(this.hasAttribute(HeadingAnchors.attributes.content)) {
171			return this.getAttribute(HeadingAnchors.attributes.content);
172		}
173		return "#";
174	}
175
176	// Placeholder nests inside of heading
177	getPlaceholderElement() {
178		let ph = document.createElement("span");
179		ph.setAttribute("aria-hidden", true);
180		ph.classList.add(HeadingAnchors.classes.placeholder);
181		let content = this.getContent();
182		if(content) {
183			ph.textContent = content;
184		}
185
186		ph.addEventListener("mouseover", (e) => {
187			let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
188			if(placeholder) {
189				this.positionAnchorFromPlaceholder(placeholder);
190			}
191		});
192
193		return ph;
194	}
195
196	getAnchorElement(heading) {
197		let anchor = document.createElement("a");
198		anchor.href = `#${heading.id}`;
199		anchor.classList.add(HeadingAnchors.classes.anchor);
200
201		let content = this.getContent();
202		anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
203
204		anchor.addEventListener("focus", e => {
205			let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
206			if(anchor) {
207				this.positionAnchor(anchor);
208			}
209		});
210
211		anchor.addEventListener("mouseover", (e) => {
212			// when CSS anchor positioning is supported, this is only used to set the font
213			let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
214			this.positionAnchor(anchor);
215		});
216
217		return anchor;
218	}
219
220	get headings() {
221		return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
222	}
223
224	get selector() {
225		return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
226	}
227}
228
229HeadingAnchors.register();
230
231export { HeadingAnchors }
232const speakButton = document.getElementById('speak-button');
233  const pauseButton = document.getElementById('pause-button');
234  let isSpeaking = false;
235
236  // 1. Pre-load voices for Mobile Chrome
237  let voices = [];
238  window.speechSynthesis.onvoiceschanged = () => {
239    voices = window.speechSynthesis.getVoices();
240  };
241
242  speakButton.addEventListener('click', () => {
243    if (isSpeaking) {
244      window.speechSynthesis.cancel();
245      resetUI();
246    } else {
247      const title = document.getElementById('article-title')?.innerText || "";
248      const content = document.getElementById('speak-content')?.innerText || "";
249      const textToRead = title + ". " + content;
250
251      if (textToRead) {
252        // 2. CRITICAL FOR MOBILE: Cancel any stuck processes first
253        window.speechSynthesis.cancel();
254
255        const utterance = new SpeechSynthesisUtterance(textToRead);
256        
257        // 3. Set a default lang for mobile reliability
258        utterance.lang = 'en-US';
259        utterance.rate = 0.9; 
260
261        utterance.onend = () => resetUI();
262        utterance.onerror = (event) => {
263            console.error('SpeechSynthesis Error:', event);
264            resetUI();
265        };
266
267        // 4. Speak
268        window.speechSynthesis.speak(utterance);
269        
270        isSpeaking = true;
271        speakButton.innerHTML = "<span>🛑</span> Stop";
272        pauseButton.style.display = "flex";
273      }
274    }
275  });
276
277  pauseButton.addEventListener('click', () => {
278    // Note: resume() on mobile Chrome can be flaky. 
279    // This is the standard way to handle it:
280    if (window.speechSynthesis.paused) {
281      window.speechSynthesis.resume();
282      pauseButton.innerHTML = "<span>▶️</span> Resume";
283    } else {
284      window.speechSynthesis.pause();
285      pauseButton.innerHTML = "<span>⏸️</span> Pause";
286    }
287  });
288
289  function resetUI() {
290    isSpeaking = false;
291    speakButton.innerHTML = "<span>🔊</span> Listen";
292    pauseButton.style.display = "none";
293    pauseButton.innerHTML = "<span>⏸️</span> Pause";
294  }
295
296  window.onbeforeunload = () => window.speechSynthesis.cancel();
297const shareBtn = document.getElementById('native-share-btn');
298  if (navigator.share) {
299    shareBtn.style.display = 'flex';
300    shareBtn.addEventListener('click', () => {
301      const articleTitle = document.querySelector('h1')?.innerText || "Rav Kook Torah";
302      const shareUrl = window.location.href.split('#')[0];
303      navigator.share({
304        title: articleTitle,
305        text: articleTitle + "\n\n", 
306        url: shareUrl
307      }).catch((error) => console.log('Error sharing', error));
308    });
309  }
310sefaria.link({
311		mode: "popup-click"	 
312	});
313(function (s, e, n, d, er) {
314    s['Sender'] = er;
315    s[er] = s[er] || function () {
316      (s[er].q = s[er].q || []).push(arguments)
317    }, s[er].l = 1 * new Date();
318    var a = e.createElement(n),
319        m = e.getElementsByTagName(n)[0];
320    a.async = 1;
321    a.src = d;
322    m.parentNode.insertBefore(a, m)
323  })(window, document, 'script', 'https://cdn.sender.net/accounts_resources/universal.js', 'sender');
324  sender('422a8c94418565')
325window.addEventListener('DOMContentLoaded', (event) => {
326    const modal = document.getElementById("search-modal");
327    const openBtn = document.getElementById("search-trigger");
328    const closeBtn = document.getElementById("modal-close-btn");
329
330    // Open Modal
331    if (openBtn) {
332      openBtn.onclick = (e) => {
333        e.preventDefault();
334        modal.style.display = "block";
335      };
336    }
337
338    // Close Modal via 'X' button
339    if (closeBtn) {
340      closeBtn.onclick = () => {
341        modal.style.display = "none";
342      };
343    }
344
345    // Close Modal via clicking outside the box
346    window.onclick = (event) => {
347      if (event.target == modal) {
348        modal.style.display = "none";
349      }
350    };
351
352    // Close via Escape key
353    document.addEventListener('keydown', (e) => {
354      if (e.key === "Escape") modal.style.display = "none";
355    });
356  });

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.