@pimaonline/pimaonline-themepack 2.4.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE.md +30 -0
  2. package/README.md +69 -111
  3. package/dist/css/main.css +1 -1
  4. package/dist/css/themes/ecn/styles.css +1 -0
  5. package/dist/css/themes/eng/styles.css +1 -0
  6. package/dist/css/themes/fsc/styles.css +1 -0
  7. package/dist/css/themes/lang/styles.css +1 -1
  8. package/dist/css/themes/ss/styles.css +1 -0
  9. package/dist/img/theme-images/ecn/arrow-2.svg +4 -0
  10. package/dist/img/theme-images/ecn/arrow.svg +4 -0
  11. package/dist/img/theme-images/ecn/point.svg +3 -0
  12. package/dist/img/theme-images/eng/button-bkg.svg +178 -0
  13. package/dist/img/theme-images/eng/halftone-banner.svg +1 -0
  14. package/dist/img/theme-images/eng/halftone.svg +177 -0
  15. package/dist/img/theme-images/eng/long-button-bkg.svg +353 -0
  16. package/dist/img/theme-images/fsc/bottomwave-pinkred.svg +17 -0
  17. package/dist/img/theme-images/fsc/bottomwave-redorange.svg +17 -0
  18. package/dist/img/theme-images/fsc/bottomwave-yellow.svg +17 -0
  19. package/dist/img/theme-images/fsc/bottomwave-yelloworange.svg +17 -0
  20. package/dist/img/theme-images/fsc/fire-icon.png +0 -0
  21. package/dist/img/theme-images/fsc/mainwave-pinkred.svg +17 -0
  22. package/dist/img/theme-images/fsc/mainwave-redorange.svg +17 -0
  23. package/dist/img/theme-images/fsc/mainwave-yellow.svg +17 -0
  24. package/dist/img/theme-images/fsc/mainwave-yelloworange.svg +17 -0
  25. package/dist/img/theme-images/lang/country-flags/argentina_flag.png +0 -0
  26. package/dist/img/theme-images/lang/country-flags/bolivia_flag.png +0 -0
  27. package/dist/img/theme-images/lang/country-flags/chile_flag.png +0 -0
  28. package/dist/img/theme-images/lang/country-flags/colombia_flag.png +0 -0
  29. package/dist/img/theme-images/lang/country-flags/costa-rica_flag.png +0 -0
  30. package/dist/img/theme-images/lang/country-flags/cuba_flag.png +0 -0
  31. package/dist/img/theme-images/lang/country-flags/dominican-republic_flag.png +0 -0
  32. package/dist/img/theme-images/lang/country-flags/ecuador_flag.png +0 -0
  33. package/dist/img/theme-images/lang/country-flags/el-salvador_flag.png +0 -0
  34. package/dist/img/theme-images/lang/country-flags/guatemala_flag.png +0 -0
  35. package/dist/img/theme-images/lang/country-flags/honduras_flag.png +0 -0
  36. package/dist/img/theme-images/lang/country-flags/mexico_flag.png +0 -0
  37. package/dist/img/theme-images/lang/country-flags/nicaragua_flag.png +0 -0
  38. package/dist/img/theme-images/lang/country-flags/panama_flag.png +0 -0
  39. package/dist/img/theme-images/lang/country-flags/paraguay_flag.png +0 -0
  40. package/dist/img/theme-images/lang/country-flags/peru_flag.png +0 -0
  41. package/dist/img/theme-images/lang/country-flags/puerto-rice_flag.png +0 -0
  42. package/dist/img/theme-images/lang/country-flags/spain_flag.png +0 -0
  43. package/dist/img/theme-images/lang/country-flags/uruguay_flag.png +0 -0
  44. package/dist/img/theme-images/lang/country-flags/venezuela_flag.png +0 -0
  45. package/dist/img/theme-images/music/half_note.svg +5 -5
  46. package/dist/img/theme-images/resort/flourish-left.svg +32 -32
  47. package/dist/img/theme-images/resort/flourish-main.svg +37 -37
  48. package/dist/img/theme-images/resort/flourish-right.svg +31 -31
  49. package/dist/img/theme-images/resort/separator.svg +15 -15
  50. package/dist/img/theme-images/ss/blockquote.svg +3 -0
  51. package/dist/img/theme-images/ss/list-style.svg +3 -0
  52. package/dist/img/theme-images/ss/main-large-blob.svg +3 -0
  53. package/dist/img/theme-images/ss/main-small-blob.svg +3 -0
  54. package/dist/img/theme-images/ss/small-blob.svg +3 -0
  55. package/dist/img/theme-images/ss/tall-blob.svg +3 -0
  56. package/dist/img/theme-images/widgets/separator.svg +17 -17
  57. package/dist/js/jumpTo.js +3 -3
  58. package/dist/js/scripts.js +326 -326
  59. package/dist/js/scripts2.js +541 -361
  60. package/dist/plugins/fancybox/fancybox-example.html +51 -51
  61. package/dist/plugins/fancybox/fancybox.css +72 -72
  62. package/dist/plugins/fancybox/helpers/jquery.fancybox-buttons.css +97 -97
  63. package/dist/plugins/fancybox/helpers/jquery.fancybox-buttons.js +122 -122
  64. package/dist/plugins/fancybox/helpers/jquery.fancybox-media.js +201 -201
  65. package/dist/plugins/fancybox/helpers/jquery.fancybox-thumbs.css +54 -54
  66. package/dist/plugins/fancybox/helpers/jquery.fancybox-thumbs.js +165 -165
  67. package/dist/plugins/fancybox/jquery.fancybox.css +274 -274
  68. package/dist/plugins/fancybox/jquery.fancybox.js +2018 -2018
  69. package/dist/plugins/fancybox/jquery.fancybox.pack.js +46 -46
  70. package/dist/plugins/flashcards/README.md +135 -135
  71. package/dist/plugins/flashcards/config.rb +24 -24
  72. package/dist/plugins/flashcards/css/style.css +215 -215
  73. package/dist/plugins/flashcards/flashcards-example.html +65 -65
  74. package/dist/plugins/flashcards/index.html +90 -90
  75. package/dist/plugins/flashcards/js/flash_cards.min.js +11 -11
  76. package/dist/plugins/flashcards/js/plugins/flash_cards.js +62 -62
  77. package/dist/plugins/flashcards/js/plugins/jquery.cycle.js +1147 -1147
  78. package/dist/plugins/flashcards/js/vendor/jquery-1.7.2.js +9404 -9404
  79. package/dist/plugins/flashcards/js/vendor/jquery-1.7.2.min.js +3 -3
  80. package/dist/plugins/flashcards/js/vendor/modernizr-2.5.3.min.js +3 -3
  81. package/dist/plugins/flashcards/resources/fonts/flash_cards/flash_cards.svg +20 -20
  82. package/dist/plugins/floating-particles/floating-particles.js +67 -67
  83. package/dist/plugins/font-awesome-icons/webfonts/fa-brands-400.svg +3570 -3570
  84. package/dist/plugins/font-awesome-icons/webfonts/fa-regular-400.svg +803 -803
  85. package/dist/plugins/font-awesome-icons/webfonts/fa-solid-900.svg +4700 -4700
  86. package/dist/plugins/global-homepage-overrides/global-homepage-overrides.css +539 -539
  87. package/dist/plugins/global-homepage-overrides/global-homepage-overrides.html +18 -18
  88. package/dist/plugins/global-homepage-overrides/global-homepage-overrides.js +52 -52
  89. package/dist/plugins/preview-banner/preview-banner.css +125 -125
  90. package/dist/plugins/preview-banner/preview-banner.html +17 -17
  91. package/dist/plugins/preview-banner/preview-banner.js +56 -56
  92. package/package.json +39 -40
@@ -1,361 +1,541 @@
1
- /// @description Main JS file for PimaOnline Themepack
2
- /// @dependencies jQuery 3.3.1 or later
3
-
4
- const courseBody = document.querySelector("body");
5
- const contentWrapper = document.querySelector("#content-wrapper");
6
- const secondColumn = document.querySelector("#second-column");
7
- const thirdColumn = document.querySelector("#third-column");
8
- const columnWidget = document.querySelector("#column-widget");
9
- const videoWrapper = document.querySelector("#video-wrapper");
10
- const rolePres = document.querySelectorAll('[role="presentation"]');
11
- const imageGallery = document.querySelector(".image-gallery");
12
- const vocabListWidget = document.querySelector("dl.vocab-list");
13
- const vocabTerms = document.querySelectorAll("dl.vocab-list dt");
14
- const vocabDefs = document.querySelectorAll("dl.vocab-list dd");
15
- const vocabCloseBtns = document.querySelectorAll("dl.vocab-list button");
16
- const vocabLists = document.querySelectorAll("dl[class^='vocab-list']");
17
- const mediaContainers = document.querySelectorAll(".media-container");
18
- const tabsWidgets = document.querySelectorAll(".tabs");
19
- const h5pIframes = document.querySelectorAll("iframe");
20
- const docHead = document.querySelector("head");
21
- const h5pResizerExists = docHead.querySelector("script[src='https://pima.h5p.com/js/h5p-resizer.js']");
22
- const h5pResizer = document.createElement("script");
23
-
24
- // Grid
25
- const addGrid = () => {
26
- if (secondColumn && thirdColumn) {
27
- courseBody.id = "three-column";
28
- } else if (secondColumn && !columnWidget) {
29
- courseBody.id = "two-column";
30
- } else if (columnWidget) {
31
- courseBody.id = "two-col-widget";
32
- } else if (videoWrapper) {
33
- courseBody.id = "video-grid";
34
- } else {
35
- courseBody.id = "one-column";
36
- }
37
- };
38
- addGrid();
39
-
40
- // JS to add role and aria-label to content-wrapper, second-column, and third-column
41
- const addAria = () => {
42
- contentWrapper.setAttribute("role", "main");
43
- if (secondColumn) {
44
- secondColumn.setAttribute("role", "region");
45
- secondColumn.setAttribute("aria-label", "Second column");
46
- }
47
- if (thirdColumn) {
48
- thirdColumn.setAttribute("role", "region");
49
- thirdColumn.setAttribute("aria-label", "Third column");
50
- }
51
- };
52
- addAria();
53
-
54
- // Clean up HTML
55
- const cleanMarkup = () => {
56
- // Remove role="presentation" attr from any element that has it
57
- if (rolePres) {
58
- rolePres.forEach((roleElem) => roleElem.removeAttribute("role"));
59
- }
60
- // Set functino to remove atrributes from elements
61
- const discardAttributes = (element, ...attributes) => {
62
- attributes.forEach((attribute) => element.removeAttribute(attribute));
63
- }
64
- // Remove attributes from tables
65
- const tableElems = document.querySelectorAll("table, thead, tbody, tr, th, td");
66
- tableElems.forEach((elem) => {
67
- discardAttributes(elem, "cellspacing", "cellpadding", "width", "style");
68
- });
69
- };
70
- cleanMarkup();
71
-
72
- // Helper JS for Responsive Tables
73
- const initResponsiveTables = () => {
74
- const tables = document.querySelectorAll(".display, .display-lg")
75
- for (let table = 0; table < tables.length; table++) {
76
- let headertext = [],
77
- headers = tables[table].querySelectorAll(".display table th, table.display th, .display-lg table th, table.display-lg th"),
78
- tablebody = tables[table].querySelector(".display table tbody, table.display tbody, .display-lg table tbody, table.display-lg tbody");
79
- for (let header = 0; header < headers.length; header++) {
80
- let current = headers[header];
81
- headertext.push(current.textContent.replace(/\r?\n|\r/, ""));
82
- }
83
- for (let y = 0, row; row = tablebody.rows[y]; y++) {
84
- for (let j = 0, col; col = row.cells[j]; j++) {
85
- col.setAttribute("data-th", headertext[j]);
86
- }
87
- }
88
- }
89
- }
90
- initResponsiveTables();
91
-
92
- // This is called by anchor links via onlick="" in the HTML
93
- // Added because default anchor links don't work on all browsers using D2L
94
- const jumpTo = (anchor) => {
95
- document.getElementById(anchor).scrollIntoView();
96
- }
97
-
98
- // Image gallery
99
- const callImageGallery = () => {
100
- // Create link element with font-awesome cdn and append it to the <head>
101
- const docHead = document.querySelector("head");
102
- const fontAwesomeCdn = document.createElement("link");
103
- fontAwesomeCdn.setAttribute("rel", "stylesheet");
104
- fontAwesomeCdn.setAttribute("href", "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css");
105
- docHead.appendChild(fontAwesomeCdn);
106
- // Begin image gallery
107
- const imgGalleries = document.querySelectorAll(".image-gallery"),
108
- imgBoxes = document.querySelectorAll(".image-box"),
109
- modalBoxContent = `<div class="modal-box invisible">
110
- <div class="gallery-overlay"></div>
111
- <figure class="modal-box--image"><i class="fa-solid fa-x close-img"></i> <img src="#" alt="image here" /><figcaption class="img-caption"></figcaption></figure>
112
- </div>
113
- <button class="hide-gallery">Hide</button>`;
114
-
115
- // createModalBox.innerHTML = modalBoxContent;
116
- for (let imgGallery = 0; imgGallery < imgGalleries.length; imgGallery++) {
117
- imgGalleries[imgGallery].insertAdjacentHTML("afterbegin", modalBoxContent);
118
- }
119
-
120
- if (document.querySelector(".modal-box")) {
121
- const overlay = document.querySelector(".gallery-overlay"),
122
- modalBox = document.querySelector(".modal-box"),
123
- modalImg = document.querySelector(".modal-box--image img"),
124
- modalCaption = document.querySelector(".img-caption"),
125
- closeImg = document.querySelector(".close-img");
126
-
127
- for (let imgBox = 0; imgBox < imgBoxes.length; imgBox++) {
128
- imgBoxes[imgBox].onclick = function () {
129
- modalBox.classList.remove("invisible");
130
- let imgSrc = this.querySelector("img").src;
131
- modalImg.src = imgSrc;
132
- let imgCaption = this.querySelector("img").alt;
133
- modalCaption.innerHTML = imgCaption;
134
- }
135
-
136
- // Make images tab-able
137
- imgBoxes[imgBox].setAttribute("tabindex", "0");
138
- imgBoxes[imgBox].addEventListener("keydown", function (enter) {
139
- if (enter.key === "Enter") {
140
- modalBox.classList.remove("invisible");
141
- let imgSrc = this.querySelector("img").src;
142
- modalImg.src = imgSrc;
143
- let imgCaption = this.querySelector("img").alt;
144
- modalCaption.innerHTML = imgCaption;
145
- }
146
- })
147
- }
148
- overlay.onclick = () => {
149
- modalBox.classList.add("invisible");
150
- }
151
- window.onkeydown = (esc) => {
152
- if (esc.keyCode === 27) {
153
- modalBox.classList.add("invisible");
154
- }
155
- }
156
-
157
- closeImg.onclick = () => {
158
- modalBox.classList.add("invisible");
159
- }
160
- const hideGalleries = document.querySelectorAll(".hide-gallery");
161
- for (let hideGallery = 0; hideGallery < hideGalleries.length; hideGallery++) {
162
- hideGalleries[hideGallery].addEventListener("click", () => {
163
- hideGalleries[hideGallery].nextElementSibling.classList.toggle("invisible");
164
- if (hideGalleries[hideGallery].innerHTML === "Hide") {
165
- hideGalleries[hideGallery].innerHTML = "Show";
166
- } else {
167
- hideGalleries[hideGallery].innerHTML = "Hide";
168
- }
169
- });
170
- }
171
- };
172
- }
173
- if (imageGallery) {
174
- callImageGallery();
175
- }
176
-
177
- // Vocab list widget
178
- const callVocabList = () => {
179
- if (vocabCloseBtns) {
180
- for(let btn = 0; btn < vocabCloseBtns.length; btn++) {
181
- vocabCloseBtns[btn].addEventListener("click", () => {
182
- for(let node = 0; node < vocabLists[btn].children.length;node++) {
183
- if(vocabLists[btn].children[node].tagName == "DD") {
184
- vocabLists[btn].children[node].removeAttribute("style");
185
- }
186
- if(vocabLists[btn].children[node].tagName == "DT") {
187
- vocabLists[btn].children[node].removeAttribute("class");
188
- }
189
- }});
190
- }
191
- }
192
- for (let activeTerm = 0; activeTerm < vocabTerms.length; activeTerm++) {
193
- vocabTerms[activeTerm].addEventListener("click", function() {
194
- this.classList.toggle("active");
195
- let termPanel = this.nextElementSibling;
196
- if (termPanel.style.display === "block") { termPanel.removeAttribute("style"); }
197
- else { termPanel.style.display = "block"; }
198
- });
199
-
200
- vocabTerms[activeTerm].addEventListener("keydown", function(e) {
201
- if(e.key === "Enter") {
202
- this.classList.toggle("active");
203
- let termPanel = this.nextElementSibling;
204
- if (termPanel.style.display === "block") { termPanel.removeAttribute("style"); }
205
- else { termPanel.style.display = "block"; }
206
- }
207
- });
208
- }
209
- }
210
- if (vocabListWidget) {callVocabList();}
211
-
212
- // Media Container
213
- const addMediaContainersAria = () => {
214
- mediaContainers.forEach((eachContainer, index) => {
215
- // loopID: find the current index value, convert it to its letter equivalent, then convert to lowercase
216
- let loopId = String.fromCharCode(index + 65).toLowerCase();
217
- let mediaObject = eachContainer.querySelector(".media-object");
218
- let iframe = mediaObject.firstElementChild;
219
- let mediaInfo = mediaObject.nextElementSibling;
220
-
221
- // If element DOES NOT have "aria-describedby" && it DOES have a sibling element.
222
- if (!iframe.hasAttribute("aria-describedby") && mediaInfo != null) {
223
- iframe.setAttribute("aria-describedby", `${loopId}`);
224
- mediaInfo.id = `${[loopId]}`;
225
- }
226
- });
227
- }
228
- if (mediaContainers) {addMediaContainersAria();}
229
-
230
- //Tabs Widget
231
- const callTabsWidget = () => {
232
-
233
- let tabsWidgetsNum = 0;
234
-
235
- tabsWidgets.forEach((tab,index) => {
236
- let tabInputs = tab.querySelectorAll("input")
237
- let tabLabels = tab.querySelectorAll("label")
238
- let tabDivs = tab.querySelectorAll("div")
239
-
240
- let groupNum = index + 1;
241
-
242
- //Add region and aria-label to parent div
243
- tab.setAttribute("role", "region");
244
- tab.setAttribute("aria-label", `tab group ${groupNum}`)
245
-
246
- for(tabIndex = 0; tabIndex < tabInputs.length; tabIndex++) {
247
-
248
- let tabNum = tabsWidgetsNum + 1;
249
-
250
- //Add class, id, name, and aria-described by for inputs
251
- tabInputs[tabIndex].classList.add("tab-input");
252
- tabInputs[tabIndex].setAttribute("type", "radio")
253
- tabInputs[tabIndex].setAttribute("id", `tab${tabNum}`);
254
- tabInputs[tabIndex].setAttribute("name", `hint-group-${groupNum}` )
255
- tabInputs[tabIndex].setAttribute("aria-describedby", `tabHeading${tabNum}`)
256
- //Add class and for for labels
257
- tabLabels[tabIndex].classList.add("tab-header");
258
- tabLabels[tabIndex].setAttribute("for", `tab${tabNum}`)
259
- //Add class, tabindex, and id for divs
260
- tabDivs[tabIndex].classList.add("tab-panel")
261
- tabDivs[tabIndex].setAttribute("tabindex", 0)
262
- tabDivs[tabIndex].setAttribute("id", `tabHeading${tabNum}`)
263
- //Add attributes for hide tab
264
- if(tabIndex + 1 == tabInputs.length) {
265
- tabLabels[tabIndex].classList.add("hide-tab")
266
- tabInputs[tabIndex].checked = true;
267
- tabDivs[tabIndex].classList.add("hide-panel")
268
- }
269
- tabsWidgetsNum++;
270
- }
271
- })
272
- }
273
- if (tabsWidgets) {callTabsWidget();}
274
-
275
- // Toggle footnotes
276
- const toggleBtns = document.querySelectorAll(".toggle-btn, .toggle-footnotes");
277
-
278
- if (document.querySelector(".toggle-btn") || document.querySelector(".toggle-footnotes")) {
279
- for (let toggleBtn = 0; toggleBtn < toggleBtns.length; toggleBtn++) {
280
- // Add tabindex
281
- toggleBtns[toggleBtn].setAttribute("tabindex", "0");
282
-
283
- // Show/hide on click
284
- toggleBtns[toggleBtn].addEventListener("click", () => {
285
- toggleBtns[toggleBtn].nextElementSibling.classList.toggle("show");
286
- })
287
-
288
- // Show/hide on enter for users who use tab
289
- toggleBtns[toggleBtn].addEventListener("keydown", (enter) => {
290
- if (enter.keyCode === 13) {
291
- toggleBtns[toggleBtn].nextElementSibling.classList.toggle("show");
292
- }
293
- })
294
- }
295
- }
296
-
297
- // Change footnotes from 'show' to 'hide'
298
- const footnotes = document.querySelector(".toggle-footnotes");
299
-
300
- if(footnotes) {
301
- footnotes.addEventListener("click", () => {
302
- footnotes.innerHTML = (footnotes.innerHTML === "[Show Footnotes]") ? "[Hide Footnotes]" : "[Show Footnotes]";
303
- })
304
- }
305
-
306
- // Animated border for HRS theme
307
- const hrsBorders = document.querySelectorAll(".hrs-border");
308
-
309
- if(hrsBorders) {
310
- for (let hrsBorder = 0; hrsBorder < hrsBorders.length; hrsBorder++) {
311
- const callAnimateBorder = new IntersectionObserver(entries => {
312
- // Loop over the entries
313
- entries.forEach(entry => {
314
- // If the element is visible
315
- if (entry.isIntersecting) {
316
- // Add the animation class
317
- entry.target.classList.add('animate-border');
318
- }
319
- });
320
- });
321
- callAnimateBorder.observe(hrsBorders[hrsBorder]);
322
- }
323
- };
324
-
325
- // Add h5p resizer dynamically
326
- h5pResizer.setAttribute("src", "https://pima.h5p.com/js/h5p-resizer.js");
327
- h5pResizer.setAttribute("charset", "UTF-8");
328
- h5pResizer.setAttribute("defer", "");
329
-
330
- h5pIframes.forEach(function(h5pIframe) {
331
- const src = h5pIframe.getAttribute("src");
332
- if (src.includes("/d2l/common/dialogs/quickLink") || src.includes("https://pima.h5p.com/content") && !h5pResizerExists) {
333
- docHead.appendChild(h5pResizer);
334
- }
335
- });
336
-
337
- // Call function with jQuery scripts
338
- const callJquery = () => {
339
- // Toggle Button's Arrow Right Points Down on Click
340
- $('.arrow-right').on('click', function () {
341
- $(this).toggleClass('arrow-down');
342
- });
343
- // TOOLTIP
344
- // Allows Screen readers to toggle a tooltip on click and to say if the tooltip is collapsed or expanded.
345
- $(".tooltip").click(function () {
346
- $(this).children(".tip-hover").toggle();
347
- if ($(this).children(".tip-hover").is(':visible')) {
348
- $(this).attr('aria-expanded', 'true');
349
- $(this).removeClass('hidden');
350
- } else {
351
- $(this).attr('aria-expanded', 'false');
352
- $(this).addClass('hidden');
353
- }
354
- });
355
- let start = 999;
356
- $('.tooltip').each(function (i) {
357
- $(this).css('z-index', start--);
358
- });
359
- $(".tooltip .video-container").parent().css("width", "450px");
360
- }
361
- callJquery();
1
+ /// @description Main JS file for PimaOnline Themepack
2
+ /// @dependencies jQuery 3.3.1 or later
3
+
4
+ const courseBody = document.querySelector("body");
5
+ const contentWrapper = document.querySelector("#content-wrapper");
6
+ const secondColumn = document.querySelector("#second-column");
7
+ const thirdColumn = document.querySelector("#third-column");
8
+ const columnWidget = document.querySelector("#column-widget");
9
+ const videoWrapper = document.querySelector("#video-wrapper");
10
+ const rolePres = document.querySelectorAll('[role="presentation"]');
11
+ const imageGallery = document.querySelector(".image-gallery");
12
+ const vocabListWidget = document.querySelector("dl.vocab-list");
13
+ const vocabTerms = document.querySelectorAll("dl.vocab-list dt");
14
+ const vocabDefs = document.querySelectorAll("dl.vocab-list dd");
15
+ const vocabCloseBtns = document.querySelectorAll("dl.vocab-list button");
16
+ const vocabLists = document.querySelectorAll("dl[class^='vocab-list']");
17
+ const mediaContainers = document.querySelectorAll(".media-container");
18
+ const tabsWidgets = document.querySelectorAll(".tabs");
19
+ const h5pIframes = document.querySelectorAll("iframe");
20
+ const docHead = document.querySelector("head");
21
+ const h5pResizerExists = docHead.querySelector("script[src='https://pima.h5p.com/js/h5p-resizer.js']");
22
+ const h5pResizer = document.createElement("script");
23
+ const focusReaderTooltipText = "Highlight text as you scroll";
24
+
25
+ // Grid
26
+ const addGrid = () => {
27
+ if (secondColumn && thirdColumn) {
28
+ courseBody.id = "three-column";
29
+ } else if (secondColumn && !columnWidget) {
30
+ courseBody.id = "two-column";
31
+ } else if (columnWidget) {
32
+ courseBody.id = "two-col-widget";
33
+ } else if (videoWrapper) {
34
+ courseBody.id = "video-grid";
35
+ } else {
36
+ courseBody.id = "one-column";
37
+ }
38
+ };
39
+ addGrid();
40
+
41
+ // JS to add role and aria-label to content-wrapper, second-column, and third-column
42
+ const addAria = () => {
43
+ contentWrapper.setAttribute("role", "main");
44
+ if (secondColumn) {
45
+ secondColumn.setAttribute("role", "region");
46
+ secondColumn.setAttribute("aria-label", "Second column");
47
+ }
48
+ if (thirdColumn) {
49
+ thirdColumn.setAttribute("role", "region");
50
+ thirdColumn.setAttribute("aria-label", "Third column");
51
+ }
52
+ };
53
+ addAria();
54
+
55
+ // Clean up HTML
56
+ const cleanMarkup = () => {
57
+ // Remove role="presentation" attr from any element that has it
58
+ if (rolePres) {
59
+ rolePres.forEach((roleElem) => roleElem.removeAttribute("role"));
60
+ }
61
+ // Set functino to remove atrributes from elements
62
+ const discardAttributes = (element, ...attributes) => {
63
+ attributes.forEach((attribute) => element.removeAttribute(attribute));
64
+ }
65
+ // Remove attributes from tables
66
+ const tableElems = document.querySelectorAll("table, thead, tbody, tr, th, td");
67
+ tableElems.forEach((elem) => {
68
+ discardAttributes(elem, "cellspacing", "cellpadding", "width", "style");
69
+ });
70
+ };
71
+ cleanMarkup();
72
+
73
+ // Helper JS for Responsive Tables
74
+ const initResponsiveTables = () => {
75
+ const tables = document.querySelectorAll(".display, .display-lg")
76
+ for (let table = 0; table < tables.length; table++) {
77
+ let headertext = [],
78
+ headers = tables[table].querySelectorAll(".display table th, table.display th, .display-lg table th, table.display-lg th"),
79
+ tablebody = tables[table].querySelector(".display table tbody, table.display tbody, .display-lg table tbody, table.display-lg tbody");
80
+ for (let header = 0; header < headers.length; header++) {
81
+ let current = headers[header];
82
+ headertext.push(current.textContent.replace(/\r?\n|\r/, ""));
83
+ }
84
+ for (let y = 0, row; row = tablebody.rows[y]; y++) {
85
+ for (let j = 0, col; col = row.cells[j]; j++) {
86
+ col.setAttribute("data-th", headertext[j]);
87
+ }
88
+ }
89
+ }
90
+ }
91
+ initResponsiveTables();
92
+
93
+ // This is called by anchor links via onlick="" in the HTML
94
+ // Added because default anchor links don't work on all browsers using D2L
95
+ const jumpTo = (anchor) => {
96
+ document.getElementById(anchor).scrollIntoView();
97
+ }
98
+
99
+ // Image gallery
100
+ const callImageGallery = () => {
101
+ // Create link element with font-awesome cdn and append it to the <head>
102
+ const docHead = document.querySelector("head");
103
+ const fontAwesomeCdn = document.createElement("link");
104
+ fontAwesomeCdn.setAttribute("rel", "stylesheet");
105
+ fontAwesomeCdn.setAttribute("href", "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css");
106
+ docHead.appendChild(fontAwesomeCdn);
107
+ // Begin image gallery
108
+ const imgGalleries = document.querySelectorAll(".image-gallery"),
109
+ imgBoxes = document.querySelectorAll(".image-box"),
110
+ modalBoxContent = `<div class="modal-box invisible">
111
+ <div class="gallery-overlay"></div>
112
+ <figure class="modal-box--image"><i class="fa-solid fa-x close-img"></i> <img src="#" alt="image here" /><figcaption class="img-caption"></figcaption></figure>
113
+ </div>
114
+ <button class="hide-gallery">Hide</button>`;
115
+
116
+ // createModalBox.innerHTML = modalBoxContent;
117
+ for (let imgGallery = 0; imgGallery < imgGalleries.length; imgGallery++) {
118
+ imgGalleries[imgGallery].insertAdjacentHTML("afterbegin", modalBoxContent);
119
+ }
120
+
121
+ if (document.querySelector(".modal-box")) {
122
+ const overlay = document.querySelector(".gallery-overlay"),
123
+ modalBox = document.querySelector(".modal-box"),
124
+ modalImg = document.querySelector(".modal-box--image img"),
125
+ modalCaption = document.querySelector(".img-caption"),
126
+ closeImg = document.querySelector(".close-img");
127
+
128
+ for (let imgBox = 0; imgBox < imgBoxes.length; imgBox++) {
129
+ imgBoxes[imgBox].onclick = function () {
130
+ modalBox.classList.remove("invisible");
131
+ let imgSrc = this.querySelector("img").src;
132
+ modalImg.src = imgSrc;
133
+ let imgCaption = this.querySelector("img").alt;
134
+ modalCaption.innerHTML = imgCaption;
135
+ }
136
+
137
+ // Make images tab-able
138
+ imgBoxes[imgBox].setAttribute("tabindex", "0");
139
+ imgBoxes[imgBox].addEventListener("keydown", function (enter) {
140
+ if (enter.key === "Enter") {
141
+ modalBox.classList.remove("invisible");
142
+ let imgSrc = this.querySelector("img").src;
143
+ modalImg.src = imgSrc;
144
+ let imgCaption = this.querySelector("img").alt;
145
+ modalCaption.innerHTML = imgCaption;
146
+ }
147
+ })
148
+ }
149
+ overlay.onclick = () => {
150
+ modalBox.classList.add("invisible");
151
+ }
152
+ window.onkeydown = (esc) => {
153
+ if (esc.keyCode === 27) {
154
+ modalBox.classList.add("invisible");
155
+ }
156
+ }
157
+
158
+ closeImg.onclick = () => {
159
+ modalBox.classList.add("invisible");
160
+ }
161
+ const hideGalleries = document.querySelectorAll(".hide-gallery");
162
+ for (let hideGallery = 0; hideGallery < hideGalleries.length; hideGallery++) {
163
+ hideGalleries[hideGallery].addEventListener("click", () => {
164
+ hideGalleries[hideGallery].nextElementSibling.classList.toggle("invisible");
165
+ if (hideGalleries[hideGallery].innerHTML === "Hide") {
166
+ hideGalleries[hideGallery].innerHTML = "Show";
167
+ } else {
168
+ hideGalleries[hideGallery].innerHTML = "Hide";
169
+ }
170
+ });
171
+ }
172
+ };
173
+ }
174
+ if (imageGallery) {
175
+ callImageGallery();
176
+ }
177
+
178
+ // Vocab list widget
179
+ const callVocabList = () => {
180
+ if (vocabCloseBtns) {
181
+ for (let btn = 0; btn < vocabCloseBtns.length; btn++) {
182
+ vocabCloseBtns[btn].addEventListener("click", () => {
183
+ for (let node = 0; node < vocabLists[btn].children.length; node++) {
184
+ if (vocabLists[btn].children[node].tagName == "DD") {
185
+ vocabLists[btn].children[node].removeAttribute("style");
186
+ }
187
+ if (vocabLists[btn].children[node].tagName == "DT") {
188
+ vocabLists[btn].children[node].removeAttribute("class");
189
+ }
190
+ }
191
+ });
192
+ }
193
+ }
194
+ for (let activeTerm = 0; activeTerm < vocabTerms.length; activeTerm++) {
195
+ vocabTerms[activeTerm].addEventListener("click", function () {
196
+ this.classList.toggle("active");
197
+ let termPanel = this.nextElementSibling;
198
+ if (termPanel.style.display === "block") { termPanel.removeAttribute("style"); }
199
+ else { termPanel.style.display = "block"; }
200
+ });
201
+
202
+ vocabTerms[activeTerm].addEventListener("keydown", function (e) {
203
+ if (e.key === "Enter") {
204
+ this.classList.toggle("active");
205
+ let termPanel = this.nextElementSibling;
206
+ if (termPanel.style.display === "block") { termPanel.removeAttribute("style"); }
207
+ else { termPanel.style.display = "block"; }
208
+ }
209
+ });
210
+ }
211
+ }
212
+ if (vocabListWidget) { callVocabList(); }
213
+
214
+ // Media Container
215
+ const addMediaContainersAria = () => {
216
+ mediaContainers.forEach((eachContainer, index) => {
217
+ // loopID: find the current index value, convert it to its letter equivalent, then convert to lowercase
218
+ let loopId = String.fromCharCode(index + 65).toLowerCase();
219
+ let mediaObject = eachContainer.querySelector(".media-object");
220
+ let iframe = mediaObject.firstElementChild;
221
+ let mediaInfo = mediaObject.nextElementSibling;
222
+
223
+ // If element DOES NOT have "aria-describedby" && it DOES have a sibling element.
224
+ if (!iframe.hasAttribute("aria-describedby") && mediaInfo != null) {
225
+ iframe.setAttribute("aria-describedby", `${loopId}`);
226
+ mediaInfo.id = `${[loopId]}`;
227
+ }
228
+ });
229
+ }
230
+ if (mediaContainers) { addMediaContainersAria(); }
231
+
232
+ //Tabs Widget
233
+ const callTabsWidget = () => {
234
+
235
+ let tabsWidgetsNum = 0;
236
+
237
+ tabsWidgets.forEach((tab, index) => {
238
+ let tabInputs = tab.querySelectorAll("input")
239
+ let tabLabels = tab.querySelectorAll("label")
240
+ let tabDivs = tab.querySelectorAll("div")
241
+
242
+ let groupNum = index + 1;
243
+
244
+ //Add region and aria-label to parent div
245
+ tab.setAttribute("role", "region");
246
+ tab.setAttribute("aria-label", `tab group ${groupNum}`)
247
+
248
+ for (tabIndex = 0; tabIndex < tabInputs.length; tabIndex++) {
249
+
250
+ let tabNum = tabsWidgetsNum + 1;
251
+
252
+ //Add class, id, name, and aria-described by for inputs
253
+ tabInputs[tabIndex].classList.add("tab-input");
254
+ tabInputs[tabIndex].setAttribute("type", "radio")
255
+ tabInputs[tabIndex].setAttribute("id", `tab${tabNum}`);
256
+ tabInputs[tabIndex].setAttribute("name", `hint-group-${groupNum}`)
257
+ tabInputs[tabIndex].setAttribute("aria-describedby", `tabHeading${tabNum}`)
258
+ //Add class and for for labels
259
+ tabLabels[tabIndex].classList.add("tab-header");
260
+ tabLabels[tabIndex].setAttribute("for", `tab${tabNum}`)
261
+ //Add class, tabindex, and id for divs
262
+ tabDivs[tabIndex].classList.add("tab-panel")
263
+ tabDivs[tabIndex].setAttribute("tabindex", 0)
264
+ tabDivs[tabIndex].setAttribute("id", `tabHeading${tabNum}`)
265
+ //Add attributes for hide tab
266
+ if (tabIndex + 1 == tabInputs.length) {
267
+ tabLabels[tabIndex].classList.add("hide-tab")
268
+ tabInputs[tabIndex].checked = true;
269
+ tabDivs[tabIndex].classList.add("hide-panel")
270
+ }
271
+ tabsWidgetsNum++;
272
+ }
273
+ })
274
+ }
275
+ if (tabsWidgets) { callTabsWidget(); }
276
+
277
+ // Toggle footnotes
278
+ const toggleBtns = document.querySelectorAll(".toggle-btn, .toggle-footnotes");
279
+
280
+ if (document.querySelector(".toggle-btn") || document.querySelector(".toggle-footnotes")) {
281
+ for (let toggleBtn = 0; toggleBtn < toggleBtns.length; toggleBtn++) {
282
+ // Add tabindex
283
+ toggleBtns[toggleBtn].setAttribute("tabindex", "0");
284
+
285
+ // Show/hide on click
286
+ toggleBtns[toggleBtn].addEventListener("click", () => {
287
+ toggleBtns[toggleBtn].nextElementSibling.classList.toggle("show");
288
+ })
289
+
290
+ // Show/hide on enter for users who use tab
291
+ toggleBtns[toggleBtn].addEventListener("keydown", (enter) => {
292
+ if (enter.keyCode === 13) {
293
+ toggleBtns[toggleBtn].nextElementSibling.classList.toggle("show");
294
+ }
295
+ })
296
+ }
297
+ }
298
+
299
+ // Change footnotes from 'show' to 'hide'
300
+ const footnotes = document.querySelector(".toggle-footnotes");
301
+
302
+ if (footnotes) {
303
+ footnotes.addEventListener("click", () => {
304
+ footnotes.innerHTML = (footnotes.innerHTML === "[Show Footnotes]") ? "[Hide Footnotes]" : "[Show Footnotes]";
305
+ })
306
+ }
307
+
308
+ // Animated border for HRS theme
309
+ const hrsBorders = document.querySelectorAll(".hrs-border");
310
+
311
+ if (hrsBorders) {
312
+ for (let hrsBorder = 0; hrsBorder < hrsBorders.length; hrsBorder++) {
313
+ const callAnimateBorder = new IntersectionObserver(entries => {
314
+ // Loop over the entries
315
+ entries.forEach(entry => {
316
+ // If the element is visible
317
+ if (entry.isIntersecting) {
318
+ // Add the animation class
319
+ entry.target.classList.add('animate-border');
320
+ }
321
+ });
322
+ });
323
+ callAnimateBorder.observe(hrsBorders[hrsBorder]);
324
+ }
325
+ };
326
+
327
+ // Add h5p resizer dynamically
328
+ h5pResizer.setAttribute("src", "https://pima.h5p.com/js/h5p-resizer.js");
329
+ h5pResizer.setAttribute("charset", "UTF-8");
330
+ h5pResizer.setAttribute("defer", "");
331
+
332
+ h5pIframes.forEach(function (h5pIframe) {
333
+ const src = h5pIframe.getAttribute("src");
334
+ if (src.includes("/d2l/common/dialogs/quickLink") || src.includes("https://pima.h5p.com/content") && !h5pResizerExists) {
335
+ docHead.appendChild(h5pResizer);
336
+ }
337
+ });
338
+
339
+ if (document.querySelector("body[focus-reader]")) {
340
+
341
+ // Initializations
342
+ let focusOn = false;
343
+
344
+ const fr_contentWrapper = document.querySelector("#content-wrapper");
345
+ const fr_thisBody = document.querySelector("body[focus-reader]");
346
+
347
+ // Add fontAwesome to header
348
+ const fr_pageHead = document.querySelector("head");
349
+ const fontAweLink = document.createElement("link");
350
+ fontAweLink.rel = "stylesheet";
351
+ fontAweLink.href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css";
352
+ fr_pageHead.append(fontAweLink);
353
+
354
+ // add intersection observer to head tag
355
+ const observerScript = document.createElement("script");
356
+ observerScript.setAttribute("src", "https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver");
357
+ fr_pageHead.append(observerScript);
358
+
359
+ // Create Switches container
360
+ const fr_switchesContainer = document.createElement("div");
361
+ fr_switchesContainer.className = "focus-reader-switches";
362
+ fr_thisBody.append(fr_switchesContainer);
363
+
364
+ // Focus-text container
365
+ const fr_focusTextContainer = document.createElement("div");
366
+ fr_switchesContainer.append(fr_focusTextContainer);
367
+
368
+ // Focus-text Icon
369
+ const fr_focusTextIcon = document.createElement("p");
370
+ fr_focusTextIcon.innerHTML = "Focus Text"
371
+ fr_focusTextContainer.append(fr_focusTextIcon);
372
+
373
+ // info icon
374
+ const fr_infoIcon = document.createElement("i");
375
+ fr_infoIcon.classList.add("fa-solid");
376
+ fr_infoIcon.classList.add("fa-circle-info");
377
+ fr_focusTextContainer.append(fr_infoIcon);
378
+
379
+ // info tooltip
380
+ const fr_infoTooltip = document.createElement("span");
381
+ const focusReaderTooltipText = "Highlight text as you scroll";
382
+ fr_infoTooltip.classList.add("info-tooltip");
383
+ fr_infoTooltip.innerHTML = focusReaderTooltipText;
384
+ fr_infoIcon.append(fr_infoTooltip);
385
+
386
+ // Focus-text switch button
387
+ const fr_focusTextSwitch = document.createElement("button");
388
+ fr_focusTextSwitch.id = "focus-text";
389
+ fr_focusTextSwitch.innerHTML = (focusOn) ? `<i class="fas fa-toggle-on"></i>` : `<i class="fas fa-toggle-off"></i>`;
390
+ fr_focusTextContainer.append(fr_focusTextSwitch);
391
+
392
+ // Focus-text logic
393
+ fr_focusTextSwitch.addEventListener("click", () => {
394
+ if (focusOn) {
395
+ focusOn = !focusOn;
396
+ fr_contentWrapper.removeAttribute("on");
397
+ fr_focusTextSwitch.innerHTML = (focusOn) ? `<i class="fas fa-toggle-on"></i>` : `<i class="fas fa-toggle-off"></i>`;
398
+ removeSpans(fr_contentWrapper);
399
+ } else {
400
+ focusOn = !focusOn;
401
+ fr_contentWrapper.setAttribute("on", "");
402
+ fr_focusTextSwitch.innerHTML = (focusOn) ? `<i class="fas fa-toggle-on"></i>` : `<i class="fas fa-toggle-off"></i>`;
403
+ const elements = document.querySelectorAll("#content-wrapper .content-body :is(p, li, dd, dt, blockquote)");
404
+ elements.forEach(element => {
405
+ traverseAndWrapTextInSpans(element, focusText);
406
+ });
407
+ }
408
+ });
409
+
410
+ const options = {
411
+ root: null,
412
+ rootMargin: '-48.9% 0px',
413
+ threshold: 0.00
414
+ };
415
+
416
+ const focusText = new IntersectionObserver(entries => {
417
+ entries.forEach(entry => {
418
+ if (entry.isIntersecting) {
419
+ entry.target.closest("span").classList.add("focus-text");
420
+ } else {
421
+ entry.target.closest("span").classList.remove("focus-text");
422
+ }
423
+ });
424
+ }, options);
425
+
426
+
427
+ }
428
+
429
+ function traverseAndWrapTextInSpans(node, focusText) {
430
+ if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '') {
431
+ // Split the text into words
432
+ const words = node.textContent.split(' ');
433
+
434
+ // Wrap each word in a span
435
+ words.forEach(word => {
436
+ if (word !== '') {
437
+ const span = document.createElement("span");
438
+ span.textContent = word + ' '; // Add a space after the word to preserve spacing
439
+
440
+ // Observe the span
441
+ focusText.observe(span);
442
+
443
+ // Insert the span before the text node
444
+ node.parentNode.insertBefore(span, node);
445
+ }
446
+ });
447
+
448
+ // Remove the original text node
449
+ node.parentNode.removeChild(node);
450
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
451
+ // Recursively process child nodes
452
+ Array.from(node.childNodes).forEach(childNode => {
453
+ traverseAndWrapTextInSpans(childNode, focusText);
454
+ });
455
+ }
456
+ }
457
+
458
+ function removeSpans(node) {
459
+ if (node.nodeType === Node.ELEMENT_NODE) {
460
+ // If the node is a span with the class 'focus-text', an empty class attribute, is empty, or has no attributes, replace it with a text node
461
+ if (node.tagName === 'SPAN' && (node.className === 'focus-text' || node.className === '' || node.textContent.trim() === '' || node.attributes.length === 0)) {
462
+ const textNode = document.createTextNode(node.textContent);
463
+ node.parentNode.insertBefore(textNode, node);
464
+ node.parentNode.removeChild(node);
465
+ } else {
466
+ // Recursively process child nodes
467
+ Array.from(node.childNodes).forEach(childNode => {
468
+ removeSpans(childNode);
469
+ });
470
+ }
471
+ }
472
+ }
473
+
474
+ // Reset margin-top and padding-top for content bodies that don't have headings as the first child element
475
+ const ecnStylesheet = document.querySelector("[href*='themes/ecn/styles.css']");
476
+ const ecnContentBodies = document.querySelectorAll(".content-body");
477
+
478
+ if (ecnStylesheet) {
479
+ ecnContentBodies.forEach((contentBody) => {
480
+ const firstChildHeading = contentBody.querySelector(":first-child");
481
+ if (firstChildHeading && firstChildHeading.tagName !== "H2" && firstChildHeading.tagName !== "H3") {
482
+ contentBody.style.marginTop = "unset";
483
+ contentBody.style.paddingTop = "15px";
484
+ }
485
+ })
486
+ }
487
+
488
+
489
+ // Call function with jQuery scripts
490
+ const callJquery = () => {
491
+ // Toggle Button's Arrow Right Points Down on Click
492
+ $('.arrow-right').on('click', function () {
493
+ $(this).toggleClass('arrow-down');
494
+ });
495
+ // TOOLTIP
496
+ // Allows Screen readers to toggle a tooltip on click and to say if the tooltip is collapsed or expanded.
497
+ $(".tooltip").click(function () {
498
+ $(this).children(".tip-hover").toggle();
499
+ if ($(this).children(".tip-hover").is(':visible')) {
500
+ $(this).attr('aria-expanded', 'true');
501
+ $(this).removeClass('hidden');
502
+ } else {
503
+ $(this).attr('aria-expanded', 'false');
504
+ $(this).addClass('hidden');
505
+ }
506
+ });
507
+ let start = 999;
508
+ $('.tooltip').each(function (i) {
509
+ $(this).css('z-index', start--);
510
+ });
511
+ $(".tooltip .video-container").parent().css("width", "450px");
512
+ }
513
+ callJquery();
514
+
515
+ // Array for all themes that require theme specific js
516
+ const customJsThemes = ["ss"];
517
+
518
+ //Search links for theme styles
519
+ const themeJsCheck = () => {
520
+
521
+ const links = document.querySelectorAll("link");
522
+
523
+ links.forEach((link) => {
524
+ const href = link.getAttribute("href");
525
+ customJsThemes.forEach((theme) => {
526
+ // If theme requires custom js, run addThemeScript()
527
+ if (href && href.includes(`/${theme}/styles.css`)) {
528
+ addThemeScript(theme);
529
+ }
530
+ });
531
+ });
532
+
533
+ // Adds a script to the head for that particular theme
534
+ function addThemeScript(theme) {
535
+ let themeScript = document.createElement("script");
536
+ themeScript.src = `../js/themes/${theme}.js`;
537
+ document.head.appendChild(themeScript);
538
+ }
539
+ }
540
+
541
+ themeJsCheck()