commonmeta-ruby 3.7.2 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/publish.yml +23 -0
  3. data/Gemfile.lock +30 -29
  4. data/bin/commonmeta +1 -1
  5. data/docs/.gitignore +1 -0
  6. data/docs/_publish.yml +4 -0
  7. data/docs/_quarto.yml +46 -0
  8. data/docs/_site/favicon.ico +0 -0
  9. data/docs/_site/images/icon.png +0 -0
  10. data/docs/_site/index.html +947 -0
  11. data/docs/_site/readers/bibtex_reader.html +802 -0
  12. data/docs/_site/search.json +74 -0
  13. data/docs/_site/site_libs/bootstrap/bootstrap-dark.min.css +9 -0
  14. data/docs/_site/site_libs/bootstrap/bootstrap-icons.css +2078 -0
  15. data/docs/_site/site_libs/bootstrap/bootstrap-icons.woff +0 -0
  16. data/docs/_site/site_libs/bootstrap/bootstrap.min.css +9 -0
  17. data/docs/_site/site_libs/bootstrap/bootstrap.min.js +7 -0
  18. data/docs/_site/site_libs/clipboard/clipboard.min.js +7 -0
  19. data/docs/_site/site_libs/quarto-html/anchor.min.js +9 -0
  20. data/docs/_site/site_libs/quarto-html/popper.min.js +6 -0
  21. data/docs/_site/site_libs/quarto-html/quarto-syntax-highlighting-dark.css +179 -0
  22. data/docs/_site/site_libs/quarto-html/quarto-syntax-highlighting.css +179 -0
  23. data/docs/_site/site_libs/quarto-html/quarto.js +889 -0
  24. data/docs/_site/site_libs/quarto-html/tippy.css +1 -0
  25. data/docs/_site/site_libs/quarto-html/tippy.umd.min.js +2 -0
  26. data/docs/_site/site_libs/quarto-nav/headroom.min.js +7 -0
  27. data/docs/_site/site_libs/quarto-nav/quarto-nav.js +288 -0
  28. data/docs/_site/site_libs/quarto-search/autocomplete.umd.js +3 -0
  29. data/docs/_site/site_libs/quarto-search/fuse.min.js +9 -0
  30. data/docs/_site/site_libs/quarto-search/quarto-search.js +1241 -0
  31. data/docs/_site/utils/doi_utils.html +787 -0
  32. data/docs/_site/writers/bibtex_writer.html +803 -0
  33. data/docs/favicon.ico +0 -0
  34. data/docs/images/icon.png +0 -0
  35. data/docs/index.qmd +83 -0
  36. data/docs/readers/bibtex_reader.ipynb +37 -0
  37. data/docs/theme.scss +7 -0
  38. data/docs/utils/doi_utils.ipynb +28 -0
  39. data/docs/writers/bibtex_writer.ipynb +39 -0
  40. data/lib/commonmeta/cli.rb +5 -5
  41. data/lib/commonmeta/crossref_utils.rb +7 -1
  42. data/lib/commonmeta/doi_utils.rb +5 -0
  43. data/lib/commonmeta/metadata.rb +66 -62
  44. data/lib/commonmeta/readers/json_feed_reader.rb +15 -5
  45. data/lib/commonmeta/version.rb +1 -1
  46. data/spec/cli_spec.rb +2 -2
  47. data/spec/doi_utils_spec.rb +14 -0
  48. data/spec/fixtures/vcr_cassettes/Commonmeta_CLI/json_feed/json_feed_blog_slug.yml +71 -0
  49. data/spec/readers/json_feed_reader_spec.rb +40 -8
  50. data/spec/writers/crossref_xml_writer_spec.rb +25 -8
  51. metadata +40 -3
@@ -0,0 +1,889 @@
1
+ const sectionChanged = new CustomEvent("quarto-sectionChanged", {
2
+ detail: {},
3
+ bubbles: true,
4
+ cancelable: false,
5
+ composed: false,
6
+ });
7
+
8
+ const layoutMarginEls = () => {
9
+ // Find any conflicting margin elements and add margins to the
10
+ // top to prevent overlap
11
+ const marginChildren = window.document.querySelectorAll(
12
+ ".column-margin.column-container > * "
13
+ );
14
+
15
+ let lastBottom = 0;
16
+ for (const marginChild of marginChildren) {
17
+ if (marginChild.offsetParent !== null) {
18
+ // clear the top margin so we recompute it
19
+ marginChild.style.marginTop = null;
20
+ const top = marginChild.getBoundingClientRect().top + window.scrollY;
21
+ if (top < lastBottom) {
22
+ const margin = lastBottom - top;
23
+ marginChild.style.marginTop = `${margin}px`;
24
+ }
25
+ const styles = window.getComputedStyle(marginChild);
26
+ const marginTop = parseFloat(styles["marginTop"]);
27
+ lastBottom = top + marginChild.getBoundingClientRect().height + marginTop;
28
+ }
29
+ }
30
+ };
31
+
32
+ window.document.addEventListener("DOMContentLoaded", function (_event) {
33
+ // Recompute the position of margin elements anytime the body size changes
34
+ if (window.ResizeObserver) {
35
+ const resizeObserver = new window.ResizeObserver(
36
+ throttle(layoutMarginEls, 50)
37
+ );
38
+ resizeObserver.observe(window.document.body);
39
+ }
40
+
41
+ const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]');
42
+ const sidebarEl = window.document.getElementById("quarto-sidebar");
43
+ const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left");
44
+ const marginSidebarEl = window.document.getElementById(
45
+ "quarto-margin-sidebar"
46
+ );
47
+ // function to determine whether the element has a previous sibling that is active
48
+ const prevSiblingIsActiveLink = (el) => {
49
+ const sibling = el.previousElementSibling;
50
+ if (sibling && sibling.tagName === "A") {
51
+ return sibling.classList.contains("active");
52
+ } else {
53
+ return false;
54
+ }
55
+ };
56
+
57
+ // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior)
58
+ function fireSlideEnter(e) {
59
+ const event = window.document.createEvent("Event");
60
+ event.initEvent("slideenter", true, true);
61
+ window.document.dispatchEvent(event);
62
+ }
63
+ const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]');
64
+ tabs.forEach((tab) => {
65
+ tab.addEventListener("shown.bs.tab", fireSlideEnter);
66
+ });
67
+
68
+ // fire slideEnter for tabby tab activations (for htmlwidget resize behavior)
69
+ document.addEventListener("tabby", fireSlideEnter, false);
70
+
71
+ // Track scrolling and mark TOC links as active
72
+ // get table of contents and sidebar (bail if we don't have at least one)
73
+ const tocLinks = tocEl
74
+ ? [...tocEl.querySelectorAll("a[data-scroll-target]")]
75
+ : [];
76
+ const makeActive = (link) => tocLinks[link].classList.add("active");
77
+ const removeActive = (link) => tocLinks[link].classList.remove("active");
78
+ const removeAllActive = () =>
79
+ [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link));
80
+
81
+ // activate the anchor for a section associated with this TOC entry
82
+ tocLinks.forEach((link) => {
83
+ link.addEventListener("click", () => {
84
+ if (link.href.indexOf("#") !== -1) {
85
+ const anchor = link.href.split("#")[1];
86
+ const heading = window.document.querySelector(
87
+ `[data-anchor-id=${anchor}]`
88
+ );
89
+ if (heading) {
90
+ // Add the class
91
+ heading.classList.add("reveal-anchorjs-link");
92
+
93
+ // function to show the anchor
94
+ const handleMouseout = () => {
95
+ heading.classList.remove("reveal-anchorjs-link");
96
+ heading.removeEventListener("mouseout", handleMouseout);
97
+ };
98
+
99
+ // add a function to clear the anchor when the user mouses out of it
100
+ heading.addEventListener("mouseout", handleMouseout);
101
+ }
102
+ }
103
+ });
104
+ });
105
+
106
+ const sections = tocLinks.map((link) => {
107
+ const target = link.getAttribute("data-scroll-target");
108
+ if (target.startsWith("#")) {
109
+ return window.document.getElementById(decodeURI(`${target.slice(1)}`));
110
+ } else {
111
+ return window.document.querySelector(decodeURI(`${target}`));
112
+ }
113
+ });
114
+
115
+ const sectionMargin = 200;
116
+ let currentActive = 0;
117
+ // track whether we've initialized state the first time
118
+ let init = false;
119
+
120
+ const updateActiveLink = () => {
121
+ // The index from bottom to top (e.g. reversed list)
122
+ let sectionIndex = -1;
123
+ if (
124
+ window.innerHeight + window.pageYOffset >=
125
+ window.document.body.offsetHeight
126
+ ) {
127
+ sectionIndex = 0;
128
+ } else {
129
+ sectionIndex = [...sections].reverse().findIndex((section) => {
130
+ if (section) {
131
+ return window.pageYOffset >= section.offsetTop - sectionMargin;
132
+ } else {
133
+ return false;
134
+ }
135
+ });
136
+ }
137
+ if (sectionIndex > -1) {
138
+ const current = sections.length - sectionIndex - 1;
139
+ if (current !== currentActive) {
140
+ removeAllActive();
141
+ currentActive = current;
142
+ makeActive(current);
143
+ if (init) {
144
+ window.dispatchEvent(sectionChanged);
145
+ }
146
+ init = true;
147
+ }
148
+ }
149
+ };
150
+
151
+ const inHiddenRegion = (top, bottom, hiddenRegions) => {
152
+ for (const region of hiddenRegions) {
153
+ if (top <= region.bottom && bottom >= region.top) {
154
+ return true;
155
+ }
156
+ }
157
+ return false;
158
+ };
159
+
160
+ const categorySelector = "header.quarto-title-block .quarto-category";
161
+ const activateCategories = (href) => {
162
+ // Find any categories
163
+ // Surround them with a link pointing back to:
164
+ // #category=Authoring
165
+ try {
166
+ const categoryEls = window.document.querySelectorAll(categorySelector);
167
+ for (const categoryEl of categoryEls) {
168
+ const categoryText = categoryEl.textContent;
169
+ if (categoryText) {
170
+ const link = `${href}#category=${encodeURIComponent(categoryText)}`;
171
+ const linkEl = window.document.createElement("a");
172
+ linkEl.setAttribute("href", link);
173
+ for (const child of categoryEl.childNodes) {
174
+ linkEl.append(child);
175
+ }
176
+ categoryEl.appendChild(linkEl);
177
+ }
178
+ }
179
+ } catch {
180
+ // Ignore errors
181
+ }
182
+ };
183
+ function hasTitleCategories() {
184
+ return window.document.querySelector(categorySelector) !== null;
185
+ }
186
+
187
+ function offsetRelativeUrl(url) {
188
+ const offset = getMeta("quarto:offset");
189
+ return offset ? offset + url : url;
190
+ }
191
+
192
+ function offsetAbsoluteUrl(url) {
193
+ const offset = getMeta("quarto:offset");
194
+ const baseUrl = new URL(offset, window.location);
195
+
196
+ const projRelativeUrl = url.replace(baseUrl, "");
197
+ if (projRelativeUrl.startsWith("/")) {
198
+ return projRelativeUrl;
199
+ } else {
200
+ return "/" + projRelativeUrl;
201
+ }
202
+ }
203
+
204
+ // read a meta tag value
205
+ function getMeta(metaName) {
206
+ const metas = window.document.getElementsByTagName("meta");
207
+ for (let i = 0; i < metas.length; i++) {
208
+ if (metas[i].getAttribute("name") === metaName) {
209
+ return metas[i].getAttribute("content");
210
+ }
211
+ }
212
+ return "";
213
+ }
214
+
215
+ async function findAndActivateCategories() {
216
+ const currentPagePath = offsetAbsoluteUrl(window.location.href);
217
+ const response = await fetch(offsetRelativeUrl("listings.json"));
218
+ if (response.status == 200) {
219
+ return response.json().then(function (listingPaths) {
220
+ const listingHrefs = [];
221
+ for (const listingPath of listingPaths) {
222
+ const pathWithoutLeadingSlash = listingPath.listing.substring(1);
223
+ for (const item of listingPath.items) {
224
+ if (
225
+ item === currentPagePath ||
226
+ item === currentPagePath + "index.html"
227
+ ) {
228
+ // Resolve this path against the offset to be sure
229
+ // we already are using the correct path to the listing
230
+ // (this adjusts the listing urls to be rooted against
231
+ // whatever root the page is actually running against)
232
+ const relative = offsetRelativeUrl(pathWithoutLeadingSlash);
233
+ const baseUrl = window.location;
234
+ const resolvedPath = new URL(relative, baseUrl);
235
+ listingHrefs.push(resolvedPath.pathname);
236
+ break;
237
+ }
238
+ }
239
+ }
240
+
241
+ // Look up the tree for a nearby linting and use that if we find one
242
+ const nearestListing = findNearestParentListing(
243
+ offsetAbsoluteUrl(window.location.pathname),
244
+ listingHrefs
245
+ );
246
+ if (nearestListing) {
247
+ activateCategories(nearestListing);
248
+ } else {
249
+ // See if the referrer is a listing page for this item
250
+ const referredRelativePath = offsetAbsoluteUrl(document.referrer);
251
+ const referrerListing = listingHrefs.find((listingHref) => {
252
+ const isListingReferrer =
253
+ listingHref === referredRelativePath ||
254
+ listingHref === referredRelativePath + "index.html";
255
+ return isListingReferrer;
256
+ });
257
+
258
+ if (referrerListing) {
259
+ // Try to use the referrer if possible
260
+ activateCategories(referrerListing);
261
+ } else if (listingHrefs.length > 0) {
262
+ // Otherwise, just fall back to the first listing
263
+ activateCategories(listingHrefs[0]);
264
+ }
265
+ }
266
+ });
267
+ }
268
+ }
269
+ if (hasTitleCategories()) {
270
+ findAndActivateCategories();
271
+ }
272
+
273
+ const findNearestParentListing = (href, listingHrefs) => {
274
+ if (!href || !listingHrefs) {
275
+ return undefined;
276
+ }
277
+ // Look up the tree for a nearby linting and use that if we find one
278
+ const relativeParts = href.substring(1).split("/");
279
+ while (relativeParts.length > 0) {
280
+ const path = relativeParts.join("/");
281
+ for (const listingHref of listingHrefs) {
282
+ if (listingHref.startsWith(path)) {
283
+ return listingHref;
284
+ }
285
+ }
286
+ relativeParts.pop();
287
+ }
288
+
289
+ return undefined;
290
+ };
291
+
292
+ const manageSidebarVisiblity = (el, placeholderDescriptor) => {
293
+ let isVisible = true;
294
+ let elRect;
295
+
296
+ return (hiddenRegions) => {
297
+ if (el === null) {
298
+ return;
299
+ }
300
+
301
+ // Find the last element of the TOC
302
+ const lastChildEl = el.lastElementChild;
303
+
304
+ if (lastChildEl) {
305
+ // Converts the sidebar to a menu
306
+ const convertToMenu = () => {
307
+ for (const child of el.children) {
308
+ child.style.opacity = 0;
309
+ child.style.overflow = "hidden";
310
+ }
311
+
312
+ nexttick(() => {
313
+ const toggleContainer = window.document.createElement("div");
314
+ toggleContainer.style.width = "100%";
315
+ toggleContainer.classList.add("zindex-over-content");
316
+ toggleContainer.classList.add("quarto-sidebar-toggle");
317
+ toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom
318
+ toggleContainer.id = placeholderDescriptor.id;
319
+ toggleContainer.style.position = "fixed";
320
+
321
+ const toggleIcon = window.document.createElement("i");
322
+ toggleIcon.classList.add("quarto-sidebar-toggle-icon");
323
+ toggleIcon.classList.add("bi");
324
+ toggleIcon.classList.add("bi-caret-down-fill");
325
+
326
+ const toggleTitle = window.document.createElement("div");
327
+ const titleEl = window.document.body.querySelector(
328
+ placeholderDescriptor.titleSelector
329
+ );
330
+ if (titleEl) {
331
+ toggleTitle.append(
332
+ titleEl.textContent || titleEl.innerText,
333
+ toggleIcon
334
+ );
335
+ }
336
+ toggleTitle.classList.add("zindex-over-content");
337
+ toggleTitle.classList.add("quarto-sidebar-toggle-title");
338
+ toggleContainer.append(toggleTitle);
339
+
340
+ const toggleContents = window.document.createElement("div");
341
+ toggleContents.classList = el.classList;
342
+ toggleContents.classList.add("zindex-over-content");
343
+ toggleContents.classList.add("quarto-sidebar-toggle-contents");
344
+ for (const child of el.children) {
345
+ if (child.id === "toc-title") {
346
+ continue;
347
+ }
348
+
349
+ const clone = child.cloneNode(true);
350
+ clone.style.opacity = 1;
351
+ clone.style.display = null;
352
+ toggleContents.append(clone);
353
+ }
354
+ toggleContents.style.height = "0px";
355
+ const positionToggle = () => {
356
+ // position the element (top left of parent, same width as parent)
357
+ if (!elRect) {
358
+ elRect = el.getBoundingClientRect();
359
+ }
360
+ toggleContainer.style.left = `${elRect.left}px`;
361
+ toggleContainer.style.top = `${elRect.top}px`;
362
+ toggleContainer.style.width = `${elRect.width}px`;
363
+ };
364
+ positionToggle();
365
+
366
+ toggleContainer.append(toggleContents);
367
+ el.parentElement.prepend(toggleContainer);
368
+
369
+ // Process clicks
370
+ let tocShowing = false;
371
+ // Allow the caller to control whether this is dismissed
372
+ // when it is clicked (e.g. sidebar navigation supports
373
+ // opening and closing the nav tree, so don't dismiss on click)
374
+ const clickEl = placeholderDescriptor.dismissOnClick
375
+ ? toggleContainer
376
+ : toggleTitle;
377
+
378
+ const closeToggle = () => {
379
+ if (tocShowing) {
380
+ toggleContainer.classList.remove("expanded");
381
+ toggleContents.style.height = "0px";
382
+ tocShowing = false;
383
+ }
384
+ };
385
+
386
+ // Get rid of any expanded toggle if the user scrolls
387
+ window.document.addEventListener(
388
+ "scroll",
389
+ throttle(() => {
390
+ closeToggle();
391
+ }, 50)
392
+ );
393
+
394
+ // Handle positioning of the toggle
395
+ window.addEventListener(
396
+ "resize",
397
+ throttle(() => {
398
+ elRect = undefined;
399
+ positionToggle();
400
+ }, 50)
401
+ );
402
+
403
+ window.addEventListener("quarto-hrChanged", () => {
404
+ elRect = undefined;
405
+ });
406
+
407
+ // Process the click
408
+ clickEl.onclick = () => {
409
+ if (!tocShowing) {
410
+ toggleContainer.classList.add("expanded");
411
+ toggleContents.style.height = null;
412
+ tocShowing = true;
413
+ } else {
414
+ closeToggle();
415
+ }
416
+ };
417
+ });
418
+ };
419
+
420
+ // Converts a sidebar from a menu back to a sidebar
421
+ const convertToSidebar = () => {
422
+ for (const child of el.children) {
423
+ child.style.opacity = 1;
424
+ child.style.overflow = null;
425
+ }
426
+
427
+ const placeholderEl = window.document.getElementById(
428
+ placeholderDescriptor.id
429
+ );
430
+ if (placeholderEl) {
431
+ placeholderEl.remove();
432
+ }
433
+
434
+ el.classList.remove("rollup");
435
+ };
436
+
437
+ if (isReaderMode()) {
438
+ convertToMenu();
439
+ isVisible = false;
440
+ } else {
441
+ // Find the top and bottom o the element that is being managed
442
+ const elTop = el.offsetTop;
443
+ const elBottom =
444
+ elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight;
445
+
446
+ if (!isVisible) {
447
+ // If the element is current not visible reveal if there are
448
+ // no conflicts with overlay regions
449
+ if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) {
450
+ convertToSidebar();
451
+ isVisible = true;
452
+ }
453
+ } else {
454
+ // If the element is visible, hide it if it conflicts with overlay regions
455
+ // and insert a placeholder toggle (or if we're in reader mode)
456
+ if (inHiddenRegion(elTop, elBottom, hiddenRegions)) {
457
+ convertToMenu();
458
+ isVisible = false;
459
+ }
460
+ }
461
+ }
462
+ }
463
+ };
464
+ };
465
+
466
+ const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]');
467
+ for (const tabEl of tabEls) {
468
+ const id = tabEl.getAttribute("data-bs-target");
469
+ if (id) {
470
+ const columnEl = document.querySelector(
471
+ `${id} .column-margin, .tabset-margin-content`
472
+ );
473
+ if (columnEl)
474
+ tabEl.addEventListener("shown.bs.tab", function (event) {
475
+ const el = event.srcElement;
476
+ if (el) {
477
+ const visibleCls = `${el.id}-margin-content`;
478
+ // walk up until we find a parent tabset
479
+ let panelTabsetEl = el.parentElement;
480
+ while (panelTabsetEl) {
481
+ if (panelTabsetEl.classList.contains("panel-tabset")) {
482
+ break;
483
+ }
484
+ panelTabsetEl = panelTabsetEl.parentElement;
485
+ }
486
+
487
+ if (panelTabsetEl) {
488
+ const prevSib = panelTabsetEl.previousElementSibling;
489
+ if (
490
+ prevSib &&
491
+ prevSib.classList.contains("tabset-margin-container")
492
+ ) {
493
+ const childNodes = prevSib.querySelectorAll(
494
+ ".tabset-margin-content"
495
+ );
496
+ for (const childEl of childNodes) {
497
+ if (childEl.classList.contains(visibleCls)) {
498
+ childEl.classList.remove("collapse");
499
+ } else {
500
+ childEl.classList.add("collapse");
501
+ }
502
+ }
503
+ }
504
+ }
505
+ }
506
+
507
+ layoutMarginEls();
508
+ });
509
+ }
510
+ }
511
+
512
+ // Manage the visibility of the toc and the sidebar
513
+ const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, {
514
+ id: "quarto-toc-toggle",
515
+ titleSelector: "#toc-title",
516
+ dismissOnClick: true,
517
+ });
518
+ const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, {
519
+ id: "quarto-sidebarnav-toggle",
520
+ titleSelector: ".title",
521
+ dismissOnClick: false,
522
+ });
523
+ let tocLeftScrollVisibility;
524
+ if (leftTocEl) {
525
+ tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, {
526
+ id: "quarto-lefttoc-toggle",
527
+ titleSelector: "#toc-title",
528
+ dismissOnClick: true,
529
+ });
530
+ }
531
+
532
+ // Find the first element that uses formatting in special columns
533
+ const conflictingEls = window.document.body.querySelectorAll(
534
+ '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]'
535
+ );
536
+
537
+ // Filter all the possibly conflicting elements into ones
538
+ // the do conflict on the left or ride side
539
+ const arrConflictingEls = Array.from(conflictingEls);
540
+ const leftSideConflictEls = arrConflictingEls.filter((el) => {
541
+ if (el.tagName === "ASIDE") {
542
+ return false;
543
+ }
544
+ return Array.from(el.classList).find((className) => {
545
+ return (
546
+ className !== "column-body" &&
547
+ className.startsWith("column-") &&
548
+ !className.endsWith("right") &&
549
+ !className.endsWith("container") &&
550
+ className !== "column-margin"
551
+ );
552
+ });
553
+ });
554
+ const rightSideConflictEls = arrConflictingEls.filter((el) => {
555
+ if (el.tagName === "ASIDE") {
556
+ return true;
557
+ }
558
+
559
+ const hasMarginCaption = Array.from(el.classList).find((className) => {
560
+ return className == "margin-caption";
561
+ });
562
+ if (hasMarginCaption) {
563
+ return true;
564
+ }
565
+
566
+ return Array.from(el.classList).find((className) => {
567
+ return (
568
+ className !== "column-body" &&
569
+ !className.endsWith("container") &&
570
+ className.startsWith("column-") &&
571
+ !className.endsWith("left")
572
+ );
573
+ });
574
+ });
575
+
576
+ const kOverlapPaddingSize = 10;
577
+ function toRegions(els) {
578
+ return els.map((el) => {
579
+ const boundRect = el.getBoundingClientRect();
580
+ const top =
581
+ boundRect.top +
582
+ document.documentElement.scrollTop -
583
+ kOverlapPaddingSize;
584
+ return {
585
+ top,
586
+ bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize,
587
+ };
588
+ });
589
+ }
590
+
591
+ let hasObserved = false;
592
+ const visibleItemObserver = (els) => {
593
+ let visibleElements = [...els];
594
+ const intersectionObserver = new IntersectionObserver(
595
+ (entries, _observer) => {
596
+ entries.forEach((entry) => {
597
+ if (entry.isIntersecting) {
598
+ if (visibleElements.indexOf(entry.target) === -1) {
599
+ visibleElements.push(entry.target);
600
+ }
601
+ } else {
602
+ visibleElements = visibleElements.filter((visibleEntry) => {
603
+ return visibleEntry !== entry;
604
+ });
605
+ }
606
+ });
607
+
608
+ if (!hasObserved) {
609
+ hideOverlappedSidebars();
610
+ }
611
+ hasObserved = true;
612
+ },
613
+ {}
614
+ );
615
+ els.forEach((el) => {
616
+ intersectionObserver.observe(el);
617
+ });
618
+
619
+ return {
620
+ getVisibleEntries: () => {
621
+ return visibleElements;
622
+ },
623
+ };
624
+ };
625
+
626
+ const rightElementObserver = visibleItemObserver(rightSideConflictEls);
627
+ const leftElementObserver = visibleItemObserver(leftSideConflictEls);
628
+
629
+ const hideOverlappedSidebars = () => {
630
+ marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries()));
631
+ sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries()));
632
+ if (tocLeftScrollVisibility) {
633
+ tocLeftScrollVisibility(
634
+ toRegions(leftElementObserver.getVisibleEntries())
635
+ );
636
+ }
637
+ };
638
+
639
+ window.quartoToggleReader = () => {
640
+ // Applies a slow class (or removes it)
641
+ // to update the transition speed
642
+ const slowTransition = (slow) => {
643
+ const manageTransition = (id, slow) => {
644
+ const el = document.getElementById(id);
645
+ if (el) {
646
+ if (slow) {
647
+ el.classList.add("slow");
648
+ } else {
649
+ el.classList.remove("slow");
650
+ }
651
+ }
652
+ };
653
+
654
+ manageTransition("TOC", slow);
655
+ manageTransition("quarto-sidebar", slow);
656
+ };
657
+ const readerMode = !isReaderMode();
658
+ setReaderModeValue(readerMode);
659
+
660
+ // If we're entering reader mode, slow the transition
661
+ if (readerMode) {
662
+ slowTransition(readerMode);
663
+ }
664
+ highlightReaderToggle(readerMode);
665
+ hideOverlappedSidebars();
666
+
667
+ // If we're exiting reader mode, restore the non-slow transition
668
+ if (!readerMode) {
669
+ slowTransition(!readerMode);
670
+ }
671
+ };
672
+
673
+ const highlightReaderToggle = (readerMode) => {
674
+ const els = document.querySelectorAll(".quarto-reader-toggle");
675
+ if (els) {
676
+ els.forEach((el) => {
677
+ if (readerMode) {
678
+ el.classList.add("reader");
679
+ } else {
680
+ el.classList.remove("reader");
681
+ }
682
+ });
683
+ }
684
+ };
685
+
686
+ const setReaderModeValue = (val) => {
687
+ if (window.location.protocol !== "file:") {
688
+ window.localStorage.setItem("quarto-reader-mode", val);
689
+ } else {
690
+ localReaderMode = val;
691
+ }
692
+ };
693
+
694
+ const isReaderMode = () => {
695
+ if (window.location.protocol !== "file:") {
696
+ return window.localStorage.getItem("quarto-reader-mode") === "true";
697
+ } else {
698
+ return localReaderMode;
699
+ }
700
+ };
701
+ let localReaderMode = null;
702
+
703
+ const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded");
704
+ const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1;
705
+
706
+ // Walk the TOC and collapse/expand nodes
707
+ // Nodes are expanded if:
708
+ // - they are top level
709
+ // - they have children that are 'active' links
710
+ // - they are directly below an link that is 'active'
711
+ const walk = (el, depth) => {
712
+ // Tick depth when we enter a UL
713
+ if (el.tagName === "UL") {
714
+ depth = depth + 1;
715
+ }
716
+
717
+ // It this is active link
718
+ let isActiveNode = false;
719
+ if (el.tagName === "A" && el.classList.contains("active")) {
720
+ isActiveNode = true;
721
+ }
722
+
723
+ // See if there is an active child to this element
724
+ let hasActiveChild = false;
725
+ for (child of el.children) {
726
+ hasActiveChild = walk(child, depth) || hasActiveChild;
727
+ }
728
+
729
+ // Process the collapse state if this is an UL
730
+ if (el.tagName === "UL") {
731
+ if (tocOpenDepth === -1 && depth > 1) {
732
+ el.classList.add("collapse");
733
+ } else if (
734
+ depth <= tocOpenDepth ||
735
+ hasActiveChild ||
736
+ prevSiblingIsActiveLink(el)
737
+ ) {
738
+ el.classList.remove("collapse");
739
+ } else {
740
+ el.classList.add("collapse");
741
+ }
742
+
743
+ // untick depth when we leave a UL
744
+ depth = depth - 1;
745
+ }
746
+ return hasActiveChild || isActiveNode;
747
+ };
748
+
749
+ // walk the TOC and expand / collapse any items that should be shown
750
+
751
+ if (tocEl) {
752
+ walk(tocEl, 0);
753
+ updateActiveLink();
754
+ }
755
+
756
+ // Throttle the scroll event and walk peridiocally
757
+ window.document.addEventListener(
758
+ "scroll",
759
+ throttle(() => {
760
+ if (tocEl) {
761
+ updateActiveLink();
762
+ walk(tocEl, 0);
763
+ }
764
+ if (!isReaderMode()) {
765
+ hideOverlappedSidebars();
766
+ }
767
+ }, 5)
768
+ );
769
+ window.addEventListener(
770
+ "resize",
771
+ throttle(() => {
772
+ if (!isReaderMode()) {
773
+ hideOverlappedSidebars();
774
+ }
775
+ }, 10)
776
+ );
777
+ hideOverlappedSidebars();
778
+ highlightReaderToggle(isReaderMode());
779
+ });
780
+
781
+ // grouped tabsets
782
+ window.addEventListener("pageshow", (_event) => {
783
+ function getTabSettings() {
784
+ const data = localStorage.getItem("quarto-persistent-tabsets-data");
785
+ if (!data) {
786
+ localStorage.setItem("quarto-persistent-tabsets-data", "{}");
787
+ return {};
788
+ }
789
+ if (data) {
790
+ return JSON.parse(data);
791
+ }
792
+ }
793
+
794
+ function setTabSettings(data) {
795
+ localStorage.setItem(
796
+ "quarto-persistent-tabsets-data",
797
+ JSON.stringify(data)
798
+ );
799
+ }
800
+
801
+ function setTabState(groupName, groupValue) {
802
+ const data = getTabSettings();
803
+ data[groupName] = groupValue;
804
+ setTabSettings(data);
805
+ }
806
+
807
+ function toggleTab(tab, active) {
808
+ const tabPanelId = tab.getAttribute("aria-controls");
809
+ const tabPanel = document.getElementById(tabPanelId);
810
+ if (active) {
811
+ tab.classList.add("active");
812
+ tabPanel.classList.add("active");
813
+ } else {
814
+ tab.classList.remove("active");
815
+ tabPanel.classList.remove("active");
816
+ }
817
+ }
818
+
819
+ function toggleAll(selectedGroup, selectorsToSync) {
820
+ for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) {
821
+ const active = selectedGroup === thisGroup;
822
+ for (const tab of tabs) {
823
+ toggleTab(tab, active);
824
+ }
825
+ }
826
+ }
827
+
828
+ function findSelectorsToSyncByLanguage() {
829
+ const result = {};
830
+ const tabs = Array.from(
831
+ document.querySelectorAll(`div[data-group] a[id^='tabset-']`)
832
+ );
833
+ for (const item of tabs) {
834
+ const div = item.parentElement.parentElement.parentElement;
835
+ const group = div.getAttribute("data-group");
836
+ if (!result[group]) {
837
+ result[group] = {};
838
+ }
839
+ const selectorsToSync = result[group];
840
+ const value = item.innerHTML;
841
+ if (!selectorsToSync[value]) {
842
+ selectorsToSync[value] = [];
843
+ }
844
+ selectorsToSync[value].push(item);
845
+ }
846
+ return result;
847
+ }
848
+
849
+ function setupSelectorSync() {
850
+ const selectorsToSync = findSelectorsToSyncByLanguage();
851
+ Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => {
852
+ Object.entries(tabSetsByValue).forEach(([value, items]) => {
853
+ items.forEach((item) => {
854
+ item.addEventListener("click", (_event) => {
855
+ setTabState(group, value);
856
+ toggleAll(value, selectorsToSync[group]);
857
+ });
858
+ });
859
+ });
860
+ });
861
+ return selectorsToSync;
862
+ }
863
+
864
+ const selectorsToSync = setupSelectorSync();
865
+ for (const [group, selectedName] of Object.entries(getTabSettings())) {
866
+ const selectors = selectorsToSync[group];
867
+ // it's possible that stale state gives us empty selections, so we explicitly check here.
868
+ if (selectors) {
869
+ toggleAll(selectedName, selectors);
870
+ }
871
+ }
872
+ });
873
+
874
+ function throttle(func, wait) {
875
+ let waiting = false;
876
+ return function () {
877
+ if (!waiting) {
878
+ func.apply(this, arguments);
879
+ waiting = true;
880
+ setTimeout(function () {
881
+ waiting = false;
882
+ }, wait);
883
+ }
884
+ };
885
+ }
886
+
887
+ function nexttick(func) {
888
+ return setTimeout(func, 0);
889
+ }