├── .gitignore ├── .prettierrc ├── HXHelper Files.zip ├── HXHelper Files ├── HXEditor.js ├── HXPopUpProblems.js ├── HXVideoChime.js ├── HXVideoLinks.js ├── VideoLinks.css ├── ajax-loader.gif ├── hx_tiny_white.png ├── imageMapResizer.min.js ├── intro_accessible.js ├── introjs.css ├── prism.css ├── prism.js ├── quote-left.png ├── quote-right.png ├── slick-theme.css ├── slick.css ├── slick.eot ├── slick.js ├── slick.svg ├── slick.ttf ├── slick.woff ├── summernote-lite.min.css ├── summernote-lite.min.js ├── summernote.eot ├── summernote.ttf ├── summernote.woff ├── summernote.woff2 ├── word_cloud_access.js └── word_cloud_access_new.js ├── LICENSE ├── README.md ├── datamap ├── File-BlankMap-World6.svg - Wikimedia Commons.webloc ├── File-Reported Bigfoot sightings (updated).svg - Wikimedia Commons.webloc ├── For_EdX.html ├── For_files_and_uploads.zip ├── US_Canada_vector_map.svg ├── World_Data_Map.html ├── World_Map_Blank_for_data.svg ├── jquery-3.3.1.min.js ├── jquery.svg.package-1.5.0 │ ├── jquery.svg.css │ ├── jquery.svg.js │ ├── jquery.svg.min.js │ ├── jquery.svganim.js │ ├── jquery.svganim.min.js │ ├── jquery.svgdom.js │ ├── jquery.svgdom.min.js │ ├── jquery.svgfilter.js │ ├── jquery.svgfilter.min.js │ ├── jquery.svggraph.js │ ├── jquery.svggraph.min.js │ ├── jquery.svgplot.js │ ├── jquery.svgplot.min.js │ ├── lion.svg │ └── svgBasic.html ├── jquery.svgdom.js ├── map_base_style.css ├── map_data.js ├── papaparse.min.js └── sample data │ ├── Alzheimers.csv │ ├── Malaria_dalys.csv │ ├── Malaria_dalys.xlsx │ ├── Musculoskeletal.csv │ └── Unintentional_Injury.csv ├── form to table ├── For_EdX.html ├── formtable_csv.js └── papaparse.min.js ├── hx-min.css ├── hx-min.js ├── hx-min.zip ├── hx-standard.zip ├── hx.css ├── hx.js ├── hxGlobalOptions.js ├── misc_zipfiles └── introjs.zip ├── prism LICENSE.txt ├── public_html ├── bookmarklet_list.html ├── course_staff_adder.js ├── erase_all_uploads.js └── flashcards │ ├── flashcards.css │ ├── flashcards.js │ ├── index.html │ ├── lib │ ├── hammer.min.js │ ├── hammer.min.js.map │ └── papaparse.min.js │ └── source.csv ├── summernote LICENSE.txt └── text slider ├── ClimateImpactExplorer.csv ├── README.md ├── Slider Test.html ├── hx-text-slider-old.css ├── hx-text-slider-old.js ├── hx-text-slider.css ├── hx-text-slider.js ├── leads-to.jpg ├── leads-to.pxm └── papaparse.js /.gitignore: -------------------------------------------------------------------------------- 1 | ._DS_STORE 2 | somewhat unrelated/.DS_Store 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /HXHelper Files.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files.zip -------------------------------------------------------------------------------- /HXHelper Files/HXPopUpProblems.js: -------------------------------------------------------------------------------- 1 | // This version is designed to be called by hx.js 2 | // Global variable HXPUPTimer is defined in the HTML. 3 | // The hmsToTime converter function is defined in hx.js 4 | 5 | var HXPopUpProblems = function (HXpopUpOptions, HXPUPTimer) { 6 | // Declaring semi-global variables for later use. 7 | var video = $('.video'); 8 | var state; 9 | var time; 10 | var problemCounter; 11 | var skipEmAll; 12 | var protectedTime = false; 13 | var problemsBeingShown = 0; 14 | 15 | // Convert mm:ss format in HXPUPTimer to seconds. 16 | for (var i = 0; i < HXPUPTimer.length; i++) { 17 | for (var key in HXPUPTimer[i]) { 18 | if (key == 'time') { 19 | HXPUPTimer[i].time = hmsToTime(HXPUPTimer[i].time); 20 | } 21 | } 22 | } 23 | 24 | // Sort the pop-up timer. 25 | HXPUPTimer.sort(timeCompare); // Uses a custom function to sort by time. 26 | // Ditch any questions that are missing from the screen. 27 | HXPUPTimer = stripMissingQuestions(HXPUPTimer); 28 | 29 | // Log play/pause events from the player. 30 | // Also set the play/pause external control properly. 31 | video.on('pause', function () { 32 | logThatThing({ video_event: 'pause' }); 33 | $('#hx-playpauseicon').html('‣'); 34 | $('#hx-playpauseword').html('Play'); 35 | }); 36 | 37 | video.on('play', function () { 38 | logThatThing({ video_event: 'play' }); 39 | // Also set the play/pause external control properly. 40 | $('#hx-playpauseicon').html('||'); // Need a better-looking pause icon. 41 | $('#hx-playpauseword').html('Pause'); 42 | }); 43 | 44 | // Check to see whether the video is ready before continuing. 45 | var waitForVid = setInterval(function () { 46 | try { 47 | state = video.data('video-player-state'); // Sometimes this fails and that's ok. 48 | 49 | if (typeof state.videoPlayer.player.getPlayerState() !== 'undefined') { 50 | clearInterval(waitForVid); 51 | setUpData(); 52 | setUpControls(); 53 | mainLoop(); 54 | } 55 | } catch (err) { 56 | console.log('waiting for first video to be ready'); 57 | } 58 | }, 200); 59 | 60 | // Take out any problems that don't appear on the page. 61 | function stripMissingQuestions(timer) { 62 | // Get an array with all the text of all the H3's (trimming whitespace) 63 | let allH3 = $('h3') 64 | .map((x, e) => $(e).text().trim()) 65 | .toArray(); 66 | 67 | // Remove any items from the timer that aren't in the list of H3's 68 | let newTimer = timer.filter((x) => allH3.includes(x.title)); 69 | 70 | // Return the new timer. 71 | return newTimer; 72 | } 73 | 74 | // Checks local storage and gets data from the video. 75 | function setUpData() { 76 | // Get the video data. 77 | video = $('.video'); 78 | state = video.data('video-player-state'); 79 | time = state.videoPlayer.currentTime; 80 | 81 | // Storing a separate problem counter for each video. 82 | // Create the counter if it doesn't exist. 83 | if (!localStorage[state.id + '-counter']) { 84 | localStorage[state.id + '-counter'] = '0'; 85 | localStorage[state.id + '-skip'] = 'false'; 86 | 87 | // If the counter didn't exist, we're on a new browser. 88 | // Clear the questions from before the current time. 89 | clearOlderPopUps(time); 90 | } 91 | 92 | // Which problem are we on? Using + to cast as integer. 93 | problemCounter = +localStorage[state.id + '-counter']; 94 | 95 | // Are we currently skipping problems? 96 | skipEmAll = localStorage[state.id + '-skip'] === 'true'; 97 | // If so, let's update the button. 98 | if (skipEmAll) { 99 | $('#hx-sunmoon').html('☾'); 100 | $('#hx-onoff').html('Problems are Off'); 101 | logThatThing({ 102 | reload_event: 'turn_problems_off', 103 | time: time, 104 | }); 105 | } 106 | 107 | // Let's avoid situations where we're faced with a question 108 | // mere seconds after the page loads, shall we? 109 | // Unless we're past the last problem... 110 | if (time < HXPUPTimer[HXPUPTimer.length - 1].time) { 111 | if (problemCounter > 0) { 112 | // Go to just after the previous question... 113 | ISaidGoTo(HXPUPTimer[problemCounter - 1].time + 1); 114 | } else { 115 | // Or all the way back to the beginning. 116 | ISaidGoTo(0); 117 | } 118 | } 119 | } 120 | 121 | // Makes the buttons work and sets up event handlers. 122 | function setUpControls() { 123 | // If they seek to a specific position, set the problem counter appropriately 124 | // so that earlier problems don't gang up on them. 125 | video.on('seek', function (event, ui) { 126 | clearOlderPopUps(ui); 127 | }); 128 | 129 | // Let someone go through the problems again if they want. 130 | // Also useful for debugging. 131 | $('#hx-popUpReset').on('click tap', function () { 132 | updateProblemCounter(0); 133 | ISaidGoTo(0); 134 | logThatThing({ control_event: 'reset counter and set t=0' }); 135 | 136 | // If problems are currently on, turn them off for two seconds after we go back. 137 | // This addresses a bug that appears in Mobile Safari. 138 | // Note that you can't put questions in the first two seconds of the video 139 | // because of this. 140 | if (!skipEmAll) { 141 | skipEmAll = true; 142 | var dontSpamProblemsEarly = setTimeout(function () { 143 | skipEmAll = false; 144 | }, 2000); 145 | } 146 | }); 147 | 148 | // Go back to one second after the previous problem. 149 | $('#hx-backOneProblem').on('click tap', function () { 150 | if (problemCounter > 1) { 151 | var newTime = HXPUPTimer[problemCounter - 2].time + 1; 152 | ISaidGoTo(newTime); 153 | logThatThing({ control_event: 'go back one' }); 154 | } else { 155 | updateProblemCounter(0); 156 | ISaidGoTo(0); 157 | logThatThing({ control_event: 'go back one to start' }); 158 | } 159 | }); 160 | 161 | // Play or pause the video 162 | $('#hx-popUpPlayPause').on('click tap', function () { 163 | if (state.videoPlayer.isPlaying()) { 164 | state.videoPlayer.pause(); 165 | $('#hx-playpauseicon').html('‣'); 166 | $('#hx-playpauseword').html('Play'); 167 | logThatThing({ control_event: 'play' }); 168 | } else { 169 | state.videoPlayer.play(); 170 | $('#hx-playpauseicon').html('||'); 171 | $('#hx-playpauseword').html('Pause'); 172 | logThatThing({ control_event: 'pause' }); 173 | } 174 | }); 175 | 176 | // Let someone turn the pop-up questions on and off. 177 | // Give visual indication by changing the button. 178 | $('#hx-problemToggle').on('click tap', function () { 179 | if (skipEmAll) { 180 | skipEmAll = false; 181 | localStorage[state.id + '-skip'] = 'false'; 182 | $('#hx-sunmoon').html('☼'); 183 | $('#hx-onoff').html('Problems are On'); 184 | logThatThing({ 185 | control_event: 'turn_problems_on', 186 | time: time, 187 | }); 188 | } else { 189 | skipEmAll = true; 190 | localStorage[state.id + '-skip'] = 'true'; 191 | $('#hx-sunmoon').html('☾'); 192 | $('#hx-onoff').html('Problems are Off'); 193 | logThatThing({ 194 | control_event: 'turn_problems_off', 195 | time: time, 196 | }); 197 | } 198 | }); 199 | } 200 | 201 | // Every 500 ms, check to see whether we're going to add a new problem. 202 | function mainLoop() { 203 | var timeChecker = setInterval(function () { 204 | try { 205 | state.videoPlayer.update(); // Forced update of time. Required for Safari. 206 | } catch (err) { 207 | // If the update fails, shut down this loop. 208 | // It's probably because we moved to a new tab. 209 | clearInterval(timeChecker); 210 | } 211 | time = state.videoPlayer.currentTime; 212 | 213 | if (problemCounter < HXPUPTimer.length) { 214 | if (time > HXPUPTimer[problemCounter].time) { 215 | if (!skipEmAll && !protectedTime) { 216 | popUpProblem(HXPUPTimer[problemCounter].title, state); 217 | } 218 | // We're still incrementing and tracking even if we skip problems. 219 | updateProblemCounter(problemCounter + 1); 220 | } 221 | } 222 | }, 500); 223 | } 224 | 225 | // Set all the time-related stuff to a particular time and then seek there. 226 | // Does the work of creating the dialogue. 227 | // It pulls a question from lower down in the page, and puts it back when we're done. 228 | function popUpProblem(title, state) { 229 | // Strip leading and trailing whitespace, 'cause it's the most common typo. 230 | title = title.trim(); 231 | 232 | // Find the div for the problem based on its title. 233 | var problemDiv = $('h3:contains(' + title + ')').closest('.vert'); 234 | 235 | var problemID = $('h3:contains(' + title + ')') 236 | .parent() 237 | .attr('id'); 238 | 239 | var tempDiv; 240 | var dialogDiv = problemDiv; 241 | var includenext = false; 242 | 243 | // Sometimes we can't find an h3 to latch onto. 244 | // We put into an HTML bit before it. 245 | // The dialog then displays the next item, and appends a clone of the HTML before it. 246 | if (problemDiv.find('span.hx-includer').text() == 'includenext') { 247 | dialogDiv = problemDiv.next(); 248 | includenext = true; 249 | } 250 | 251 | logThatThing({ 252 | display_problem: title, 253 | problem_id: problemID, 254 | time: time, 255 | }); 256 | 257 | // Pause the video. 258 | state.videoPlayer.pause(); 259 | 260 | // Make a modal dialog out of the chosen problem. 261 | dialogDiv.dialog({ 262 | modal: true, 263 | dialogClass: 'hx-popup no-close', 264 | resizable: true, 265 | width: HXpopUpOptions.width, 266 | show: { 267 | effect: HXpopUpOptions.effect, 268 | duration: HXpopUpOptions.effectlength, 269 | }, 270 | position: { 271 | my: 'top', 272 | at: 'top', 273 | of: video, 274 | }, 275 | // position: { 276 | // my: HXpopUpOptions.myPosition, 277 | // at: HXpopUpOptions.atPosition, 278 | // of: HXpopUpOptions.ofTarget, 279 | // }, 280 | buttons: { 281 | Skip: function () { 282 | if (includenext) { 283 | tempDiv.remove(); 284 | } // We added it, we should erase it. 285 | dialogDestroyed('skip_problem'); 286 | $(this).dialog('destroy'); // Put the problem back when we're done. 287 | }, 288 | Done: function () { 289 | if (includenext) { 290 | tempDiv.remove(); 291 | } // We added it, we should erase it. 292 | dialogDestroyed('mark_done'); 293 | $(this).dialog('destroy'); // Put the problem back when we're done. 294 | }, 295 | }, 296 | open: function () { 297 | // If we're including two divs, append a clone of the first one above. 298 | if (includenext) { 299 | tempDiv = problemDiv.clone(); 300 | dialogDiv.prepend(tempDiv); 301 | } 302 | // Highlight various controls. 303 | $('span.ui-button-text:contains("Done")').addClass('answeredButton'); 304 | $('input.check.Check').attr( 305 | 'style', 306 | ' background: linear-gradient(to top, #9df 0%,#7bd 20%,#adf 100%); background-color:#ACF; text-shadow: none;' 307 | ); 308 | problemsBeingShown++; 309 | }, 310 | close: function () { 311 | state.videoPlayer.play(); 312 | if (includenext) { 313 | tempDiv.remove(); 314 | } // We added it, we should erase it. 315 | logThatThing({ unusual_event: 'dialog closed unmarked' }); // Should be pretty rare. I took out the 'close' button. 316 | }, 317 | }); 318 | } 319 | 320 | // Log the destruction of the dialog and play the video if there are no more dialogs up. 321 | function dialogDestroyed(message) { 322 | logThatThing({ control_event: message }); 323 | $('input.check.Check').removeAttr('style'); // un-blue the check button. 324 | problemsBeingShown--; 325 | if (problemsBeingShown < 1) { 326 | state.videoPlayer.play(); 327 | } 328 | } 329 | 330 | // This resets the problem counter to match the time. 331 | function clearOlderPopUps(soughtTime) { 332 | logThatThing({ control_event: 'seek to ' + soughtTime }); 333 | updateProblemCounter(0); // Resetting fresh. 334 | for (var i = 0; i < HXPUPTimer.length; i++) { 335 | if (soughtTime > HXPUPTimer[i].time) { 336 | updateProblemCounter(i + 1); 337 | } else { 338 | break; 339 | } 340 | } 341 | } 342 | 343 | // I blame multiple Javascript timing issues. 344 | function ISaidGoTo(thisTime) { 345 | time = thisTime; 346 | state.videoPlayer.player.seekTo(thisTime); 347 | logThatThing({ seek_to: thisTime }); 348 | } 349 | 350 | // Keep the counter and the local storage in sync. 351 | function updateProblemCounter(number) { 352 | problemCounter = number; 353 | localStorage[state.id + '-counter'] = number.toString(); 354 | logThatThing({ problem_counter_set: problemCounter }); 355 | } 356 | 357 | // This is a sorting function for my timer. 358 | function timeCompare(a, b) { 359 | if (a.time < b.time) return -1; 360 | if (a.time > b.time) return 1; 361 | return 0; 362 | } 363 | 364 | return true; 365 | }; 366 | -------------------------------------------------------------------------------- /HXHelper Files/HXVideoChime.js: -------------------------------------------------------------------------------- 1 | var HXVideoChime = function(hxChimeOptions) { 2 | // Declaring semi-global variables for later use. 3 | // HXChimeTimer is defined in HTML on the page. 4 | var video = $('.video'); 5 | var chimebox = $('.hx-video-chimebox'); 6 | 7 | logThatThing('Video chimes starting'); 8 | 9 | // Let people turn the chimes on and off. 10 | let chimetoggle = $(''); 11 | 12 | chimebox.append(chimetoggle); 13 | chimetoggle.wrap('

'); 14 | if (localStorage.hxChimesOff === 'true') { 15 | chimetoggle.text('Turn video chimes off'); 16 | } else { 17 | chimetoggle.text('Turn video chimes on'); 18 | } 19 | chimetoggle.on('click tap', function() { 20 | localStorage.hxChimesOff = localStorage.hxChimesOff !== 'true'; 21 | chimetoggle.text( 22 | localStorage.hxChimesOff === 'true' 23 | ? 'Turn video chimes off' 24 | : 'Turn video chimes on' 25 | ); 26 | }); 27 | 28 | // Mark each video and set of controls with a class and anchor 29 | // that will let us handle each of them separately. 30 | // Numbering from 1 to make things easier for course creators. 31 | video.each(function(index) { 32 | $(this).addClass('for-video-' + (index + 1)); 33 | }); 34 | video.each(function(index) { 35 | $(this) 36 | .parent() 37 | .prepend(''); 38 | }); 39 | 40 | video.each(function(vidnumber) { 41 | var thisVid = $(this); 42 | var chimeData = setUpLists(vidnumber); 43 | // Check to see whether the video is ready before continuing. 44 | var waitForVid = setInterval(function() { 45 | try { 46 | var state = thisVid.data('video-player-state'); // Sometimes this fails and that's ok. 47 | 48 | if (typeof state.videoPlayer !== 'undefined') { 49 | if ( 50 | typeof state.videoPlayer.player.getPlayerState() !== 'undefined' 51 | ) { 52 | console.log('video data loaded'); 53 | mainLoop(state, chimeData, vidnumber); 54 | clearInterval(waitForVid); 55 | } 56 | } 57 | } catch (err) { 58 | console.log('waiting for video ' + (vidnumber + 1) + ' to be ready'); 59 | } 60 | }, 250); 61 | }); 62 | 63 | // Make a list of all the times where there would be chimes. 64 | function setUpLists(vidnumber) { 65 | console.log('setting up lists of chimes'); 66 | 67 | let sortedTimer = HXChimeTimer[vidnumber].slice().sort(timeCompare); 68 | let chimeData = { 69 | text: sortedTimer.map(e => e.title), 70 | timer: sortedTimer.map(e => hmsToTime(e.time)), 71 | sounds: sortedTimer.map(e => e.sound), 72 | played: [] 73 | }; 74 | 75 | // Write a list on-screen. 76 | chimeData.text.forEach(function(e, i) { 77 | let ch = $(''); 78 | ch.text(timeToHMS(chimeData.timer[i]) + ' - ' + chimeData.text[i]); 79 | $(chimebox[vidnumber]).append(ch); 80 | $(chimebox[vidnumber]).append($('
')); 81 | }); 82 | 83 | return chimeData; 84 | } 85 | 86 | // Every 250 ms, check to see whether we're going to play a chime. 87 | function mainLoop(state, chimeData, vidnumber) { 88 | console.log('start main loop for chimes'); 89 | let time = 0; 90 | let lastTime = 0; 91 | let twoAgo = 0; 92 | let chimeTime = 0; 93 | let okToPlay = true; 94 | 95 | console.log(state); 96 | 97 | var timeChecker = setInterval(function() { 98 | try { 99 | state.videoPlayer.update(); // Forced update of time. Required for Safari. 100 | } catch (err) { 101 | // If this fails, shut down this loop. 102 | // It's probably because we moved to a new tab. 103 | clearInterval(timeChecker); 104 | } 105 | 106 | twoAgo = lastTime; 107 | lastTime = time; 108 | time = state.videoPlayer.currentTime; 109 | chimeTime = chimeData.timer[nextChime(chimeData, lastTime)]; 110 | 111 | // Don't chime on initial play event. 112 | // Note: The time when the "play" event actually fires is late. 113 | // Checking the time is more reliable. 114 | if (twoAgo === lastTime) { 115 | okToPlay = false; 116 | } else { 117 | okToPlay = true; 118 | } 119 | 120 | // console.log(lastTime, chimeTime, time); 121 | // If the chime time is the median value, play a chime. 122 | if ( 123 | lastTime <= chimeTime && 124 | time >= chimeTime && 125 | okToPlay && 126 | localStorage.hxChimesOff !== 'false' 127 | ) { 128 | playChime(chimeData, lastTime, vidnumber); 129 | } 130 | }, 250); 131 | } 132 | 133 | // Play the current chime. 134 | function playChime(chimeData, time, vidnumber) { 135 | let n = nextChime(chimeData, time); 136 | let assetURL = getAssetURL(window.location.href, 'complete'); 137 | let audio = new Audio(assetURL + chimeData.sounds[n]); 138 | audio.play(); 139 | console.log( 140 | 'Playing chime ' + 141 | (n + 1) + 142 | ', ' + 143 | chimeData.sounds[n] + 144 | ', for video ' + 145 | (vidnumber + 1) 146 | ); 147 | } 148 | 149 | function nextChime(chimeData, time) { 150 | let next = 0; 151 | for (let n = 0; n < chimeData.timer.length; n++) { 152 | if (chimeData.timer[n] < time) { 153 | next++; 154 | } 155 | } 156 | return next; 157 | } 158 | 159 | // This is a sorting function for my timer. 160 | function timeCompare(a, b) { 161 | if (hmsToTime(a.time) < hmsToTime(b.time)) return -1; 162 | if (hmsToTime(a.time) > hmsToTime(b.time)) return 1; 163 | return 0; 164 | } 165 | 166 | return true; 167 | }; 168 | -------------------------------------------------------------------------------- /HXHelper Files/HXVideoLinks.js: -------------------------------------------------------------------------------- 1 | var HXVideoLinks = function(hxLinkOptions) { 2 | // Declaring semi-global variables for later use. 3 | var video = $('.video'); 4 | var vidWrappers = $('.video-wrapper'); 5 | var time; 6 | var linkTimer = []; 7 | var linkBeingShown = []; 8 | 9 | logThatThing('Video Links starting'); 10 | 11 | // Mark each video and set of controls with a class and anchor 12 | // that will let us handle each of them separately. 13 | // Numbering from 1 to make things easier for course creators. 14 | video.each(function(index) { 15 | $(this).addClass('for-video-' + (index + 1)); 16 | }); 17 | video.each(function(index) { 18 | $(this) 19 | .parent() 20 | .prepend(''); 21 | }); 22 | vidWrappers.each(function(index) { 23 | $(this).addClass('for-video-' + (index + 1)); 24 | }); 25 | 26 | video.each(function(vidnumber) { 27 | var thisVid = $(this); 28 | setUpLists(vidnumber); 29 | 30 | // Check to see whether the video is ready before continuing. 31 | var waitForVid = setInterval(function() { 32 | try { 33 | var state = thisVid.data('video-player-state'); // Sometimes this fails and that's ok. 34 | 35 | if (typeof state.videoPlayer !== 'undefined') { 36 | if ( 37 | typeof state.videoPlayer.player.getPlayerState() !== 'undefined' 38 | ) { 39 | console.log('video data loaded'); 40 | 41 | // Follow a video player link from an old page, if we have one. 42 | checkJumpFromOldPage(); 43 | 44 | // We're positioning links based on the video. 45 | vidWrappers.addClass('link-positioner'); 46 | 47 | setUpListeners(state); 48 | mainLoop(state, vidnumber); 49 | clearInterval(waitForVid); 50 | } 51 | } 52 | } catch (err) { 53 | console.log('waiting for video ' + (vidnumber + 1) + ' to be ready'); 54 | } 55 | }, 200); 56 | }); 57 | 58 | // Take the simple list in our HTML and make it FABULOUS 59 | function setUpLists(vidnumber) { 60 | // Let's copy the links to the appropriate location so we can position them there. 61 | var vidlinks = $('#hx-vidlinks-static-' + (vidnumber + 1)) 62 | .clone() 63 | .prop('id', 'hx-vidlinks-live-' + (vidnumber + 1)); 64 | vidlinks.appendTo('.video-wrapper.for-video-' + (vidnumber + 1)); 65 | 66 | linkTimer[vidnumber] = []; 67 | 68 | // Each link needs a little bit added to it, to keep the author view simple. 69 | // This preps the links that we're going to display on the video. 70 | $('#hx-vidlinks-live-' + (vidnumber + 1)) 71 | .children() 72 | .each(function(index) { 73 | var thisLinkBox = $(this); 74 | var thisLink = $(this).find('a'); 75 | 76 | // Give the link a class and a unique ID 77 | thisLinkBox.addClass('hx-vidlink_' + hxLinkOptions.location); 78 | thisLinkBox.attr('id', 'link-card-live-' + index); 79 | 80 | // Give the images a class for styling purporses. 81 | thisLink.find('img').addClass('hx-vidlinkicon'); 82 | 83 | // Make all the links open in new pages. 84 | thisLink.attr('target', '_blank'); 85 | // Style all the links 86 | thisLink.addClass('hx-link-text-live'); 87 | 88 | // Screen readers should skip these links. Rarely (but not never) an issue. 89 | thisLinkBox.attr('aria-hidden', 'true'); 90 | 91 | // Build the link timer from the divs. 92 | var tempTimer = { 93 | time: hmsToTime(thisLinkBox.attr('data-time')), 94 | shown: false 95 | }; 96 | linkTimer[vidnumber].push(tempTimer); 97 | }); 98 | 99 | // This preps the ones that are visible all the time. 100 | $('#hx-vidlinks-static-' + (vidnumber + 1)) 101 | .children() 102 | .each(function(index) { 103 | var thisLinkBox = $(this); 104 | var thisLink = $(this).find('a'); 105 | 106 | // Give the link a class and a unique ID 107 | thisLinkBox.addClass('hx-vidlink-static'); 108 | thisLinkBox.attr('id', 'link-card-static-' + index); 109 | 110 | // Remove the images. 111 | thisLink.find('img').remove(); 112 | 113 | // Add time links before. 114 | let timelink = $(''); 115 | timelink.attr('href', thisLinkBox.attr('data-time')); 116 | timelink.addClass('jumptime'); 117 | timelink.text(timeToHMS(hmsToTime(thisLinkBox.attr('data-time')))); 118 | thisLinkBox.prepend(' - '); 119 | thisLinkBox.prepend(timelink); 120 | console.log('timelink added'); 121 | }); 122 | 123 | // Finish making the unordered list. 124 | $('#hx-vidlinks-static-' + (vidnumber + 1) + ' .hx-vidlink-static').wrapAll( 125 | '

' 126 | ); 127 | 128 | linkTimer[vidnumber].sort(timeCompare); // Uses a custom function to sort by time. 129 | } 130 | 131 | // Set up listeners for the live links. 132 | function setUpListeners(state) { 133 | // If they click on one of the live links, pause the video. 134 | $('.hx-link-text-live').on('click tap', function() { 135 | state.videoPlayer.pause(); 136 | }); 137 | } 138 | 139 | // Every 500 ms, check to see whether we're going to show a new link. 140 | function mainLoop(state, vidnumber) { 141 | var timeChecker = setInterval(function() { 142 | try { 143 | state.videoPlayer.update(); // Forced update of time. Required for Safari. 144 | } catch (err) { 145 | // If this fails, shut down this loop. 146 | // It's probably because we moved to a new tab. 147 | clearInterval(timeChecker); 148 | } 149 | time = state.videoPlayer.currentTime; 150 | 151 | // If we should be showing a link: 152 | if (currentLink(time, vidnumber) != -1) { 153 | // ...and there's something being shown, 154 | if (linkBeingShown[vidnumber]) { 155 | // but it's not the one that should be shown, 156 | if (currentLink(time, vidnumber) != currentLinkShown(vidnumber)) { 157 | // then hide it. 158 | hideLink(currentLinkShown(vidnumber), vidnumber); 159 | } 160 | } 161 | 162 | // ...and there's nothing being shown, 163 | else { 164 | // then show the one we should be showing. 165 | showLink(currentLink(time, vidnumber), vidnumber); 166 | } 167 | 168 | // If we should NOT be showing a link, 169 | } else { 170 | // ...and one is showing, hide it. 171 | if (currentLinkShown(vidnumber) != -1) { 172 | hideLink(currentLinkShown(vidnumber), vidnumber); 173 | } 174 | } 175 | }, 500); 176 | } 177 | 178 | // Show the link on the video. While we're at it, bold the one in the list too. 179 | function showLink(n, vidnumber) { 180 | console.log('showing link ' + (n + 1) + ' for video ' + (vidnumber + 1)); 181 | $('#hx-vidlinks-live-' + (vidnumber + 1) + ' #link-card-live-' + n).show( 182 | hxLinkOptions.effect, 183 | hxLinkOptions.show, 184 | hxLinkOptions.speed 185 | ); 186 | $('#hx-vidlinks-static-' + (vidnumber + 1) + ' #link-card-static-' + n) 187 | .children() 188 | .addClass('hx-boldlink'); 189 | linkTimer[vidnumber][n].shown = true; 190 | linkBeingShown[vidnumber] = true; 191 | } 192 | 193 | // Hide the link on the video and un-bold the one on the list. 194 | function hideLink(n, vidnumber) { 195 | console.log('hiding link ' + (n + 1) + ' for video ' + (vidnumber + 1)); 196 | $('#hx-vidlinks-live-' + (vidnumber + 1) + ' #link-card-live-' + n).hide( 197 | hxLinkOptions.effect, 198 | hxLinkOptions.show, 199 | hxLinkOptions.speed 200 | ); 201 | $('#hx-vidlinks-static-' + (vidnumber + 1) + ' #link-card-static-' + n) 202 | .children() 203 | .removeClass('hx-boldlink'); 204 | linkTimer[vidnumber][n].shown = false; 205 | linkBeingShown[vidnumber] = false; 206 | } 207 | 208 | function checkJumpFromOldPage() { 209 | console.log('check for jump'); 210 | // If we have a jump-to-time link pending for this page, 211 | // go there and start the video playing. 212 | if (localStorage.HXVideoLinkGo === 'true') { 213 | if (window.location.href.indexOf(localStorage.HXVideoLinkUnit) != -1) { 214 | logThatThing({ 215 | 'Jumping to video': { 216 | unit: localStorage.HXVideoLinkUnit, 217 | 'video number': localStorage.HXVideoLinkNumber, 218 | time: localStorage.HXVideoLinkTime 219 | } 220 | }); 221 | // Make sure the video is ready before we try to go to the time. 222 | jumpToTime( 223 | localStorage.HXVideoLinkNumber, 224 | localStorage.HXVideoLinkTime 225 | ); 226 | } 227 | } else { 228 | console.log('No video link to jump to.'); 229 | } 230 | 231 | localStorage.HXVideoLinkGo = 'false'; 232 | } 233 | 234 | // Which link SHOULD we be showing right now? Return -1 if none. 235 | // If we should be showing several, returns the first one. 236 | function currentLink(t, vidnumber) { 237 | var linkNumber = -1; 238 | 239 | for (var i = 0; i < linkTimer[vidnumber].length; i++) { 240 | if ( 241 | t >= linkTimer[vidnumber][i].time && 242 | t < linkTimer[vidnumber][i].time + hxLinkOptions.hideLinkAfter 243 | ) { 244 | linkNumber = i; 245 | break; 246 | } 247 | } 248 | return linkNumber; 249 | } 250 | 251 | // Which link are we ACTUALLY showing right now? Return -1 if none. 252 | // If we're showing several, returns the first one. 253 | function currentLinkShown(vidnumber) { 254 | var linkNumber = -1; 255 | 256 | for (var i = 0; i < linkTimer[vidnumber].length; i++) { 257 | if (linkTimer[vidnumber][i].shown) { 258 | linkNumber = i; 259 | break; 260 | } 261 | } 262 | return linkNumber; 263 | } 264 | 265 | // This is a sorting function for my timer. 266 | function timeCompare(a, b) { 267 | if (a.time < b.time) return -1; 268 | if (a.time > b.time) return 1; 269 | return 0; 270 | } 271 | 272 | return true; 273 | }; 274 | -------------------------------------------------------------------------------- /HXHelper Files/VideoLinks.css: -------------------------------------------------------------------------------- 1 | #vidlinks-static { 2 | } 3 | 4 | #vidlinks-live { 5 | } 6 | 7 | .link-text-live { 8 | } 9 | 10 | .vidlink_bl { 11 | display: none; 12 | opacity: 0.7; 13 | background-color: black; 14 | 15 | font-size: 16px; 16 | color: white; 17 | 18 | position: absolute; 19 | bottom: 50px; 20 | 21 | padding: 4px; 22 | border: 2px solid grey; 23 | margin: 2px; 24 | 25 | border-radius: 4px; 26 | box-shadow: 2px 2px 4px #111; 27 | } 28 | 29 | .vidlink_br { 30 | display: none; 31 | opacity: 0.7; 32 | background-color: black; 33 | 34 | font-size: 16px; 35 | color: white; 36 | 37 | position: absolute; 38 | bottom: 50px; 39 | right: 5px; 40 | 41 | padding: 4px; 42 | border: 2px solid grey; 43 | margin: 2px; 44 | 45 | border-radius: 4px; 46 | box-shadow: 2px 2px 4px #111; 47 | } 48 | 49 | .vidlink_tl { 50 | display: none; 51 | opacity: 0.7; 52 | background-color: black; 53 | 54 | font-size: 16px; 55 | color: white; 56 | 57 | position: absolute; 58 | top: 5px; 59 | 60 | padding: 4px; 61 | border: 2px solid grey; 62 | margin: 2px; 63 | 64 | border-radius: 4px; 65 | box-shadow: 2px 2px 4px #111; 66 | } 67 | 68 | .vidlink_tr { 69 | display: none; 70 | opacity: 0.7; 71 | background-color: black; 72 | 73 | font-size: 16px; 74 | color: white; 75 | 76 | position: absolute; 77 | top: 5px; 78 | right: 5px; 79 | 80 | padding: 4px; 81 | border: 2px solid grey; 82 | margin: 2px; 83 | 84 | border-radius: 4px; 85 | box-shadow: 2px 2px 4px #111; 86 | } 87 | 88 | .vidlink-static { 89 | display: list-item; 90 | } 91 | 92 | .vidlink_bl a:link { 93 | color: white; 94 | } 95 | 96 | .vidlink_tl a:link { 97 | color: white; 98 | } 99 | 100 | .vidlink_tr a:link { 101 | color: white; 102 | } 103 | 104 | .vidlink_br a:link { 105 | color: white; 106 | } 107 | 108 | .vidlink_bl a:visited { 109 | color: white; 110 | } 111 | 112 | .vidlink_tl a:visited { 113 | color: white; 114 | } 115 | 116 | .vidlink_tr a:visited { 117 | color: white; 118 | } 119 | 120 | .vidlink_br a:visited { 121 | color: white; 122 | } 123 | 124 | .link-positioner { 125 | position: relative !important; 126 | } 127 | 128 | 129 | .boldlink { 130 | font-weight: bold !important; 131 | } -------------------------------------------------------------------------------- /HXHelper Files/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files/ajax-loader.gif -------------------------------------------------------------------------------- /HXHelper Files/hx_tiny_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files/hx_tiny_white.png -------------------------------------------------------------------------------- /HXHelper Files/imageMapResizer.min.js: -------------------------------------------------------------------------------- 1 | /*! Image Map Resizer (imageMapResizer.min.js ) - v1.0.7 - 2018-05-01 2 | * Desc: Resize HTML imageMap to scaled image. 3 | * Copyright: (c) 2018 David J. Bradshaw - dave@bradshaw.net 4 | * License: MIT 5 | */ 6 | 7 | !function(){"use strict";function a(){function a(){function a(a,d){function e(a){var d=1===(f=1-f)?"width":"height";return c[d]+Math.floor(Number(a)*b[d])}var f=0;j[d].coords=a.split(",").map(e).join(",")}var b={width:l.width/l.naturalWidth,height:l.height/l.naturalHeight},c={width:parseInt(window.getComputedStyle(l,null).getPropertyValue("padding-left"),10),height:parseInt(window.getComputedStyle(l,null).getPropertyValue("padding-top"),10)};k.forEach(a)}function b(a){return a.coords.replace(/ *, */g,",").replace(/ +/g,",")}function c(){clearTimeout(m),m=setTimeout(a,250)}function d(){l.width===l.naturalWidth&&l.height===l.naturalHeight||a()}function e(){l.addEventListener("load",a,!1),window.addEventListener("focus",a,!1),window.addEventListener("resize",c,!1),window.addEventListener("readystatechange",a,!1),document.addEventListener("fullscreenchange",a,!1)}function f(){return"function"==typeof i._resize}function g(a){return document.querySelector('img[usemap="'+a+'"]')}function h(){j=i.getElementsByTagName("area"),k=Array.prototype.map.call(j,b),l=g("#"+i.name)||g(i.name),i._resize=a}var i=this,j=null,k=null,l=null,m=null;f()?i._resize():(h(),e(),d())}function b(){function b(a){if(!a.tagName)throw new TypeError("Object is not a valid DOM element");if("MAP"!==a.tagName.toUpperCase())throw new TypeError("Expected tag, found <"+a.tagName+">.")}function c(c){c&&(b(c),a.call(c),d.push(c))}var d;return function(a){switch(d=[],typeof a){case"undefined":case"string":Array.prototype.forEach.call(document.querySelectorAll(a||"map"),c);break;case"object":c(a);break;default:throw new TypeError("Unexpected data type ("+typeof a+").")}return d}}"function"==typeof define&&define.amd?define([],b):"object"==typeof module&&"object"==typeof module.exports?module.exports=b():window.imageMapResize=b(),"jQuery"in window&&(jQuery.fn.imageMapResize=function(){return this.filter("map").each(a).end()})}(); 8 | //# sourceMappingURL=imageMapResizer.map 9 | -------------------------------------------------------------------------------- /HXHelper Files/introjs.css: -------------------------------------------------------------------------------- 1 | .introjs-overlay { 2 | position: absolute; 3 | z-index: 999999; 4 | background-color: #000; 5 | opacity: 0; 6 | background: -moz-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%); 7 | background: -webkit-gradient(radial,center center,0px,center center,100%,color-stop(0%,rgba(0,0,0,0.4)),color-stop(100%,rgba(0,0,0,0.9))); 8 | background: -webkit-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%); 9 | background: -o-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%); 10 | background: -ms-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%); 11 | background: radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%); 12 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#66000000',endColorstr='#e6000000',GradientType=1); 13 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; 14 | filter: alpha(opacity=50); 15 | -webkit-transition: all 0.3s ease-out; 16 | -moz-transition: all 0.3s ease-out; 17 | -ms-transition: all 0.3s ease-out; 18 | -o-transition: all 0.3s ease-out; 19 | transition: all 0.3s ease-out; 20 | } 21 | 22 | .introjs-fixParent { 23 | z-index: auto !important; 24 | opacity: 1.0 !important; 25 | position: absolute !important; 26 | -webkit-transform: none !important; 27 | -moz-transform: none !important; 28 | -ms-transform: none !important; 29 | -o-transform: none !important; 30 | transform: none !important; 31 | } 32 | 33 | .introjs-showElement, 34 | tr.introjs-showElement > td, 35 | tr.introjs-showElement > th { 36 | z-index: 9999999 !important; 37 | } 38 | 39 | .introjs-disableInteraction { 40 | z-index: 99999999 !important; 41 | position: absolute; 42 | } 43 | 44 | .introjs-relativePosition, 45 | tr.introjs-showElement > td, 46 | tr.introjs-showElement > th { 47 | position: relative; 48 | } 49 | 50 | .introjs-helperLayer { 51 | position: absolute; 52 | z-index: 9999998; 53 | background-color: #FFF; 54 | background-color: rgba(255,255,255,.9); 55 | border: 1px solid #777; 56 | border: 1px solid rgba(0,0,0,.5); 57 | border-radius: 4px; 58 | box-shadow: 0 2px 15px rgba(0,0,0,.4); 59 | -webkit-transition: all 0.3s ease-out; 60 | -moz-transition: all 0.3s ease-out; 61 | -ms-transition: all 0.3s ease-out; 62 | -o-transition: all 0.3s ease-out; 63 | transition: all 0.3s ease-out; 64 | } 65 | 66 | .introjs-tooltipReferenceLayer { 67 | position: absolute; 68 | visibility: hidden; 69 | z-index: 10000000; 70 | background-color: transparent; 71 | -webkit-transition: all 0.3s ease-out; 72 | -moz-transition: all 0.3s ease-out; 73 | -ms-transition: all 0.3s ease-out; 74 | -o-transition: all 0.3s ease-out; 75 | transition: all 0.3s ease-out; 76 | } 77 | 78 | .introjs-helperLayer *, 79 | .introjs-helperLayer *:before, 80 | .introjs-helperLayer *:after { 81 | -webkit-box-sizing: content-box; 82 | -moz-box-sizing: content-box; 83 | -ms-box-sizing: content-box; 84 | -o-box-sizing: content-box; 85 | box-sizing: content-box; 86 | } 87 | 88 | .introjs-helperLayer { 89 | padding-right: 10px; 90 | } 91 | 92 | .introjs-helperNumberLayer { 93 | position: absolute; 94 | visibility: visible; 95 | top: -16px; 96 | left: -16px; 97 | z-index: 9999999999 !important; 98 | padding: 2px; 99 | font-family: Arial, verdana, tahoma; 100 | font-size: 13px; 101 | font-weight: bold; 102 | color: white; 103 | text-align: center; 104 | text-shadow: 1px 1px 1px rgba(0,0,0,.3); 105 | background: #ff3019; /* Old browsers */ 106 | background: -webkit-linear-gradient(top, #ff3019 0%, #cf0404 100%); /* Chrome10+,Safari5.1+ */ 107 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ff3019), color-stop(100%, #cf0404)); /* Chrome,Safari4+ */ 108 | background: -moz-linear-gradient(top, #ff3019 0%, #cf0404 100%); /* FF3.6+ */ 109 | background: -ms-linear-gradient(top, #ff3019 0%, #cf0404 100%); /* IE10+ */ 110 | background: -o-linear-gradient(top, #ff3019 0%, #cf0404 100%); /* Opera 11.10+ */ 111 | background: linear-gradient(to bottom, #ff3019 0%, #cf0404 100%); /* W3C */ 112 | width: 20px; 113 | height:20px; 114 | line-height: 20px; 115 | border: 3px solid white; 116 | border-radius: 50%; 117 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3019', endColorstr='#cf0404', GradientType=0); /* IE6-9 */ 118 | filter: progid:DXImageTransform.Microsoft.Shadow(direction=135, strength=2, color=ff0000); /* IE10 text shadows */ 119 | box-shadow: 0 2px 5px rgba(0,0,0,.4); 120 | } 121 | 122 | .introjs-arrow { 123 | border: 5px solid white; 124 | content:''; 125 | position: absolute; 126 | } 127 | .introjs-arrow.top { 128 | top: -10px; 129 | border-top-color:transparent; 130 | border-right-color:transparent; 131 | border-bottom-color:white; 132 | border-left-color:transparent; 133 | } 134 | .introjs-arrow.top-right { 135 | top: -10px; 136 | right: 10px; 137 | border-top-color:transparent; 138 | border-right-color:transparent; 139 | border-bottom-color:white; 140 | border-left-color:transparent; 141 | } 142 | .introjs-arrow.top-middle { 143 | top: -10px; 144 | left: 50%; 145 | margin-left: -5px; 146 | border-top-color:transparent; 147 | border-right-color:transparent; 148 | border-bottom-color:white; 149 | border-left-color:transparent; 150 | } 151 | .introjs-arrow.right { 152 | right: -10px; 153 | top: 10px; 154 | border-top-color:transparent; 155 | border-right-color:transparent; 156 | border-bottom-color:transparent; 157 | border-left-color:white; 158 | } 159 | .introjs-arrow.right-bottom { 160 | bottom:10px; 161 | right: -10px; 162 | border-top-color:transparent; 163 | border-right-color:transparent; 164 | border-bottom-color:transparent; 165 | border-left-color:white; 166 | } 167 | .introjs-arrow.bottom { 168 | bottom: -10px; 169 | border-top-color:white; 170 | border-right-color:transparent; 171 | border-bottom-color:transparent; 172 | border-left-color:transparent; 173 | } 174 | .introjs-arrow.left { 175 | left: -10px; 176 | top: 10px; 177 | border-top-color:transparent; 178 | border-right-color:white; 179 | border-bottom-color:transparent; 180 | border-left-color:transparent; 181 | } 182 | .introjs-arrow.left-bottom { 183 | left: -10px; 184 | bottom:10px; 185 | border-top-color:transparent; 186 | border-right-color:white; 187 | border-bottom-color:transparent; 188 | border-left-color:transparent; 189 | } 190 | 191 | .introjs-tooltip { 192 | position: absolute; 193 | visibility: visible; 194 | padding: 10px; 195 | background-color: white; 196 | min-width: 200px; 197 | max-width: 300px; 198 | border-radius: 3px; 199 | box-shadow: 0 1px 10px rgba(0,0,0,.4); 200 | -webkit-transition: opacity 0.1s ease-out; 201 | -moz-transition: opacity 0.1s ease-out; 202 | -ms-transition: opacity 0.1s ease-out; 203 | -o-transition: opacity 0.1s ease-out; 204 | transition: opacity 0.1s ease-out; 205 | } 206 | 207 | .introjs-tooltipbuttons { 208 | text-align: right; 209 | white-space: nowrap; 210 | } 211 | 212 | /* 213 | Buttons style by http://nicolasgallagher.com/lab/css3-github-buttons/ 214 | Changed by Afshin Mehrabani 215 | */ 216 | .introjs-button { 217 | position: relative; 218 | overflow: visible; 219 | display: inline-block; 220 | padding: 0.3em 0.8em; 221 | border: 1px solid #d4d4d4; 222 | margin: 0; 223 | text-decoration: none; 224 | text-shadow: 1px 1px 0 #fff; 225 | font: 11px/normal sans-serif; 226 | color: #333; 227 | white-space: nowrap; 228 | cursor: pointer; 229 | outline: none; 230 | background-color: #ececec; 231 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f4f4f4), to(#ececec)); 232 | background-image: -moz-linear-gradient(#f4f4f4, #ececec); 233 | background-image: -o-linear-gradient(#f4f4f4, #ececec); 234 | background-image: linear-gradient(#f4f4f4, #ececec); 235 | -webkit-background-clip: padding; 236 | -moz-background-clip: padding; 237 | -o-background-clip: padding-box; 238 | /*background-clip: padding-box;*/ /* commented out due to Opera 11.10 bug */ 239 | -webkit-border-radius: 0.2em; 240 | -moz-border-radius: 0.2em; 241 | border-radius: 0.2em; 242 | /* IE hacks */ 243 | zoom: 1; 244 | *display: inline; 245 | margin-top: 10px; 246 | } 247 | 248 | .introjs-button:hover { 249 | border-color: #bcbcbc; 250 | text-decoration: none; 251 | box-shadow: 0px 1px 1px #e3e3e3; 252 | } 253 | 254 | .introjs-button:focus, 255 | .introjs-button:active { 256 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ececec), to(#f4f4f4)); 257 | background-image: -moz-linear-gradient(#ececec, #f4f4f4); 258 | background-image: -o-linear-gradient(#ececec, #f4f4f4); 259 | background-image: linear-gradient(#ececec, #f4f4f4); 260 | } 261 | 262 | /* overrides extra padding on button elements in Firefox */ 263 | .introjs-button::-moz-focus-inner { 264 | padding: 0; 265 | border: 0; 266 | } 267 | 268 | .introjs-skipbutton { 269 | margin-right: 5px; 270 | color: #7a7a7a; 271 | } 272 | 273 | .introjs-prevbutton { 274 | -webkit-border-radius: 0.2em 0 0 0.2em; 275 | -moz-border-radius: 0.2em 0 0 0.2em; 276 | border-radius: 0.2em 0 0 0.2em; 277 | border-right: none; 278 | } 279 | 280 | .introjs-nextbutton { 281 | -webkit-border-radius: 0 0.2em 0.2em 0; 282 | -moz-border-radius: 0 0.2em 0.2em 0; 283 | border-radius: 0 0.2em 0.2em 0; 284 | } 285 | 286 | .introjs-disabled, .introjs-disabled:hover, .introjs-disabled:focus { 287 | color: #9a9a9a; 288 | border-color: #d4d4d4; 289 | box-shadow: none; 290 | cursor: default; 291 | background-color: #f4f4f4; 292 | background-image: none; 293 | text-decoration: none; 294 | } 295 | 296 | .introjs-bullets { 297 | text-align: center; 298 | } 299 | .introjs-bullets ul { 300 | clear: both; 301 | margin: 15px auto 0; 302 | padding: 0; 303 | display: inline-block; 304 | } 305 | .introjs-bullets ul li { 306 | list-style: none; 307 | float: left; 308 | margin: 0 2px; 309 | } 310 | .introjs-bullets ul li a { 311 | display: block; 312 | width: 6px; 313 | height: 6px; 314 | background: #ccc; 315 | border-radius: 10px; 316 | -moz-border-radius: 10px; 317 | -webkit-border-radius: 10px; 318 | text-decoration: none; 319 | } 320 | .introjs-bullets ul li a:hover { 321 | background: #999; 322 | } 323 | .introjs-bullets ul li a.active { 324 | background: #999; 325 | } 326 | 327 | .introjs-progress { 328 | overflow: hidden; 329 | height: 10px; 330 | margin: 10px 0 5px 0; 331 | border-radius: 4px; 332 | background-color: #ecf0f1 333 | } 334 | .introjs-progressbar { 335 | float: left; 336 | width: 0%; 337 | height: 100%; 338 | font-size: 10px; 339 | line-height: 10px; 340 | text-align: center; 341 | background-color: #08c; 342 | } 343 | 344 | .introjsFloatingElement { 345 | position: absolute; 346 | height: 0; 347 | width: 0; 348 | left: 50%; 349 | top: 50%; 350 | } 351 | 352 | .introjs-fixedTooltip { 353 | position: fixed; 354 | } 355 | 356 | .introjs-hint { 357 | position: absolute; 358 | background: transparent; 359 | width: 20px; 360 | height: 15px; 361 | } 362 | 363 | .introjs-hidehint { 364 | display: none; 365 | } 366 | 367 | .introjs-fixedhint { 368 | position: fixed; 369 | } 370 | 371 | .introjs-hint:hover > .introjs-hint-pulse { 372 | border: 5px solid rgba(60, 60, 60, 0.57); 373 | } 374 | 375 | .introjs-hint-pulse { 376 | width: 10px; 377 | height: 10px; 378 | border: 5px solid rgba(60, 60, 60, 0.27); 379 | -webkit-border-radius: 30px; 380 | -moz-border-radius: 30px; 381 | border-radius: 30px; 382 | background-color: rgba(136, 136, 136, 0.24); 383 | z-index: 10; 384 | position: absolute; 385 | -webkit-transition: all 0.2s ease-out; 386 | -moz-transition: all 0.2s ease-out; 387 | -ms-transition: all 0.2s ease-out; 388 | -o-transition: all 0.2s ease-out; 389 | transition: all 0.2s ease-out; 390 | } 391 | 392 | .introjs-hint-dot { 393 | border: 10px solid rgba(146, 146, 146, 0.36); 394 | background: transparent; 395 | -webkit-border-radius: 60px; 396 | -moz-border-radius: 60px; 397 | border-radius: 60px; 398 | height: 50px; 399 | width: 50px; 400 | -webkit-animation: introjspulse 3s ease-out; 401 | -moz-animation: introjspulse 3s ease-out; 402 | animation: introjspulse 3s ease-out; 403 | -webkit-animation-iteration-count: infinite; 404 | -moz-animation-iteration-count: infinite; 405 | animation-iteration-count: infinite; 406 | position: absolute; 407 | top: -25px; 408 | left: -25px; 409 | z-index: 1; 410 | opacity: 0; 411 | } 412 | 413 | @-moz-keyframes intrjspulse { 414 | 0% { 415 | -moz-transform: scale(0); 416 | opacity: 0.0; 417 | } 418 | 25% { 419 | -moz-transform: scale(0); 420 | opacity: 0.1; 421 | } 422 | 50% { 423 | -moz-transform: scale(0.1); 424 | opacity: 0.3; 425 | } 426 | 75% { 427 | -moz-transform: scale(0.5); 428 | opacity: 0.5; 429 | } 430 | 100% { 431 | -moz-transform: scale(1); 432 | opacity: 0.0; 433 | } 434 | } 435 | 436 | @-webkit-keyframes "introjspulse" { 437 | 0% { 438 | -webkit-transform: scale(0); 439 | opacity: 0.0; 440 | } 441 | 25% { 442 | -webkit-transform: scale(0); 443 | opacity: 0.1; 444 | } 445 | 50% { 446 | -webkit-transform: scale(0.1); 447 | opacity: 0.3; 448 | } 449 | 75% { 450 | -webkit-transform: scale(0.5); 451 | opacity: 0.5; 452 | } 453 | 100% { 454 | -webkit-transform: scale(1); 455 | opacity: 0.0; 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /HXHelper Files/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.22.0 2 | https://prismjs.com/download.html#themes=prism-coy&languages=markup+css+clike+javascript+json+latex+matlab+python+r&plugins=keep-markup */ 3 | /** 4 | * prism.js Coy theme for JavaScript, CoffeeScript, CSS and HTML 5 | * Based on https://github.com/tshedor/workshop-wp-theme (Example: http://workshop.kansan.com/category/sessions/basics or http://workshop.timshedor.com/category/sessions/basics); 6 | * @author Tim Shedor 7 | */ 8 | 9 | code[class*="language-"], 10 | pre[class*="language-"] { 11 | color: black; 12 | background: none; 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | font-size: 1em; 15 | text-align: left; 16 | white-space: pre; 17 | word-spacing: normal; 18 | word-break: normal; 19 | word-wrap: normal; 20 | line-height: 1.5; 21 | 22 | -moz-tab-size: 4; 23 | -o-tab-size: 4; 24 | tab-size: 4; 25 | 26 | -webkit-hyphens: none; 27 | -moz-hyphens: none; 28 | -ms-hyphens: none; 29 | hyphens: none; 30 | } 31 | 32 | /* Code blocks */ 33 | pre[class*="language-"] { 34 | position: relative; 35 | margin: .5em 0; 36 | overflow: visible; 37 | padding: 0; 38 | } 39 | pre[class*="language-"]>code { 40 | position: relative; 41 | border-left: 10px solid #358ccb; 42 | box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf; 43 | background-color: #fdfdfd; 44 | background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); 45 | background-size: 3em 3em; 46 | background-origin: content-box; 47 | background-attachment: local; 48 | } 49 | 50 | code[class*="language-"] { 51 | max-height: inherit; 52 | height: inherit; 53 | padding: 0 1em; 54 | display: block; 55 | overflow: auto; 56 | } 57 | 58 | /* Margin bottom to accommodate shadow */ 59 | :not(pre) > code[class*="language-"], 60 | pre[class*="language-"] { 61 | background-color: #fdfdfd; 62 | -webkit-box-sizing: border-box; 63 | -moz-box-sizing: border-box; 64 | box-sizing: border-box; 65 | margin-bottom: 1em; 66 | } 67 | 68 | /* Inline code */ 69 | :not(pre) > code[class*="language-"] { 70 | position: relative; 71 | padding: .2em; 72 | border-radius: 0.3em; 73 | color: #c92c2c; 74 | border: 1px solid rgba(0, 0, 0, 0.1); 75 | display: inline; 76 | white-space: normal; 77 | } 78 | 79 | pre[class*="language-"]:before, 80 | pre[class*="language-"]:after { 81 | content: ''; 82 | z-index: -2; 83 | display: block; 84 | position: absolute; 85 | bottom: 0.75em; 86 | left: 0.18em; 87 | width: 40%; 88 | height: 20%; 89 | max-height: 13em; 90 | box-shadow: 0px 13px 8px #979797; 91 | -webkit-transform: rotate(-2deg); 92 | -moz-transform: rotate(-2deg); 93 | -ms-transform: rotate(-2deg); 94 | -o-transform: rotate(-2deg); 95 | transform: rotate(-2deg); 96 | } 97 | 98 | pre[class*="language-"]:after { 99 | right: 0.75em; 100 | left: auto; 101 | -webkit-transform: rotate(2deg); 102 | -moz-transform: rotate(2deg); 103 | -ms-transform: rotate(2deg); 104 | -o-transform: rotate(2deg); 105 | transform: rotate(2deg); 106 | } 107 | 108 | .token.comment, 109 | .token.block-comment, 110 | .token.prolog, 111 | .token.doctype, 112 | .token.cdata { 113 | color: #7D8B99; 114 | } 115 | 116 | .token.punctuation { 117 | color: #5F6364; 118 | } 119 | 120 | .token.property, 121 | .token.tag, 122 | .token.boolean, 123 | .token.number, 124 | .token.function-name, 125 | .token.constant, 126 | .token.symbol, 127 | .token.deleted { 128 | color: #c92c2c; 129 | } 130 | 131 | .token.selector, 132 | .token.attr-name, 133 | .token.string, 134 | .token.char, 135 | .token.function, 136 | .token.builtin, 137 | .token.inserted { 138 | color: #2f9c0a; 139 | } 140 | 141 | .token.operator, 142 | .token.entity, 143 | .token.url, 144 | .token.variable { 145 | color: #a67f59; 146 | background: rgba(255, 255, 255, 0.5); 147 | } 148 | 149 | .token.atrule, 150 | .token.attr-value, 151 | .token.keyword, 152 | .token.class-name { 153 | color: #1990b8; 154 | } 155 | 156 | .token.regex, 157 | .token.important { 158 | color: #e90; 159 | } 160 | 161 | .language-css .token.string, 162 | .style .token.string { 163 | color: #a67f59; 164 | background: rgba(255, 255, 255, 0.5); 165 | } 166 | 167 | .token.important { 168 | font-weight: normal; 169 | } 170 | 171 | .token.bold { 172 | font-weight: bold; 173 | } 174 | .token.italic { 175 | font-style: italic; 176 | } 177 | 178 | .token.entity { 179 | cursor: help; 180 | } 181 | 182 | .token.namespace { 183 | opacity: .7; 184 | } 185 | 186 | @media screen and (max-width: 767px) { 187 | pre[class*="language-"]:before, 188 | pre[class*="language-"]:after { 189 | bottom: 14px; 190 | box-shadow: none; 191 | } 192 | 193 | } 194 | 195 | /* Plugin styles: Line Numbers */ 196 | pre[class*="language-"].line-numbers.line-numbers { 197 | padding-left: 0; 198 | } 199 | 200 | pre[class*="language-"].line-numbers.line-numbers code { 201 | padding-left: 3.8em; 202 | } 203 | 204 | pre[class*="language-"].line-numbers.line-numbers .line-numbers-rows { 205 | left: 0; 206 | } 207 | 208 | /* Plugin styles: Line Highlight */ 209 | pre[class*="language-"][data-line] { 210 | padding-top: 0; 211 | padding-bottom: 0; 212 | padding-left: 0; 213 | } 214 | pre[data-line] code { 215 | position: relative; 216 | padding-left: 4em; 217 | } 218 | pre .line-highlight { 219 | margin-top: 0; 220 | } 221 | 222 | -------------------------------------------------------------------------------- /HXHelper Files/quote-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files/quote-left.png -------------------------------------------------------------------------------- /HXHelper Files/quote-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files/quote-right.png -------------------------------------------------------------------------------- /HXHelper Files/slick-theme.css: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | /* Slider */ 3 | .slick-loading .slick-list 4 | { 5 | background: #fff url('./ajax-loader.gif') center center no-repeat; 6 | } 7 | 8 | /* Icons */ 9 | @font-face 10 | { 11 | font-family: 'slick'; 12 | font-weight: normal; 13 | font-style: normal; 14 | 15 | src: url('./slick.eot'); 16 | src: url('./slick.eot?#iefix') format('embedded-opentype'), url('./slick.woff') format('woff'), url('./slick.ttf') format('truetype'), url('./slick.svg#slick') format('svg'); 17 | } 18 | /* Arrows */ 19 | .slick-prev, 20 | .slick-next 21 | { 22 | font-size: 0; 23 | line-height: 0; 24 | 25 | position: absolute; 26 | top: 50%; 27 | 28 | display: block; 29 | 30 | width: 20px; 31 | height: 20px; 32 | padding: 0; 33 | -webkit-transform: translate(0, -50%); 34 | -ms-transform: translate(0, -50%); 35 | transform: translate(0, -50%); 36 | 37 | cursor: pointer; 38 | 39 | color: transparent; 40 | border: none; 41 | outline: none; 42 | background: transparent; 43 | } 44 | .slick-prev:hover, 45 | .slick-prev:focus, 46 | .slick-next:hover, 47 | .slick-next:focus 48 | { 49 | color: transparent; 50 | outline: none; 51 | background: transparent; 52 | } 53 | .slick-prev:hover:before, 54 | .slick-prev:focus:before, 55 | .slick-next:hover:before, 56 | .slick-next:focus:before 57 | { 58 | opacity: 1; 59 | } 60 | .slick-prev.slick-disabled:before, 61 | .slick-next.slick-disabled:before 62 | { 63 | opacity: .25; 64 | } 65 | 66 | .slick-prev:before, 67 | .slick-next:before 68 | { 69 | font-family: 'slick'; 70 | font-size: 20px; 71 | line-height: 1; 72 | 73 | opacity: .75; 74 | color: white; 75 | 76 | -webkit-font-smoothing: antialiased; 77 | -moz-osx-font-smoothing: grayscale; 78 | } 79 | 80 | .slick-prev 81 | { 82 | left: -25px; 83 | } 84 | [dir='rtl'] .slick-prev 85 | { 86 | right: -25px; 87 | left: auto; 88 | } 89 | .slick-prev:before 90 | { 91 | content: '◀'; 92 | } 93 | [dir='rtl'] .slick-prev:before 94 | { 95 | content: '▶'; 96 | } 97 | 98 | .slick-next 99 | { 100 | right: -25px; 101 | } 102 | [dir='rtl'] .slick-next 103 | { 104 | right: auto; 105 | left: -25px; 106 | } 107 | .slick-next:before 108 | { 109 | content: '▶'; 110 | } 111 | [dir='rtl'] .slick-next:before 112 | { 113 | content: '◀'; 114 | } 115 | 116 | /* Dots */ 117 | .slick-dotted.slick-slider 118 | { 119 | margin-bottom: 10px; 120 | } 121 | 122 | .slick-dots 123 | { 124 | position: absolute; 125 | bottom: -25px; 126 | 127 | display: block; 128 | 129 | width: 100%; 130 | padding: 0; 131 | margin: 0; 132 | 133 | list-style: none; 134 | 135 | text-align: center; 136 | } 137 | .slick-dots li 138 | { 139 | position: relative; 140 | 141 | display: inline-block; 142 | 143 | width: 20px; 144 | height: 20px; 145 | margin: 0 5px; 146 | padding: 0; 147 | 148 | cursor: pointer; 149 | } 150 | .slick-dots li button 151 | { 152 | font-size: 0; 153 | line-height: 0; 154 | 155 | display: block; 156 | 157 | width: 20px; 158 | height: 20px; 159 | padding: 5px; 160 | 161 | cursor: pointer; 162 | 163 | color: transparent; 164 | border: 0; 165 | outline: none; 166 | background: transparent; 167 | } 168 | .slick-dots li button:hover, 169 | .slick-dots li button:focus 170 | { 171 | outline: none; 172 | } 173 | .slick-dots li button:hover:before, 174 | .slick-dots li button:focus:before 175 | { 176 | opacity: 1; 177 | } 178 | .slick-dots li button:before 179 | { 180 | font-family: 'slick'; 181 | font-size: 6px; 182 | line-height: 20px; 183 | 184 | position: absolute; 185 | top: 0; 186 | left: 0; 187 | 188 | width: 20px; 189 | height: 20px; 190 | 191 | content: '•'; 192 | text-align: center; 193 | 194 | opacity: .25; 195 | color: #189; 196 | 197 | -webkit-font-smoothing: antialiased; 198 | -moz-osx-font-smoothing: grayscale; 199 | } 200 | .slick-dots li.slick-active button:before 201 | { 202 | opacity: .75; 203 | color: #189; 204 | } 205 | -------------------------------------------------------------------------------- /HXHelper Files/slick.css: -------------------------------------------------------------------------------- 1 | /* Slider */ 2 | .slick-slider 3 | { 4 | position: relative; 5 | 6 | display: block; 7 | box-sizing: border-box; 8 | 9 | -webkit-user-select: none; 10 | -moz-user-select: none; 11 | -ms-user-select: none; 12 | user-select: none; 13 | 14 | -webkit-touch-callout: none; 15 | -khtml-user-select: none; 16 | -ms-touch-action: pan-y; 17 | touch-action: pan-y; 18 | -webkit-tap-highlight-color: transparent; 19 | } 20 | 21 | .slick-list 22 | { 23 | position: relative; 24 | 25 | display: block; 26 | overflow: hidden; 27 | 28 | margin: 0; 29 | padding: 0; 30 | } 31 | .slick-list:focus 32 | { 33 | outline: none; 34 | } 35 | .slick-list.dragging 36 | { 37 | cursor: pointer; 38 | cursor: hand; 39 | } 40 | 41 | .slick-slider .slick-track, 42 | .slick-slider .slick-list 43 | { 44 | -webkit-transform: translate3d(0, 0, 0); 45 | -moz-transform: translate3d(0, 0, 0); 46 | -ms-transform: translate3d(0, 0, 0); 47 | -o-transform: translate3d(0, 0, 0); 48 | transform: translate3d(0, 0, 0); 49 | } 50 | 51 | .slick-track 52 | { 53 | position: relative; 54 | top: 0; 55 | left: 0; 56 | 57 | display: block; 58 | margin-left: auto; 59 | margin-right: auto; 60 | } 61 | .slick-track:before, 62 | .slick-track:after 63 | { 64 | display: table; 65 | 66 | content: ''; 67 | } 68 | .slick-track:after 69 | { 70 | clear: both; 71 | } 72 | .slick-loading .slick-track 73 | { 74 | visibility: hidden; 75 | } 76 | 77 | .slick-slide 78 | { 79 | display: none; 80 | float: left; 81 | 82 | height: 100%; 83 | min-height: 1px; 84 | } 85 | [dir='rtl'] .slick-slide 86 | { 87 | float: right; 88 | } 89 | .slick-slide img 90 | { 91 | display: block; 92 | } 93 | .slick-slide.slick-loading img 94 | { 95 | display: none; 96 | } 97 | .slick-slide.dragging img 98 | { 99 | pointer-events: none; 100 | } 101 | .slick-initialized .slick-slide 102 | { 103 | display: block; 104 | } 105 | .slick-loading .slick-slide 106 | { 107 | visibility: hidden; 108 | } 109 | .slick-vertical .slick-slide 110 | { 111 | display: block; 112 | 113 | height: auto; 114 | 115 | border: 1px solid transparent; 116 | } 117 | .slick-arrow.slick-hidden { 118 | display: none; 119 | } 120 | -------------------------------------------------------------------------------- /HXHelper Files/slick.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files/slick.eot -------------------------------------------------------------------------------- /HXHelper Files/slick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by Fontastic.me 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /HXHelper Files/slick.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files/slick.ttf -------------------------------------------------------------------------------- /HXHelper Files/slick.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files/slick.woff -------------------------------------------------------------------------------- /HXHelper Files/summernote.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files/summernote.eot -------------------------------------------------------------------------------- /HXHelper Files/summernote.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files/summernote.ttf -------------------------------------------------------------------------------- /HXHelper Files/summernote.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files/summernote.woff -------------------------------------------------------------------------------- /HXHelper Files/summernote.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/HXHelper Files/summernote.woff2 -------------------------------------------------------------------------------- /HXHelper Files/word_cloud_access.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | // Only do stuff once the word clouds actually appear. 3 | let completed_time = 0; 4 | let empty_time = 0; 5 | let interval = 250; 6 | let loop_timeout = 3000; // miliseconds 7 | let current_counts = []; 8 | let timers = []; 9 | 10 | function completeCloudChecker() { 11 | console.debug('complete time ' + completed_time); 12 | if ( 13 | $('.xblock-student_view-word_cloud .word_cloud > svg > g > g').length > 0 14 | ) { 15 | parseClouds(); 16 | clearInterval(waitForCompletedClouds); 17 | } 18 | if (completed_time > loop_timeout) { 19 | clearInterval(waitForCompletedClouds); 20 | } 21 | completed_time += interval; 22 | } 23 | 24 | function emptyCloudChecker() { 25 | console.debug('empty time ' + empty_time); 26 | if ($('.xblock-student_view-word_cloud button.save').length > 0) { 27 | completed_time = 0; 28 | empty_time = 0; 29 | $('.xblock-student_view-word_cloud button.save').on('click', function () { 30 | console.log('clicky click'); 31 | completed_time = 0; 32 | waitForCompletedClouds = setInterval(completeCloudChecker, interval); 33 | }); 34 | clearInterval(waitForEmptyClouds); 35 | } 36 | if (empty_time > loop_timeout) { 37 | clearInterval(waitForEmptyClouds); 38 | } 39 | empty_time += interval; 40 | } 41 | 42 | let waitForCompletedClouds = setInterval(completeCloudChecker, interval); 43 | let waitForEmptyClouds = setInterval(emptyCloudChecker, interval); 44 | 45 | function parseClouds() { 46 | console.debug('parseClouds'); 47 | // Loop through all the word clouds. 48 | $('.xblock-student_view-word_cloud').each(function () { 49 | console.debug(this); 50 | let result_box = $(this).find('.total_num_words'); 51 | let word_list = []; 52 | 53 | // Scraping the text and getting word/percent pairs 54 | // Might want to sort this eventually, but right now it's pre-sorted. 55 | $(this) 56 | .find('.word_cloud > svg > g > g') 57 | .each(function () { 58 | word_list.push({ 59 | word: $(this).find('text').text(), 60 | percent: Number($(this).find('title').text().slice(0, -1)), 61 | }); 62 | }); 63 | 64 | word_list.sort((a, b) => b.percent - a.percent); 65 | 66 | console.debug(word_list); 67 | 68 | // Don't run on empty word clouds. 69 | if (word_list.length === 0) { 70 | return false; 71 | } 72 | 73 | // Put a summary into the box. 74 | result_box.empty(); 75 | result_box.append('

Top Words

'); 76 | 77 | let num_top_items = 5; 78 | let top_list = $(''); 79 | for (let i = 0; i < num_top_items; i++) { 80 | top_list.append( 81 | '
  • ' + word_list[i].word + ': ' + word_list[i].percent + '%
  • ' 82 | ); 83 | } 84 | result_box.append(top_list); 85 | 86 | // Add a collapsible bit with the rest of the info. 87 | let details_tag = $('
    '); 88 | let summary_tag = $('Show full word list'); 89 | let rest_of_list = $(''); 90 | for (let i = num_top_items; i < word_list.length; i++) { 91 | rest_of_list.append( 92 | '
  • ' + word_list[i].word + ': ' + word_list[i].percent + '%
  • ' 93 | ); 94 | } 95 | details_tag.append(summary_tag); 96 | details_tag.append(rest_of_list); 97 | result_box.append(details_tag); 98 | }); 99 | } 100 | }); 101 | -------------------------------------------------------------------------------- /HXHelper Files/word_cloud_access_new.js: -------------------------------------------------------------------------------- 1 | var hxWordCloudHandler = (function(){ 2 | 3 | console.debug("working"); 4 | 5 | // A list of DOM elements. Keeping track of all the word clouds. 6 | let word_cloud_list = []; 7 | let word_cloud_observers = []; 8 | 9 | // Check for the components we want. 10 | // If they already exist, running a MutationObserver won't help. 11 | if($('.word_cloud').length === 0){ 12 | 13 | }else{ 14 | $('.word_cloud').each(function(){ 15 | // For completed clouds: 16 | $(this).find('svg > g > g').each(function(){ 17 | parseCloud(this); 18 | }); 19 | // For clouds that haven't been submitted yet: 20 | $(this).find('button.save').each(function(){ 21 | 22 | }); 23 | }); 24 | } 25 | 26 | // Set mutation observer on the
    tag. 27 | const main_tag = document.querySelector('main'); 28 | const config = { childList: true, subtree: true }; 29 | const main_observer = new MutationObserver(mainCallback); 30 | main_observer.observe(main_tag, config); 31 | // Close it out after 10 seconds 32 | let stopObserving = setTimeout(function(){ 33 | main_observer.disconnect(); 34 | console.log("Done observing main"); 35 | }, 10000); 36 | 37 | function mainCallback(mutationsList, main_observer) { 38 | // Use traditional 'for loops' for IE 11 39 | for(const mutation of mutationsList) { 40 | if (mutation.type === 'childList') { 41 | // console.debug('A child node has been added or removed.'); 42 | // console.debug(mutation.target); 43 | // Check this element against the list of word clouds. 44 | let class_list = Array.from(mutation.target.classList); 45 | if(class_list.includes('word_cloud')){ 46 | // The container for word clouds has an id that starts with "word_cloud" 47 | if(mutation.target.id.includes('word_cloud')){ 48 | // If it's a new element, add it and start a word cloud observer on it. 49 | if(!word_cloud_list.includes(mutation.target)){ 50 | word_cloud_list.push(mutation.target); 51 | console.debug("new word cloud: "); 52 | console.debug(mutation.target); 53 | let n = word_cloud_list.length - 1; 54 | word_cloud_observers[n] = new MutationObserver(wordCallback); 55 | word_cloud_observers[n].observe(main_tag, config); 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | // In each word cloud, look for two possible mutations (additions): 64 | // 'button.save' indicates an incomplete cloud. 65 | // Keep those observers running. 66 | // When button.save disappears, start an observer for the cloud again. 67 | // Look for a completed cloud. (Close out after 5 sec again.) 68 | 69 | // '.word_cloud > svg > g > g' indicates a completed cloud. 70 | // run parseCloud on it. 71 | // Close out those observers after 5 seconds. 72 | 73 | // For a completed cloud: 74 | function parseCloud(cloud) { 75 | console.debug('parseCloud'); 76 | console.debug(cloud); 77 | let result_box = $(cloud).find('.total_num_words'); 78 | let word_list = []; 79 | 80 | // Scraping the text and getting word/percent pairs 81 | // Might want to sort this eventually, but right now it's pre-sorted. 82 | $(cloud) 83 | .find('.word_cloud > svg > g > g') 84 | .each(function () { 85 | word_list.push({ 86 | word: $(this).find('text').text(), 87 | percent: Number($(this).find('title').text().slice(0, -1)), 88 | }); 89 | }); 90 | 91 | word_list.sort((a, b) => b.percent - a.percent); 92 | 93 | console.debug(word_list); 94 | 95 | // Don't run on empty word clouds. 96 | if (word_list.length === 0) { 97 | return false; 98 | } 99 | 100 | // Put a summary into the box. 101 | result_box.empty(); 102 | result_box.append('

    Top Words

    '); 103 | 104 | let num_top_items = 5; 105 | let top_list = $(''); 106 | for (let i = 0; i < num_top_items; i++) { 107 | top_list.append( 108 | '
  • ' + word_list[i].word + ': ' + word_list[i].percent + '%
  • ' 109 | ); 110 | } 111 | result_box.append(top_list); 112 | 113 | // Add a collapsible bit with the rest of the info. 114 | let details_tag = $('
    '); 115 | let summary_tag = $('Show full word list'); 116 | let rest_of_list = $(''); 117 | for (let i = num_top_items; i < word_list.length; i++) { 118 | rest_of_list.append( 119 | '
  • ' + word_list[i].word + ': ' + word_list[i].percent + '%
  • ' 120 | ); 121 | } 122 | details_tag.append(summary_tag); 123 | details_tag.append(rest_of_list); 124 | result_box.append(details_tag); 125 | } 126 | 127 | })(); 128 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Colin Fredericks, President and Fellows of Harvard College 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /datamap/File-BlankMap-World6.svg - Wikimedia Commons.webloc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | URL 6 | https://commons.wikimedia.org/wiki/File:BlankMap-World6.svg 7 | 8 | 9 | -------------------------------------------------------------------------------- /datamap/File-Reported Bigfoot sightings (updated).svg - Wikimedia Commons.webloc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | URL 6 | https://commons.wikimedia.org/wiki/File:Reported_Bigfoot_sightings_(updated).svg 7 | 8 | 9 | -------------------------------------------------------------------------------- /datamap/For_EdX.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 22 | 23 |

    Check out the map below, which you should describe in text here so that you don't need to write separate alt text for it.

    24 | 25 |

    Map: DALYs for Malaria worldwide

    26 |
    27 | ");c.attr("src",t),c.attr("id","hxbackpackframe"),c.attr("aria-hidden","true"),c.attr("tabindex","-1"),c.css("display","none"),$("body").append(c)}if(O.length){$("head").append($(''));let h=$('
    Editor loading...
    '),d=$('');h.prepend(d),O.append(h),hxBackpackLoaded&&void 0===s?s=new HXEditor(e.useBackpack,e.HXEditorOptions):(console.log("Backpack: "+hxBackpackLoaded),console.log("HXED: "+typeof s=="undefined"?"undefined":"ok"))}T.length>0&&w&&($("head").append($('')),n=new HXVideoLinks(e.VidLinkOptions),$(".jumptime").on("click tap",function(e){e.preventDefault();let t=1;void 0!==$(this).attr("data-for-vidnum")&&(t=$(this).attr("data-for-vidnum"));let i=D($(this).attr("href"));V(t,i)}),0!==window.HXPUPTimer.length&&(l=new HXPopUpProblems(a.PUPOptions,window.HXPUPTimer)),0!==window.HXChimeTimer.length&&(o=new HXVideoChime(a.ChimeOptions,window.HXChimeTimer))),m.length&&e.highlightCode&&$("head").append($(''));let p=Array.from(document.getElementsByClassName("hx-survey-url"));if(p.length){U({hx_surveys:"found"});let u=Array.from(document.getElementsByClassName("hx-survey-url")),f=u.map(function(e){return"a"===e.tagName.toLowerCase()?e.href:"iframe"===e.tagName.toLowerCase()?e.src:e.innerText}),x=f.map(e=>e+"&university="+g.institution+"&course_id="+g.id+"&course_run="+g.run);u.forEach(function(e,t){"a"===e.tagName.toLowerCase()?e.href=x[t]:"iframe"===e.tagName.toLowerCase()?e.src=x[t]:e.innerText=x[t]})}$("a.hx-vidtime").on("click tap",function(){let e=D($(this).attr("data-time")),t=$(this).attr("href"),i=t.slice(t.indexOf("#video")).replace("#video",""),s=t.slice(t.indexOf("/jump_to_id/")+13,t.indexOf("#video"));0===t.indexOf("#")?(U({"link starts video at time":e}),n.jumpToTime(i,e)):(localStorage.HXVideoLinkGo="true",localStorage.HXVideoLinkUnit=s,localStorage.HXVideoLinkNumber=i,localStorage.HXVideoLinkTime=e,U({"storing video info for jump":{unit:s,"video number":i,time:e}}))}),e.collapsedNav&&($(".sequence-nav").hide(),$(".sequence > .path").hide(),$("h3.unit-title").hide());let _=$("#hx-tint");_.length&&$(_).on("click tap",function(){$("p").animate({color:"blue"})}),e.openPageDiscussion&&$(".discussion-show.btn").click(),e.markExternalLinks&&(U("marking external links"),$(".vert .xblock a, .static_tab_wrapper .xblock a").each(function(e,t){var i=$(t).attr("href");P(i)&&$(t).append(' External link')}));let C,H;j("hx-togglebutton","hx-toggletarget","hx-toggleremember"),$("[class^=hx-highlighter]").on("click tap",function(){let t=B(this.className,"hx-highlighter");e.highlightState?$(".hx-highlight"+t).css({"background-color":e.highlightColor,transition:"background 0.2s"}):$(".hx-highlight"+t).css({"background-color":e.highlightBackground,transition:"background 0.2s"}),e.highlightState=!e.highlightState,U({"Highlight button":"pressed","Highlight number":t})}),e.makeTOC&&($(".edx-notes-wrapper-content").length?$(".edx-notes-wrapper-content:first-of-type").prepend('
    '):$($("#seq_content .vert .xblock")[0]).prepend('
    '),C="

    Table of Contents

  • '+t+"
  • ")),$(this).is("h4")&&(i=4,$(H[e-1]).is("h3")?(e>0&&C.slice(0,C.length-5),C+='",$("#auto_toc").append(C));$(".hx-popup-opener").length&&($("map").each(function(e){let t=[];$(this).find("area").each(function(e){t.push('
  • '+this.title+"
  • ")});let i="
      "+t.join("")+"
    ";i="

    Clickable Areas:

    "+i;let s=$(this).data("make-accessible-list");"false"===s||!1===s||$(this).after(i)}),$(".hx-popup-opener").on("click tap",function(e){let t=this.className.split(/\s+/)[0];$("div."+t).dialog({dialogClass:"hx-popup-dialog",title:$(this).attr("title"),position:{my:"center",at:"center",of:$(e.target)},show:{effect:"fade",duration:200},hide:{effect:"fade",duration:100},buttons:{Close:function(){$(this).dialog("close")}}},function(e){$("div."+e).css({display:""}),alert(e)}),U({"Pop-up Dialog":"opened",Dialog:t})}));let L=$('span[class^="hx-footnote"]');if(L.length&&function e(t){let i,s,n,l,o;for(let a=0;a"),l=$('h3:contains("Footnote")'),o=$(l[l.length-1]).parent(),$(n).detach().appendTo(o),$(i).wrap('').wrap(),n.prepend(''),n.append('

    (back)

    ')}(L),y.length&&($("head").append($('')),console.log(e.textSliderOptions),i=new HXTextSlider(e.textSliderOptions),U({"dynamic slider":"created"})),k.length){$("head").append($('')),$("head").append($(''));let S=0,X=setInterval(function(){void 0!==$.fn.slick&&(clearInterval(X),k.slick(e.slickOptions),U({slider:"created"})),++S>30&&(clearInterval(X),U("Slick not ready after 30 tries. Giving up."))},100)}v.length&&b.length&&($("head").append($('')),$("head").append($('')),v.slick(e.slickNavOptions),b.slick(e.slickBigOptions),U({"paired slider":"created"}))}function C(e,t){console.log(e,t);let i;"problem_check"===e?i=t.split("_")[1]:"problem_show"===e&&(i=t.problem.split("@")[2]);let s=$('[id^="'+i+'"]').parent()[0];setTimeout(function(){X("hx-togglebutton","hx-toggletarget"),m.length&&x.highlightCode&&Prism.highlightAllUnder(s)},500)}function H(e,t){e="toggle_"+e,console.log("set vis of "+e+" to "+t);try{let i=JSON.parse(localStorage.HXToggleMemory);i[e]=t,localStorage.HXToggleMemory=JSON.stringify(i)}catch(s){console.log("error"),console.log(s),localStorage.HXToggleMemory=JSON.stringify({[e]:t})}}function L(e,t,i,s){console.log("hiding"),$.ui?$("."+t+s).hide("slide",{direction:"up"},"fast"):$("."+t+s).hide(),$("."+e+s).attr("aria-expanded","false"),$("."+t+s).attr("aria-hidden","true"),$(".hx-toggleset ."+e+s).siblings().attr("disabled",!1),i&&H(s,!1)}function S(e,t,i,s){console.log("showing"),$.ui?$("."+t+s).show("slide",{direction:"up"},"fast"):$("."+t+s).show(),$("."+e+s).attr("aria-expanded","true"),$("."+t+s).attr("aria-hidden","false"),$(".hx-toggleset ."+e+s).siblings().attr("disabled",!0),i&&H(s,!0)}function X(e,t){console.log("setting visibility from memory");let i=[];$("[class*="+t+"]").each(function(){let s=B(this.className,t);if(console.log("toggle "+s+"..."),-1===i.indexOf(s)){i.push(s);let n=function e(t){t="toggle_"+t;let i=!1;try{i=JSON.parse(localStorage.HXToggleMemory)[t]}catch(s){}return i}(s);null!=n&&(n?(console.log("...should be shown. Showing it."),S(e,t,!1,s)):(console.log("...should be hidden. Hiding it."),L(e,t,!1,s)))}else console.log("...is already handled.")})}function j(e,t,i){let s=[];$("[class*="+e+"]").each(function(){let i=B(this.className,e);-1===s.indexOf(i)&&(console.log("prepping toggle "+i),s.push(i),$("."+t+i+":visible").length>0?($("."+e+i).attr("aria-expanded","true"),$("."+t+i).attr("aria-hidden","false")):($("."+e+i).attr("aria-expanded","false"),$("."+t+i).attr("aria-hidden","true")))}),X(e,t),$(".hx-clearmemory").on("click tap",function(){let t=B(this.className,e);null!==t?H(t,void 0):localStorage.HXToggleMemory=""}),$("[class*="+e+"]").on("click tap",function(){var s,n,l;let o=B(this.className,e),a=$(this).hasClass(i),r="";"true"===$(this).attr("aria-expanded")?(r="invisible",L(e,t,a,o,$(document))):(r="visible",S(e,t,a,o,$(document)),1===$("."+t+o).length&&(s=$("."+t+o)[0],n=s.getBoundingClientRect(),l=Math.max(document.documentElement.clientHeight,window.innerHeight),n.bottom<0||n.top-l>=0)&&$("."+t+o)[0].scrollIntoView()),U({"Toggle button":"pressed","Toggled to":r,"Toggle number":o})})}function P(e){return!!(void 0!==e&&isNaN(D(e)))&&!(e.includes("edx.org")||e.includes("edxapp")||e.includes("edx-cdn.org")||e.includes("edx-video.net")||e.includes("/courses/")||e.includes("/assets/courseware/")||e.includes("jump_to_id")||e.includes("cloudfront.net")||e.includes("mailto")||e.includes("javascript:void")||e.includes("javascript:;"))&&"#"!=e.slice(0,1)}function N(e,t){e.includes("%2B")&&(e=e.replace("%2B","+")),e.includes("%3A")&&(e=e.replace("%3A",":"));let i=e.match(/https:\/\/.+\//)[0];if("site"==t)return i;let s=e.replace("courses/course","asset");s.search("xblock/block-v1")>-1&&(s=(s=s.replace("xblock/block","asset")).replace("+type@","/"));let n=s.indexOf("+"),l=s.indexOf("/",n);return(s=s.slice(0,l),s+="+type@asset+block/","partial"==t)?s.replace(i,""):s}function B(e,t){let i=e.split(/\s+/);for(let s=0;s0&&(U({video:"found"}),f.push("HXVideoLinks.js"),window.HXPUPTimer.length>0&&f.push("HXPopUpProblems.js"),window.HXChimeTimer.length>0&&f.push("HXVideoChime.js")),$.getMultiScripts(f,r).done(function(){U({"Loaded scripts":f}),x=hxGlobalOptions?E(window.hxLocalOptions,hxGlobalOptions,a):E(window.hxLocalOptions,{},a),_(x)}).fail(function(e,t,i){console.log(e),console.log(t),console.log(i),U({scripts:f,script_load_error:t}),x=E(window.hxLocalOptions,{},a),_(x)}),(t=jQuery).fn.hxKonami=function(e,i){return void 0===i&&(i="38,38,40,40,37,39,37,39,66,65"),this.each(function(){let s=[];t(this).keydown(function(t){for(s.push(t.keyCode);s.length>i.split(",").length;)s.shift();s.toString().indexOf(i)>=0&&(s=[],e(t))})})},$(window).hxKonami(function(){alert("+30 Lives"),U({"easter egg":"Konami Code"})}),$(window).off("message.hx").on("message.hx",function(e){var t=e.originalEvent.data;if(e.originalEvent.origin===location.origin&&"string"==typeof t&&"backpack_ready"===t){console.log("Backpack ready.");let i=$("#hxbackpackframe")[0].contentWindow;window.hxSetData=i.hxSetData,window.hxClearData=i.hxSetData,window.hxGetData=i.hxGetData,window.hxGetAllData=i.hxGetAllData,window.hxBackpackLoaded=i.hxBackpackLoaded,x.useBackpack&&O.length>0&&void 0===s&&(s=new HXEditor(x.useBackpack,x.HXEditorOptions))}}),window.getAssetURL=N,window.hmsToTime=D,window.timeToHMS=function e(t){var i,s,n,l="";return i=Math.floor((t=Math.floor(t))/3600),(s=Math.floor(t/60%60))<10&&(s="0"+s),(n=Math.floor(t%60))<10&&(n="0"+n),l="0:"+n,s>0&&(l=s+":"+n),i>0&&(l=i+":"+s+":"+n),l},window.logThatThing=U,window.prepAccessibleToggles=j,window.isExternalLink=P,window.popDataMap=function e(){console.log("Open full-sized map image.");let t=$("#mapframe"),i=t.contents().find("#LargeMapView");t.toggleClass("hx-svg-view"),t.hasClass("hx-svg-view")?i.text("View Regular"):i.text("View Large")},window.jumpToTime=V,window.hxBackpackLoaded=$("#hxbackpackframe").length>0};$(document).ready(function(){if(void 0!==window.hxjs_is_already_running){console.log("hx-js is already loaded, skipping this copy.");return}window.hxjs_is_already_running=!0,HXGlobalJS()}); -------------------------------------------------------------------------------- /hx-min.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/hx-min.zip -------------------------------------------------------------------------------- /hx-standard.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/hx-standard.zip -------------------------------------------------------------------------------- /hxGlobalOptions.js: -------------------------------------------------------------------------------- 1 | /********************************************/ 2 | // Global options for hx.js 3 | // If you haven't changed anything here, it's showing you the default options. 4 | // Change things here to set them course-wide. 5 | // To reset to default, just remove them or reupload the original 6 | // version of this file from the GitHub repository. 7 | /********************************************/ 8 | 9 | var hxGlobalOptions = { 10 | hasGlobalOptions: true, 11 | markExternalLinks: true, 12 | useBackpack: true, 13 | 14 | /********************************************/ 15 | // Slick Image Slider Options 16 | /********************************************/ 17 | slickOptions: {}, 18 | slickNavOptions: {}, 19 | slickBigOptions: {}, 20 | textSliderOptions: {}, 21 | 22 | // Pop-up questions and links for videos 23 | PUPOptions: {}, 24 | VidLinkOptions: {}, 25 | 26 | // Custom emoji for Emoji Response questions (not yet implemented) 27 | // Use this format: 28 | // [ { 'emoji': '✓', 'alt': 'I understand' }, 29 | // { 'emoji': '❓', 'alt': 'I am confused' }, 30 | // { 'emoji': '😯', 'alt': 'I am surprised' }, 31 | // { 'emoji': '🤔', 'alt': 'I am skeptical' }, 32 | // { 'emoji': '😴', 'name': 'I am bored' } ] 33 | custom_emoji: [] 34 | }; 35 | -------------------------------------------------------------------------------- /misc_zipfiles/introjs.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/misc_zipfiles/introjs.zip -------------------------------------------------------------------------------- /prism LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2012 Lea Verou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /public_html/bookmarklet_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

    Bookmarklets for use with edX

    9 |

    10 | Add course staff in bulk. Works from the Membership tab in the Instructor Dashboard. 14 |

    15 |

    16 | Delete everything in Files & Uploads. Works from Files & Uploads. TAKE A BACKUP FIRST. 20 |

    21 |

    22 | Thank you to 23 | DevHQ 28 | for the method. 29 |

    30 | 31 | 32 | -------------------------------------------------------------------------------- /public_html/course_staff_adder.js: -------------------------------------------------------------------------------- 1 | /**************************** 2 | * Course staff adder for edX 3 | * Runs via bookmarklet. 4 | * Written by Colin Fredericks at HarvardX 5 | * MIT licensed 6 | ****************************/ 7 | 8 | $(document).ready(function () { 9 | console.log('course_staff_adder called'); 10 | 11 | showInputDialog(); 12 | 13 | function showInputDialog() { 14 | // Open dialog with text area and GO button. 15 | // Let user paste in a list of usernames or e-mail addresses 16 | // Get those as an array. 17 | 18 | makeModal(); 19 | $('#modal-1').dialog({ 20 | title: 'User List Input', 21 | modal: true, 22 | width: '50%', 23 | buttons: [ 24 | { 25 | text: 'Cancel', 26 | click: function () { 27 | $('#modal-1').dialog('destroy'); 28 | }, 29 | }, 30 | { 31 | text: 'Go', 32 | click: function () { 33 | addUsers(); 34 | console.log('adding users'); 35 | }, 36 | }, 37 | ], 38 | open: function (event, ui) { 39 | $('.ui-dialog-titlebar-close').hide(); 40 | $('.ui-dialog').focus(); 41 | $('.ui-widget-overlay').on('click', function () { 42 | $('#modal-1').dialog('close'); 43 | }); 44 | }, 45 | }); 46 | console.log('testing'); 47 | } 48 | 49 | function addUsers() { 50 | // Get the list of users to add. 51 | let new_users = $('#new_user_list').val(); 52 | // Separate on newlines and commas. 53 | let user_list = new_users.split(/[,\n]/g); 54 | // Strip whitespace 55 | user_list.forEach(function (e, i) { 56 | user_list[i] = e.trim(); 57 | }); 58 | console.log(user_list); 59 | 60 | // Try to add a user every .2 seconds. 61 | let i = 0; 62 | let timer = 0; 63 | let max_time = 2; //seconds 64 | let added_users = []; 65 | let lost_users = []; 66 | // Get the username entry box and the submit button 67 | let entry_box = $('.bottom-bar input.add-field:visible'); 68 | let add_button = $('.bottom-bar input.add:visible'); 69 | 70 | let ticker = setInterval(function () { 71 | let user = user_list[i]; 72 | 73 | // If we haven't tried to add this user yet, go for it. 74 | if (added_users[i] === undefined) { 75 | entry_box.val(user); 76 | add_button.click(); 77 | } 78 | 79 | // Move on once the username is added or the max time is past. 80 | if ($('td:contains("' + user + '")').length > 0) { 81 | added_users[i] = user; 82 | i++; 83 | } else if (timer > max_time) { 84 | lost_users.push(user); 85 | console.log("couldn't add " + user); 86 | timer = 0; 87 | i++; 88 | } 89 | 90 | // When we're done, let us know if we missed anyone. 91 | if (i >= user_list.length) { 92 | clearInterval(ticker); 93 | console.log(added_users); 94 | if (lost_users.length > 0) { 95 | // TODO: Not reporting correctly, not sure why. 96 | // alert('Could not add these users: ' + lost_users.join(', ')); 97 | console.log(lost_users); 98 | } 99 | } 100 | timer += 0.2; 101 | }, 200); 102 | } 103 | 104 | function makeModal() { 105 | if ($('#modal-1').length > 0) { 106 | console.log('modal already exists'); 107 | return; 108 | } 109 | 110 | let d = $('
    '); 111 | d.attr('id', 'modal-1'); 112 | 113 | let content = $('
    '); 114 | content.attr('id', 'modal-1-content'); 115 | 116 | let explanation = $('

    '); 117 | explanation.text( 118 | 'Paste e-mail addresses or usernames below, then hit the "Go" button.' 119 | ); 120 | 121 | let user_type = $('#member-lists-selector option:selected').text(); 122 | let details = $('

    '); 123 | details.text( 124 | 'Separate with commas or newlines. Users will be added as ' + 125 | user_type + 126 | '. To switch between staff/admin/other, go back and change the dropdown on this page.' 127 | ); 128 | 129 | let listbox = $(''); 130 | listbox.attr('id', 'new_user_list'); 131 | 132 | content.append(explanation); 133 | content.append(details); 134 | content.append(listbox); 135 | 136 | d.append(content); 137 | $('body').append(d); 138 | console.log('modal made'); 139 | } 140 | }); 141 | -------------------------------------------------------------------------------- /public_html/erase_all_uploads.js: -------------------------------------------------------------------------------- 1 | /**************************** 2 | * Course Content Eraser for edX 3 | * Runs via bookmarklet. 4 | * Written by Colin Fredericks at HarvardX 5 | * MIT licensed 6 | ****************************/ 7 | 8 | $(document).ready(function () { 9 | console.log('erase_all_uploads called'); 10 | 11 | let time_between_deletions = 3000; 12 | 13 | showWarningDialog(); 14 | 15 | function showWarningDialog() { 16 | // Do they really want to delete every single thing in Files & Uploads? 17 | 18 | makeModal(); 19 | $('#modal-1').dialog({ 20 | title: 'Double-Checking Your Intent', 21 | modal: true, 22 | width: '50%', 23 | buttons: [ 24 | { 25 | text: 'I changed my mind.', 26 | click: function () { 27 | $('#modal-1').dialog('destroy'); 28 | }, 29 | }, 30 | { 31 | text: 'DESTROY.', 32 | click: function () { 33 | destroyEverything(); 34 | console.log('erasing everything'); 35 | }, 36 | }, 37 | ], 38 | open: function (event, ui) { 39 | $('.ui-dialog-titlebar-close').hide(); 40 | $('.ui-dialog').focus(); 41 | $('.ui-widget-overlay').on('click', function () { 42 | $('#modal-1').dialog('close'); 43 | }); 44 | }, 45 | }); 46 | $('#modal-1').parent().css('background', 'white'); 47 | $('#modal-1').parent().css('border', '2px solid black'); 48 | console.log('dialog displayed'); 49 | } 50 | 51 | // Some files cannot be deleted and need to be skipped. 52 | function canBeDeleted(filename) { 53 | let undeletable = [ 54 | 'python_lib.zip', 55 | '%', 56 | ]; 57 | 58 | for (let i = 0; i < undeletable.length; i++) { 59 | if (filename.includes(undeletable[i])) { 60 | return false; 61 | } 62 | } 63 | return true; 64 | } 65 | 66 | // Not all files can be deleted. Return the index of the first one that can. 67 | function getNextDeletableFile() { 68 | let first_undeletable = 0; 69 | let all_filenames = $("button[data-identifier='asset-delete-button']") 70 | .parents('tr') 71 | .find('span[data-identifier="asset-file-name"]'); 72 | 73 | // Go through all the files and find the first one that can be deleted. 74 | 75 | for(let i of all_filenames) { 76 | if(!canBeDeleted(i.textContent)) { 77 | first_undeletable++; 78 | } else { 79 | break; 80 | } 81 | } 82 | 83 | return first_undeletable; 84 | } 85 | 86 | // Delete every file that we can actually delete. 87 | function destroyEverything() { 88 | // Try to delete a file every X seconds. 89 | 90 | let timer = setInterval(function () { 91 | let delete_buttons = $("button[data-identifier='asset-delete-button']"); 92 | console.log(delete_buttons); 93 | // Not all files can be deleted. Skip ahead to one that can. 94 | let n = getNextDeletableFile(); 95 | console.log(n); 96 | if (delete_buttons.length >= n) { 97 | delete_buttons[n].click(); 98 | console.log('clicked delete button'); 99 | // look for a visible confirmation dialog every 500 ms. 100 | let inner_timer = setInterval(function () { 101 | let confirm_button = $( 102 | "button[data-identifier='asset-confirm-delete-button']:visible" 103 | ); 104 | if (confirm_button.length > 0) { 105 | console.log(confirm_button); 106 | clearInterval(inner_timer); 107 | confirm_button[0].click(); 108 | console.log('confirmed deletion'); 109 | } 110 | }, 500); 111 | } else { 112 | clearInterval(timer); 113 | $('#modal-1').dialog('destroy'); 114 | } 115 | }, time_between_deletions); 116 | } 117 | 118 | // Check to make sure they really want to do this. 119 | function makeModal() { 120 | if ($('#modal-1').length > 0) { 121 | console.log('modal already exists'); 122 | return; 123 | } 124 | 125 | let duration = Number($('.result-count-wrapper span')[7].innerText.replace(",","")) * time_between_deletions / 1000; 126 | let duration_text = ''; 127 | if(duration > 3600) { 128 | duration_text = Math.round(duration / 3600) + ' hours and '; 129 | duration = duration % 3600; 130 | } 131 | if(duration > 60) { 132 | duration_text = duration_text + Math.round(duration / 60) + ' minutes'; 133 | } else { 134 | duration_text = duration_text + duration + ' seconds'; 135 | } 136 | console.debug(duration); 137 | console.debug(duration_text); 138 | 139 | let d = $('
    '); 140 | d.attr('id', 'modal-1'); 141 | 142 | let content = $('
    '); 143 | content.attr('id', 'modal-1-content'); 144 | 145 | let explanation = $('

    '); 146 | explanation.text( 147 | 'Are you really sure you want to delete EVERYTHING in Files and Uploads?' 148 | ); 149 | 150 | let details = $('

    '); 151 | details.text( 152 | 'Maybe you want to take a backup of the course first so you can save the SRT files? ' + 153 | 'Maybe you want to search for a specific file type first and run this again to just delete those? ' + 154 | 'There\'s no "undo" button here, FYI, and the dialog is going to flash a lot ' + 155 | 'for the next ' + 156 | duration_text 157 | ); 158 | 159 | content.append(explanation); 160 | content.append(details); 161 | 162 | d.append(content); 163 | $('body').append(d); 164 | console.log('modal made'); 165 | } 166 | }); 167 | -------------------------------------------------------------------------------- /public_html/flashcards/flashcards.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1 0 auto; 9 | } 10 | 11 | h2 { 12 | margin: 0em 0.5em 0.75em 0.5em; 13 | } 14 | 15 | #flashcard-container { 16 | width: 70%; 17 | max-width: 800px; 18 | display: grid; 19 | grid-template-columns: 25px 1fr 32px; /* Why 32 px? Not sure. Just need it to balance visually. */ 20 | font-size: x-large; 21 | } 22 | 23 | .slidebox { 24 | height: calc(100% - 1em); 25 | position: relative; 26 | } 27 | 28 | .arrow-navigation { 29 | flex: 1 0 auto; 30 | cursor: pointer; 31 | place-self: center; 32 | background: none; 33 | border: none; 34 | } 35 | 36 | .flashcard { 37 | background-color: transparent; 38 | width: 100%; 39 | min-height: 300px; 40 | max-height: 800px; 41 | perspective: 3000px; 42 | position: relative; 43 | overflow: hidden; 44 | white-space: nowrap; 45 | } 46 | 47 | .flashcard .inner { 48 | width: calc(100% - 1em); 49 | height: calc(100% - 1em); 50 | position: relative; 51 | display: inline-block; 52 | transform-style: preserve-3d; 53 | border: 2px solid black; 54 | border-radius: 10px; 55 | margin: 0.5em; 56 | } 57 | 58 | .flashcard .front, 59 | .flashcard .back { 60 | width: 100%; 61 | height: 100%; 62 | position: absolute; 63 | text-align: center; 64 | background-color: white; 65 | border-radius: 8px; 66 | color: black; 67 | -webkit-backface-visibility: hidden; /* Safari */ 68 | backface-visibility: hidden; 69 | } 70 | 71 | .flashcard .back { 72 | transform: rotateY(180deg); 73 | } 74 | 75 | .card-content { 76 | height: 100%; 77 | border-radius: 8px; 78 | display: grid; 79 | grid: 1fr 2fr 1fr / 1fr; 80 | } 81 | 82 | .title { 83 | text-align: center; 84 | padding: 1em 0.5em 0 0.5em; 85 | white-space: normal; 86 | } 87 | 88 | .question { 89 | text-align: center; 90 | padding: 0 1em 0 1em; 91 | white-space: normal; 92 | } 93 | 94 | .answer { 95 | text-align: center; 96 | padding: 0 1em 0 1em; 97 | white-space: normal; 98 | } 99 | 100 | .bottom { 101 | color: white; 102 | background-color: rgba(163, 0, 0, 1); 103 | border-radius: 0 0 8px 8px; 104 | display: flex; 105 | font-size: medium; 106 | font-weight: bold; 107 | justify-content: space-between; 108 | width: 100%; 109 | } 110 | 111 | .number { 112 | padding: 0.5em; 113 | align-self: end; 114 | } 115 | 116 | .flip-button { 117 | background-color: rgba(163, 0, 0, 1); 118 | align-self: end; 119 | cursor: pointer; 120 | height: 100%; 121 | border: none; 122 | color: white; 123 | font-weight: bold; 124 | font-size: larger; 125 | padding-right: 1em; 126 | } 127 | 128 | .sr-only { 129 | position: absolute; 130 | width: 1px; 131 | height: 1px; 132 | padding: 0; 133 | margin: -1px; 134 | overflow: hidden; 135 | clip: rect(0, 0, 0, 0); 136 | border: 0; 137 | } 138 | -------------------------------------------------------------------------------- /public_html/flashcards/flashcards.js: -------------------------------------------------------------------------------- 1 | const slide_timing = { 2 | duration: 600, 3 | easing: 'ease', 4 | fill: 'forwards', 5 | iterations: 1, 6 | }; 7 | 8 | const flip_timing = { 9 | duration: 600, 10 | easing: 'ease', 11 | fill: 'forwards', 12 | iterations: 1, 13 | }; 14 | 15 | const tabbable_classes = '.title, .question, .answer, .number, .flip-button'; 16 | window.flashcards_accepting_input = true; 17 | 18 | loadCards('source.csv'); 19 | 20 | /** 21 | * Load the flashcards from csv file using papaparse 22 | * and start the rest of the code when complete. 23 | * @param {string} filename - The name of the csv file to load 24 | * @returns {void} 25 | */ 26 | function loadCards(filename) { 27 | Papa.parse(filename, { 28 | download: true, 29 | header: true, 30 | skipEmptyLines: true, 31 | complete: function (results) { 32 | let cards = results.data; 33 | // Trim the whitespace from all the fields 34 | for (let i = 0; i < cards.length; i++) { 35 | let card = cards[i]; 36 | for (let key in card) { 37 | console.debug(card[key]); 38 | if (typeof card[key] === 'string') { 39 | card[key] = card[key].trim(); 40 | } 41 | } 42 | } 43 | // TODO: interpret markdown in the cards 44 | readyGo(cards); 45 | }, 46 | }); 47 | } 48 | 49 | /** 50 | * Gets some frequently-used variables, sets up the cards, and 51 | * sets up listeners. 52 | * @param {array} cards - An array of card objects from the csv file 53 | * @returns {void} 54 | */ 55 | function readyGo(cards) { 56 | // Create the flashcards 57 | let card_array = createCards(cards); 58 | let current_card = 0; 59 | 60 | let slidebox = document.createElement('div'); 61 | slidebox.classList.add('slidebox'); 62 | document.querySelector('.flashcard').appendChild(slidebox); 63 | 64 | // Put them on the page. 65 | for (let i = 0; i < card_array.length; i++) { 66 | slidebox.appendChild(card_array[i]); 67 | } 68 | let all_cards = document.querySelectorAll('.flashcard .inner'); 69 | all_cards[0].classList.add('current-card'); 70 | // Set the tabindex on the front of the first card so it can be focused 71 | all_cards[0].querySelectorAll('.front ').tabIndex = 0; 72 | 73 | // Get the width of the flashcards 74 | let card_width = Math.round( 75 | all_cards[1].getBoundingClientRect().left - 76 | all_cards[0].getBoundingClientRect().left 77 | ); 78 | 79 | // When someone clicks on a flashcard, it flips over to show the answer 80 | document.querySelectorAll('.flashcard .inner').forEach((flashcard) => { 81 | flashcard.addEventListener('click', () => { 82 | flip(flashcard, all_cards, current_card); 83 | }); 84 | }); 85 | 86 | // If someone uses the up or down arrow keys, the flashcard flips 87 | document.addEventListener('keydown', (event) => { 88 | if (keyWithoutModifiers(event) === 'ArrowUp') { 89 | console.debug(event); 90 | flip(all_cards[current_card], all_cards, current_card); 91 | } else if (keyWithoutModifiers(event) === 'ArrowDown') { 92 | flip(all_cards[current_card], all_cards, current_card); 93 | console.debug(event); 94 | } 95 | }); 96 | 97 | // When someone swipes, the flashcard slides out and the next one slides in 98 | var cardbox = new Hammer(document.querySelector('.flashcard'), {}); 99 | cardbox.on('swiperight', function (ev) { 100 | current_card = cardMoveBuffer('left', current_card, card_width); 101 | }); 102 | cardbox.on('swipeleft', function (ev) { 103 | current_card = cardMoveBuffer('right', current_card, card_width); 104 | }); 105 | 106 | // If someone uses the left or right arrow keys, the flashcard slides out and the next one slides in 107 | document.addEventListener('keydown', (event) => { 108 | if (keyWithoutModifiers(event) === 'ArrowLeft') { 109 | current_card = cardMoveBuffer('left', current_card, card_width); 110 | } else if (keyWithoutModifiers(event) === 'ArrowRight') { 111 | current_card = cardMoveBuffer('right', current_card, card_width); 112 | } 113 | }); 114 | 115 | // If someone clicks on the left or right arrow, the flashcard slides out and the next one slides in 116 | document.querySelectorAll('.arrow-navigation').forEach(function (arrow) { 117 | arrow.addEventListener('click', () => { 118 | current_card = cardMoveBuffer( 119 | Array.from(arrow.classList).join(' '), 120 | current_card, 121 | card_width 122 | ); 123 | }); 124 | }); 125 | 126 | let waiter; 127 | // If someone reizes the window, get the new card width. 128 | // Reset the left edge of the cards if needed. 129 | window.onresize = function () { 130 | clearTimeout(waiter); 131 | waiter = setTimeout(handleResize, 100, card_width, current_card, all_cards); 132 | }; 133 | 134 | } 135 | 136 | /** 137 | * Move the slidebox so the current card is in the center 138 | * @param {void} 139 | * @returns {void} 140 | */ 141 | function handleResize(card_width, current_card, all_cards) { 142 | let slidebox = document.querySelector('.slidebox'); 143 | // Move the left edge of the cards so that the current card is in the center 144 | // Get the position of the left edge of the current card 145 | let current_card_left = all_cards[current_card].getBoundingClientRect().left; 146 | // Get the position of the left edge of the slidebox 147 | let slidebox_left = slidebox.getBoundingClientRect().left; 148 | // Slide the slidebox so that the current card is in the center 149 | let delta_width = card_width - (current_card_left - slidebox_left); 150 | if (delta_width > 0) { 151 | moveElement(slidebox, delta_width * current_card, 'left', true); 152 | updateFocus(); 153 | } 154 | } 155 | 156 | /** 157 | * Returns the key of the event, or false if there are modifiers 158 | * @param {*} event 159 | * @returns (string|boolean) 160 | */ 161 | function keyWithoutModifiers(event) { 162 | let modifiers = ['altKey', 'ctrlKey', 'metaKey', 'shiftKey']; 163 | for (let i = 0; i < modifiers.length; i++) { 164 | if (event[modifiers[i]]) { 165 | return false; 166 | } 167 | } 168 | return event.key; 169 | } 170 | 171 | /** 172 | * Creates the flashcard HTML elements 173 | * @param {array} cards - An array of card objects from the csv file 174 | * @returns {array} An array of flashcard HTML elements 175 | */ 176 | function createCards(cards) { 177 | let body = document.querySelector('body'); 178 | let cardElements = []; 179 | 180 | for (let i = 0; i < cards.length; i++) { 181 | let card = cards[i]; 182 | let cardElement = document.createElement('div'); 183 | cardElement.classList.add('inner'); 184 | let front = document.createElement('div'); 185 | front.classList.add('front'); 186 | let back = document.createElement('div'); 187 | back.classList.add('back'); 188 | 189 | let front_content = document.createElement('div'); 190 | front_content.classList.add('card-content'); 191 | let front_title = document.createElement('h2'); 192 | front_title.classList.add('title'); 193 | front_title.innerText = card['Front Title']; 194 | let question = document.createElement('div'); 195 | question.classList.add('question'); 196 | question.innerText = card.Question; 197 | 198 | let back_content = document.createElement('div'); 199 | back_content.classList.add('card-content'); 200 | let back_title = document.createElement('h2'); 201 | back_title.classList.add('title'); 202 | back_title.innerText = card['Back Title']; 203 | let answer = document.createElement('div'); 204 | answer.classList.add('answer'); 205 | answer.innerText = card.Answer; 206 | 207 | let front_bottom = document.createElement('div'); 208 | front_bottom.classList.add('bottom'); 209 | let back_bottom = front_bottom.cloneNode(true); 210 | 211 | let front_number = document.createElement('div'); 212 | front_number.classList.add('number'); 213 | front_number.innerText = `${i + 1} of ${cards.length}`; 214 | let back_number = front_number.cloneNode(true); 215 | 216 | let front_flip = document.createElement('button'); 217 | front_flip.classList.add('flip-button'); 218 | let back_flip = front_flip.cloneNode(true); 219 | front_flip.innerText = 'Show Answer'; 220 | back_flip.innerText = 'Show Question'; 221 | 222 | cardElement.appendChild(front); 223 | cardElement.appendChild(back); 224 | 225 | front.appendChild(front_content); 226 | front_content.appendChild(front_title); 227 | front_content.appendChild(question); 228 | front_bottom.appendChild(front_number); 229 | front_bottom.appendChild(front_flip); 230 | front_content.appendChild(front_bottom); 231 | 232 | back.appendChild(back_content); 233 | back_content.appendChild(back_title); 234 | back_content.appendChild(answer); 235 | back_bottom.appendChild(back_number); 236 | back_bottom.appendChild(back_flip); 237 | back_content.appendChild(back_bottom); 238 | 239 | // Set all the tab indexes to -1 except for the first front. 240 | cardElement.querySelectorAll('*').forEach(function (element) { 241 | element.setAttribute('tabindex', '-1'); 242 | }); 243 | if (i === 0) { 244 | front.querySelectorAll(tabbable_classes).forEach(function (element) { 245 | element.setAttribute('tabindex', '0'); 246 | }); 247 | } 248 | 249 | cardElements.push(cardElement); 250 | body.appendChild(cardElement); 251 | } 252 | return cardElements; 253 | } 254 | 255 | /** 256 | * Set the tab index of certain pieces of the current side of the current card 257 | * to 0, and all the rest to -1. Then focus the current visible side. 258 | * @param {void} 259 | * @returns {void} 260 | */ 261 | function updateFocus() { 262 | // Wait 110ms for the slidebox to finish moving. 263 | setTimeout(function () { 264 | let inner = document.querySelector('.flashcard .current-card'); 265 | let front = inner.querySelector('.front'); 266 | let back = inner.querySelector('.back'); 267 | 268 | // Set all the tab indexes to -1 before we start. 269 | document.querySelectorAll(tabbable_classes).forEach(function (element) { 270 | element.setAttribute('tabindex', '-1'); 271 | }); 272 | 273 | if (inner.classList.contains('flip')) { 274 | front.querySelectorAll('*').forEach(function (element) { 275 | element.setAttribute('tabindex', '-1'); 276 | }); 277 | back.querySelectorAll(tabbable_classes).forEach(function (element) { 278 | element.setAttribute('tabindex', '0'); 279 | }); 280 | back.querySelector('h2').focus(); 281 | } else { 282 | front.querySelectorAll(tabbable_classes).forEach(function (element) { 283 | element.setAttribute('tabindex', '0'); 284 | }); 285 | back.querySelectorAll('*').forEach(function (element) { 286 | element.setAttribute('tabindex', '-1'); 287 | }); 288 | front.querySelector('h2').focus(); 289 | } 290 | }, 110); 291 | } 292 | 293 | /** 294 | * Flips the card and calls updateFocus() 295 | * @param {HTMLElement} inner - The inner div of the card to flip 296 | * @returns {void} 297 | */ 298 | function flip(inner) { 299 | let flip_options = []; 300 | inner.classList.toggle('flip'); 301 | if (inner.classList.contains('flip')) { 302 | flip_options = [{ rotate: 'y 0deg' }, { rotate: 'y 180deg' }]; 303 | } else { 304 | flip_options = [{ rotate: 'y 180deg' }, { rotate: 'y 0deg' }]; 305 | } 306 | inner.animate(flip_options, flip_timing); 307 | updateFocus(); 308 | } 309 | 310 | /** 311 | * Takes in the command to move a card and waits some 312 | * so we don't move again in the middle of a move. 313 | * @param {string} direction - The direction to move the card 314 | * @param {number} current_card - The current card number 315 | * @param {number} card_width - The width of the card 316 | */ 317 | function cardMoveBuffer(direction, current_card, card_width) { 318 | // console.debug("cardMoveBuffer"); 319 | if (window.flashcards_accepting_input) { 320 | current_card = slideCard(direction, current_card, card_width); 321 | window.flashcards_accepting_input = false; 322 | setTimeout(function () { 323 | window.flashcards_accepting_input = true; 324 | updateFocus(); 325 | }, slide_timing.duration); 326 | return current_card; 327 | } 328 | return current_card; 329 | } 330 | 331 | /** 332 | * Moves the card in the given direction 333 | * @param {string} direction - The direction to move the card 334 | * @param {number} current_card - The current card number 335 | * @param {number} card_width - The width of the card 336 | * @returns {number} - The new current card number 337 | */ 338 | function slideCard(class_list, current_card, card_width) { 339 | // console.debug("slideCard"); 340 | let slidebox = document.querySelector('.flashcard .slidebox'); 341 | let all_cards = document.querySelectorAll('.flashcard .inner'); 342 | let direction = class_list.includes('left') ? 'right' : 'left'; 343 | let other_direction = direction === 'left' ? 'right' : 'left'; 344 | 345 | if (direction === 'left') { 346 | current_card++; 347 | } else if (direction === 'right') { 348 | current_card--; 349 | } 350 | 351 | if (current_card < 0) { 352 | moveElement(slidebox, card_width * (all_cards.length - 1), other_direction); 353 | current_card = all_cards.length - 1; 354 | } else if (current_card < all_cards.length) { 355 | moveElement(slidebox, card_width, direction); 356 | } else { 357 | // If there's no next element, go back to the beginning 358 | moveElement(slidebox, card_width * (all_cards.length - 1), other_direction); 359 | current_card = 0; 360 | } 361 | 362 | // recenterCards(all_cards, current_card); 363 | 364 | // Mark the current card. 365 | for (c of all_cards) { 366 | c.classList.remove('current-card'); 367 | } 368 | all_cards[current_card].classList.add('current-card'); 369 | return current_card; 370 | } 371 | 372 | /** 373 | * Moves the element in the given direction using .animate() 374 | * @param {HTMLElement} elem - The element to move 375 | * @param {number} distance - The distance to move the element 376 | * @param {string} direction - The direction to move the element 377 | * @returns {void} 378 | */ 379 | function moveElement(elem, distance, direction, fast = false) { 380 | let slide_options = makeSlideOptions(elem, distance, direction); 381 | // console.debug(slide_options[1]); 382 | if (fast) { 383 | elem.animate(slide_options, { 384 | duration: 300, 385 | easing: 'ease', 386 | fill: 'forwards', 387 | iterations: 1, 388 | }); 389 | } else { 390 | elem.animate(slide_options, slide_timing); 391 | } 392 | } 393 | 394 | /** 395 | * Helper function to create the options for .animate() 396 | * @param {*} elem - The element to move 397 | * @param {*} distance - The distance to move the element 398 | * @param {*} direction - The direction to move the element 399 | * @returns {Array} - The options for .animate() 400 | */ 401 | function makeSlideOptions(elem, distance, direction) { 402 | let animate_text = ''; 403 | let current_position = { x: position(elem).x, y: position(elem).y }; 404 | 405 | if (direction === 'right') { 406 | animate_text = current_position.x + distance + 'px'; 407 | } else if (direction === 'left') { 408 | animate_text = current_position.x - distance + 'px'; 409 | } else if (direction === 'up') { 410 | animate_text = ' ' + current_position.y - distance + 'px'; 411 | } else if (direction === 'down') { 412 | animate_text = ' ' + current_position.y + distance + 'px'; 413 | } else { 414 | console.debug( 415 | "Invalid direction. Must be 'right', 'left', 'up', or 'down'." 416 | ); 417 | } 418 | 419 | return [ 420 | { translate: position(elem).x + 'px ' + position(elem).y + 'px' }, 421 | { translate: animate_text }, 422 | ]; 423 | } 424 | 425 | /** 426 | * Gets the x/y position of an element taking into account bounding 427 | * rectangles, offsets, and scroll positions. 428 | * @param {HTMLElement} elem - The element to get the position of 429 | * @returns {object} - The position of the element {x: number, y: number} 430 | */ 431 | function position(elem) { 432 | return { 433 | y: Math.round( 434 | elem.getBoundingClientRect().top - 435 | elem.offsetTop - 436 | elem.offsetParent.offsetTop + 437 | document.body.scrollTop 438 | ), 439 | x: Math.round( 440 | elem.getBoundingClientRect().left - 441 | elem.offsetLeft - 442 | elem.offsetParent.offsetLeft + 443 | document.body.scrollLeft 444 | ), 445 | }; 446 | } 447 | -------------------------------------------------------------------------------- /public_html/flashcards/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Flashcard Demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 |

    Flashcard Demo

    16 |

    17 | Click the question to see the answer. Click the arrows, use the 18 | arrow keys, or swipe on mobile to go to the next card or go back. 19 | You can find the code for this demo on 20 | GitHub. 21 |

    22 | 23 |
    24 | 30 |
    31 | 37 |
    38 |
    39 | 40 | 41 | -------------------------------------------------------------------------------- /public_html/flashcards/source.csv: -------------------------------------------------------------------------------- 1 | Front Title,Back Title,Question,Answer 2 | VPAL,VPAL,VPAL stands for...,Vice Provost for Advances in Learning 3 | X,X,The X in edX stands for...,"Nothing, it just sounded cool." 4 | LXP,LXP,The X in LXP stands for...,"Experience. Yes, it's a word that starts with an E. That's how powerful X is, it can even infect acronyms from other words." 5 | ,No title,question,answer 6 | No back,,Question,Answer 7 | No question,Back Title,,Answer 8 | No answer,Back Title,Question, 9 | ,,, 10 | Just a title,,, 11 | ,Just a back title,, 12 | ,,Just a question,, 13 | ,,,Just an answer 14 | x,y,z,q -------------------------------------------------------------------------------- /summernote LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015~ Summernote Team (https://github.com/orgs/summernote/people) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /text slider/ClimateImpactExplorer.csv: -------------------------------------------------------------------------------- 1 | ID,Title,OwnIcon,Category,Breadcrumb,AboveFold,Next,Previous,Image,Alt,Caption,FoldHeader1,FoldText1,FoldHeader2,FoldText2,FoldHeader3,FoldText3,Icon1,IconTarget1,IconAlt1,Icon2,IconTarget2,IconAlt2,Icon3,IconTarget3,IconAlt3,Icon4,IconTarget4,IconAlt4 2 | SlideA,Slide A,food-chain-icon.png,red,Food Chain,"

    I am the first paragraph. I have a link to Slide 2.

    ",Slide2,,https://placebear.com/350/250,Placebear 250,Number one bear.,Paragraph Two,

    This is some 'quoted' text. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph.

    ,Paragraph Three,

    This is some 'quoted' text. I am the third paragraph. I am the third paragraph. I am the third paragraph. I am the third paragraph. I am the third paragraph. I am the third paragraph. I am the third paragraph. I am the third paragraph. I am the third paragraph. I am the third paragraph. I am the third paragraph. I am the third paragraph. I am the third paragraph.

    ,Paragraph Four,

    This is some 'quoted' text. I am the fourth paragraph. I am the fourth paragraph. I am the fourth paragraph. I am the fourth paragraph. I am the fourth paragraph. I am the fourth paragraph. I am the fourth paragraph. I am the fourth paragraph. I am the fourth paragraph. I am the fourth paragraph. I am the fourth paragraph. I am the fourth paragraph. I am the fourth paragraph.

    ,food-chain-icon.png,FoodChain,Impacts food chain,shambling-zombie-icon.png,Zombies,Results in zombies,,,,,, 3 | Slide2,Slide 2,shambling-zombie-icon.png,blue,Zombies,"

    I am the first paragraph. Here is some potentially bad text: ' "" & Ø

    I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I have a link to Slide Gamma. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph.

    ",SlideGamma,"SlideA, SeaRise",https://placebear.com/250/450,Placebear 350,Bear puns bother me.,,,,,,,food-chain-icon.png,FoodChain,Impacts food chain,shambling-zombie-icon.png,Zombies,Results in zombies,flood-icon.png,Floods,Impacts shoreline communities,tick-icon.png,TheTick,The Tick appears 4 | SlideGamma,Slide Gamma,flood-icon.png,yellow,Floods,

    I am the first paragraph.

    ,,Slide2,https://placebear.com/150/150,Placebear 150,I can hardly bear them.,Extra Detail,

    I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph. I am the second paragraph.

    ,,,,,,,,,,,,,,,, 5 | SeaRise,Sea Level Rise,tick-icon.png,green,Sea Rise,"

    Placeholder text for paragraph one.

    Placeholder text for paragraph two. Link to Dengue. Outside link to Migration. End of paragraph two.

    ",,,/static/sea_rise_map_global_5m.png,"Affected areas are coastal, including nearly all of the world's major cities.",,,,,,,,coastal_pop_icon.png,CoastalPopulations,Affects coastal populations,poor_icon.png,EconDisad,Affects the economically disadvantaged,,,,,, -------------------------------------------------------------------------------- /text slider/README.md: -------------------------------------------------------------------------------- 1 | ## Dynamic Text Slider 2 | 3 | Here's what you need to do: 4 | 5 | 1. Make a CSV file made with headers similar to the `ClimateImpactExplorer.csv` file shown as an example in this directory. 6 | 2. Upload the `papaparse.js`, `hx-text-slider.css`, and `hx-text-slider.js` files to Files & Uploads. The first one is the parser for CSV files. 7 | 3. Copy the HTML from `Slider Test.html` into a Raw HTML component in edX. 8 | 9 | You'll see a version of the slider, though with broken icon links. 10 | 11 | To change the source file, go into the HTML and change the line that says `var slidesFile = "/static/ClimateImpactExplorer.csv";` to something else. 12 | 13 | Here's what the required columns in the data file are for: 14 | * **ID** - Does not appear, but is used in links. Write a link with `class="slidelink"` and `data-target=` whatever ID you want, and that will be used to call up the right slide with the matching ID. 15 | * **Title** - appears in the slide. 16 | * **Breadcrumb** - the text that shows up in the breadcrumb section. 17 | * **AboveFold** - text that is not collapsed. 18 | * **Next** - a list of IDs for the slides that come after this one. 19 | * **Previous** - a list of IDs for the slides that come before this one. 20 | 21 | The optional columns are: 22 | * **Image, Alt, and Caption** - An image that appears on the slide, its alt text, and a caption to appear beneath it. 23 | * **FoldHeaderN and FoldTextN** - These are headers and text for collapsible sections within the slide. You can have up to 9 folds before the system breaks. 24 | * **IconN, IconTargetN, and IconAltN** - These are the filename, link target, and alt text for your icons. You can have up to 9 icons before the system breaks. 25 | -------------------------------------------------------------------------------- /text slider/Slider Test.html: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 |

    Test Slider

    28 | 29 |

    View Live to see it in action.

    30 | 31 |
    32 |
    33 |
    Initializing...
    34 |
    35 |
    36 | -------------------------------------------------------------------------------- /text slider/hx-text-slider-old.css: -------------------------------------------------------------------------------- 1 | .hx-dynamic-sliderbox{ 2 | background-color: #FFF; 3 | border: none !important; 4 | padding: 10px !important; 5 | } 6 | 7 | .slick-slider { 8 | -webkit-user-select: text; 9 | -moz-user-select: text; 10 | -ms-user-select: text; 11 | user-select: text; 12 | } 13 | 14 | /* Need to hide the elements that are off-screen */ 15 | .slick-slide{ 16 | visibility: hidden; 17 | } 18 | 19 | .slick-slide.slick-active{ 20 | visibility: visible; 21 | } 22 | 23 | 24 | /* For small screens */ 25 | .hx-slidelayout { 26 | } 27 | 28 | .hx-leftbox{ 29 | display: inline-block; 30 | vertical-align:top; 31 | padding: 4px; 32 | } 33 | 34 | .hx-rightbox{ 35 | display: inline-block; 36 | } 37 | 38 | .hx-rightbox figure{ 39 | padding: 2px; 40 | margin: 0px 0px 10px 0px; 41 | } 42 | 43 | .hx-text-slider-nav{ 44 | padding: 5px; 45 | background-color: #eee; 46 | box-shadow: inset 1px 1px 8px #aaa; 47 | border-radius: 3px; 48 | } 49 | 50 | .hx-prevnext-icons{ 51 | text-align: center; 52 | font-weight: 600; /* Semi-bold */ 53 | display: inline-block; 54 | margin: 0px 6px 0px 6px; 55 | } 56 | 57 | .hx-prevnext-icons img{ 58 | display: block; 59 | margin: auto; 60 | } 61 | 62 | img.out-of-scope{ 63 | opacity: 0.6; 64 | filter: gray; /* IE6-9 */ 65 | -webkit-filter: grayscale(1); /* Google Chrome, Safari 6+ & Opera 15+ */ 66 | filter: grayscale(1); /* Microsoft Edge and Firefox 35+ */ 67 | } 68 | 69 | div.out-of-scope{ 70 | font-weight: 300; /* Un-bold */ 71 | } 72 | 73 | .hx-next{ 74 | text-align: right; 75 | } 76 | 77 | .hx-next h4{ 78 | text-align: right; 79 | } 80 | 81 | 82 | @media only screen and (min-width: 820px) { 83 | /* For desktop: */ 84 | 85 | .hx-slidelayout { 86 | display: flex; 87 | } 88 | 89 | .hx-leftbox{ 90 | flex: 1 1 auto; 91 | min-width: 300px; 92 | display: inline-block; 93 | padding-right: 8px; 94 | } 95 | 96 | .hx-rightbox{ 97 | flex: 0 0 auto; 98 | padding: 10px; 99 | display: inline-block; 100 | } 101 | 102 | .hx-text-slider-nav{ 103 | display: flex; 104 | padding: 5px; 105 | background-color: #eee; 106 | box-shadow: inset 1px 1px 8px #aaa; 107 | border-radius: 3px; 108 | } 109 | 110 | .hx-previous{ 111 | flex: 1 0 50%; 112 | } 113 | 114 | .hx-previous-icons{ 115 | } 116 | 117 | .hx-next{ 118 | flex: 1 0 50%; 119 | text-align: right; 120 | } 121 | 122 | .hx-next h4{ 123 | text-align: right; 124 | } 125 | 126 | .hx-next-icons{ 127 | display: flex; 128 | justify-content: space-evenly; 129 | } 130 | 131 | .hx-prevnext-icons{ 132 | text-align: center; 133 | display: inline-block; 134 | margin: 0px 6px 0px 6px; 135 | } 136 | 137 | } 138 | 139 | .crumbbox{ 140 | background-color: #eee; 141 | box-shadow: inset 1px 1px 8px #bbb; 142 | border-radius: 3px; 143 | } 144 | 145 | .hx-sliderbox { 146 | border: none; 147 | padding: 8px 4px 4px 4px; 148 | } 149 | 150 | .hx-slide-icon{ 151 | float: left; 152 | position: relative; 153 | z-index: 10; 154 | } 155 | 156 | .hx-slide-title{ 157 | position: relative; 158 | top: 30px; 159 | left: 15px; 160 | } 161 | 162 | .backToParentSlide, .goToHomeSlide, .showOverviewMap { 163 | border: none; 164 | box-shadow: none; 165 | color: #999; 166 | background-color: transparent; 167 | background-image: none; 168 | font-size: 16pt; 169 | padding: 4px 4px 4px 8px; 170 | } 171 | 172 | .showOverviewMap { 173 | float: right; 174 | padding: 4px 8px 4px 4px; 175 | } 176 | 177 | .overviewMap { 178 | display: none; 179 | } 180 | 181 | .no-close .ui-dialog-titlebar-close { 182 | display: none; 183 | } 184 | 185 | .canGoBack { 186 | color: black; 187 | } 188 | 189 | .slideBreadcrumbs { 190 | color: #999; 191 | font-size: 12pt; 192 | font-style: italic; 193 | } 194 | 195 | .slideicons a{ 196 | float:left; 197 | margin: 2px 2px 2px 0px; 198 | } 199 | 200 | .slideBreadcrumbs .bclink{ 201 | color: #389; 202 | } 203 | 204 | .hx-togglenext{ 205 | border: 1px solid #bbb; 206 | border-radius: 3px; 207 | padding: 2px 2px 2px 4px; 208 | } 209 | 210 | 211 | /* Items for auto-generated overview */ 212 | .hxslide-overview-bigbox{ 213 | display: flex; 214 | flex-wrap: nowrap; 215 | max-width: 1080px; 216 | min-height: 300px 217 | max-height: 350px; 218 | box-shadow: inset 1px 1px 8px #bbb; 219 | padding: 8px 0px 8px 0px; 220 | margin: 0px 0px 8px 0px; 221 | } 222 | 223 | .hxslide-overview-container{ 224 | flex: 1 1 28%; 225 | 226 | display: flex; 227 | flex-flow: wrap; 228 | align-content: center; 229 | } 230 | 231 | .hxslide-overview-item{ 232 | display: inline; 233 | flex: 1 1 auto; 234 | 235 | min-width: 75px; 236 | margin: 2px; 237 | 238 | text-align: center; 239 | font-size: 12pt; 240 | overflow-wrap: normal; 241 | hyphens: auto; 242 | } 243 | 244 | .hxslide-overview-leftbox{ 245 | justify-content: flex-end; 246 | } 247 | 248 | .hxslide-overview-centerbox{ 249 | flex: 1 .5 34%; 250 | align-content: center; 251 | justify-content: center; 252 | } 253 | 254 | .hxslide-overview-rightbox{ 255 | justify-content: flex-start; 256 | } 257 | 258 | .hxslide-overview-leftbracket, .hxslide-overview-rightbracket{ 259 | flex: 0 .5 5%; 260 | display: flex; 261 | 262 | text-align: center; 263 | align-items: center; 264 | justify-content: center; 265 | } 266 | 267 | .hxslide-overview-leftbracket img, .hxslide-overview-rightbracket img{ 268 | height: 100%; 269 | max-width: 100%; 270 | min-width: 20px; 271 | } 272 | 273 | .hxslide-overview-container img{ 274 | width: 100%; 275 | } 276 | 277 | .hxslide-overview-keystone{ 278 | max-width: 800px; 279 | min-width: 150px; 280 | text-align: center; 281 | } 282 | -------------------------------------------------------------------------------- /text slider/hx-text-slider.css: -------------------------------------------------------------------------------- 1 | .hx-dynamic-sliderbox{ 2 | background-color: #FFF; 3 | border: none !important; 4 | padding: 10px !important; 5 | } 6 | 7 | .slick-slider { 8 | -webkit-user-select: text; 9 | -moz-user-select: text; 10 | -ms-user-select: text; 11 | user-select: text; 12 | } 13 | 14 | /* Need to hide the elements that are off-screen */ 15 | .slick-slide{ 16 | visibility: hidden; 17 | } 18 | 19 | .slick-slide.slick-active{ 20 | visibility: visible; 21 | } 22 | 23 | 24 | /* For small screens */ 25 | .hx-slidelayout { 26 | } 27 | 28 | .hx-leftbox{ 29 | display: inline-block; 30 | vertical-align:top; 31 | padding: 4px; 32 | } 33 | 34 | .hx-rightbox{ 35 | display: inline-block; 36 | } 37 | 38 | .hx-rightbox figure{ 39 | padding: 2px; 40 | margin: 0px 0px 10px 0px; 41 | } 42 | 43 | img.out-of-scope{ 44 | opacity: 0.6; 45 | filter: gray; /* IE6-9 */ 46 | -webkit-filter: grayscale(1); /* Google Chrome, Safari 6+ & Opera 15+ */ 47 | filter: grayscale(1); /* Microsoft Edge and Firefox 35+ */ 48 | } 49 | 50 | div.out-of-scope{ 51 | font-weight: 300; /* Un-bold */ 52 | } 53 | 54 | @media only screen and (min-width: 820px) { 55 | /* For desktop: */ 56 | 57 | .hx-slidelayout { 58 | display: flex; 59 | } 60 | 61 | } 62 | 63 | .controlbox{ 64 | background-color: #eee; 65 | border: 1px solid #ddd; 66 | display: flex; 67 | } 68 | 69 | .hx-sliderbox { 70 | border: none; 71 | padding: 8px 4px 4px 4px; 72 | } 73 | 74 | .hx-slide-icon{ 75 | float: left; 76 | position: relative; 77 | z-index: 10; 78 | } 79 | 80 | .hx-slide-title{ 81 | position: relative; 82 | top: 30px; 83 | left: 15px; 84 | } 85 | 86 | .overviewNavControl{ 87 | flex: 0 1 auto; 88 | border: none; 89 | box-shadow: none; 90 | background-color: transparent; 91 | background-image: none; 92 | font-size: 16pt; 93 | padding: 4px 4px 4px 8px; 94 | display: inline-block; 95 | } 96 | 97 | .showOverview { 98 | flex: 0 1 auto; 99 | border: none; 100 | box-shadow: none; 101 | background-color: transparent; 102 | background-image: none; 103 | font-size: 13pt; 104 | padding: 0px 4px 0px 8px; 105 | width: 100%; 106 | text-align: left; 107 | } 108 | 109 | .showOverviewNote{ 110 | font-weight: bold; 111 | } 112 | 113 | .overviewMap { 114 | display: none; 115 | } 116 | 117 | .no-close .ui-dialog-titlebar-close { 118 | display: none; 119 | } 120 | 121 | .inactiveControl { 122 | color: #ccc; 123 | } 124 | 125 | .slideicons a{ 126 | float:left; 127 | margin: 2px 2px 2px 0px; 128 | } 129 | 130 | .hx-togglenext{ 131 | border: 1px solid #bbb; 132 | border-radius: 3px; 133 | padding: 2px 2px 2px 4px; 134 | } 135 | 136 | 137 | /* Items for auto-generated overview */ 138 | .hxslide-overview-bigbox{ 139 | display: flex; 140 | flex-wrap: nowrap; 141 | max-width: 1080px; 142 | min-height: 300px 143 | max-height: 350px; 144 | padding-bottom: 8px; 145 | /* border: 1px solid #ccc; */ 146 | box-shadow: inset 0px 0px 2px 1px rgba(204,204,204,1) !important; 147 | } 148 | 149 | .hxslide-overview-container{ 150 | flex: 1 1 28%; 151 | 152 | display: flex; 153 | flex-flow: wrap; 154 | align-content: flex-start; 155 | justify-content: center; 156 | } 157 | 158 | .hxslide-column-indicator{ 159 | font-weight: bold; 160 | color: white; 161 | width: 100%; 162 | padding: 4px 4px 4px 8px; 163 | } 164 | 165 | .hx-indicator-spacer{ 166 | margin-bottom: 4px; 167 | width: 100%; 168 | height: 30px; 169 | } 170 | 171 | .hxslide-overview-item{ 172 | display: block; 173 | flex: 1 1 auto; 174 | 175 | min-width: 65px; 176 | max-width: 200px; 177 | margin: 2px; 178 | 179 | text-align: center; 180 | font-size: 12pt; 181 | overflow-wrap: normal; 182 | hyphens: auto; 183 | } 184 | 185 | .hxslide-overview-container img{ 186 | width: 100%; 187 | } 188 | 189 | .hxslide-overview-keystone{ 190 | max-width: 800px; 191 | min-width: 150px; 192 | text-align: center; 193 | } 194 | -------------------------------------------------------------------------------- /text slider/leads-to.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/text slider/leads-to.jpg -------------------------------------------------------------------------------- /text slider/leads-to.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Colin-Fredericks/hx-js/b3f64a79a595b0ab8f91d8316313b91a52f4ef40/text slider/leads-to.pxm --------------------------------------------------------------------------------