source: main/trunk/greenstone3/web/interfaces/default/js/utility_scripts.js@ 36269

Last change on this file since 36269 was 36269, checked in by davidb, 22 months ago

fixed scrollIntoView issue where entire page would scroll by changing option block to nearest from center. small changes to colours

File size: 24.6 KB
Line 
1/** JavaScript file of utility functions.
2 * At present contains functions for sanitising of URLs,
3 * since tomcat 8+, being more compliant with URL/URI standards, is more strict about URLs.
4 */
5
6/*
7 Given a string consisting of a single character, returns the %hex (%XX)
8 https://www.w3resource.com/javascript-exercises/javascript-string-exercise-27.php
9 https://stackoverflow.com/questions/40100096/what-is-equivalent-php-chr-and-ord-functions-in-javascript
10 https://www.w3resource.com/javascript-exercises/javascript-string-exercise-27.php
11*/
12function urlEncodeChar(single_char_string) {
13 /*var hex = Number(single_char_string.charCodeAt(0)).toString(16);
14 var str = "" + hex;
15 str = "%" + str.toUpperCase();
16 return str;
17 */
18
19 var hex = "%" + Number(single_char_string.charCodeAt(0)).toString(16).toUpperCase();
20 return hex;
21}
22
23/*
24 Tomcat 8 appears to be stricter in requiring unsafe and reserved chars
25 in URLs to be escaped with URL encoding
26 See section "Character Encoding Chart of
27 https://perishablepress.com/stop-using-unsafe-characters-in-urls/
28 Reserved chars:
29 ; / ? : @ = &
30 -----> %3B %2F %3F %3A %40 %3D %26
31 [Now also reserved, but no special meaning yet in URLs (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent)
32 and not required to be enforced yet, so we're aren't at present dealing with these:
33 ! ' ( ) *
34 ]
35 Unsafe chars:
36 " < > # % { } | \ ^ ~ [ ] ` and SPACE/BLANK
37 ----> %22 %3C %3E %23 %25 %7B %7D %7C %5C %5E ~ %5B %5D %60 and %20
38 But the above conflicts with the reserved vs unreserved listings at
39 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
40 Possibly more info: https://stackoverflow.com/questions/1547899/which-characters-make-a-url-invalid
41
42 And the bottom of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
43 lists additional characters that have been reserved since and which need encoding when in a URL component.
44
45 Javascript already provides functions encodeURI() and encodeURIComponent(), see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
46 However, the set of chars they deal with only partially overlap with the set of chars that need encoding as per the RFC3986 for URIs and RFC1738 for URLs discussed at
47 https://perishablepress.com/stop-using-unsafe-characters-in-urls/
48 We want to handle all the characters listed as unsafe and reserved at https://perishablepress.com/stop-using-unsafe-characters-in-urls/
49 so we define and use our own conceptually equivalent methods for both existing JavaScript methods:
50 - makeSafeURL() for Javascript's encodeURI() to make sure all unsafe characters in URLs are escaped by being URL encoded
51 - and makeSafeURLComponent() for JavaScript's encodeURIComponent to additionally make sure all reserved characters in a URL portion are escaped by being URL encoded too
52
53 Function makeSafeURL() is passed a string that represents a URL and therefore only deals with characters that are unsafe in a URL and which therefore require escaping.
54 Function makeSafeURLComponent() deals with portions of a URL that when decoded need not represent a URL at all, for example data like inline templates passed in as a
55 URL query string's parameter values. As such makeSafeURLComponent() should escape both unsafe URL characters and characters that are reserved in URLs since reserved
56 characters in the query string part (as query param values representing data) may take on a different meaning from their reserved meaning in a URL context.
57*/
58
59/* URL encodes both
60 - UNSAFE characters to make URL safe, by calling makeSafeURL()
61 - and RESERVED characters (characters that have reserved meanings within a URL) to make URL valid, since the url component parameter could use reserved characters
62 in a non-URL sense. For example, the inline template (ilt) parameter value of a URL could use '=' and '&' signs where these would have XSLT rather than URL meanings.
63
64 See end of https://www.w3schools.com/jsref/jsref_replace.asp to use a callback passing each captured element of a regex in str.replace()
65*/
66function makeURLComponentSafe(url_part, encode_percentages) {
67 // https://stackoverflow.com/questions/12797118/how-can-i-declare-optional-function-parameters-in-javascript
68 encode_percentages = encode_percentages || 1; // this method forces the URL-encoding of any % in url_part, e.g. do this for inline-templates that haven't ever been encoded
69
70 var url_encoded = makeURLSafe(url_part, encode_percentages);
71 //return url_encoded.replace(/;/g, "%3B").replace(/\//g, "%2F").replace(/\?/g, "%3F").replace(/\:/g, "%3A").replace(/\@/g, "%40").replace(/=/g, "%3D").replace(/\&/g,"%26");
72 url_encoded = url_encoded.replace(/[\;\/\?\:\@\=\&]/g, function(s) {
73 return urlEncodeChar(s);
74 });
75 return url_encoded;
76}
77
78/*
79 URL encode UNSAFE characters to make URL passed in safe.
80 Set encode_percentages to 1 (true) if you don't want % signs encoded: you'd do so if the url is already partly URL encoded.
81*/
82function makeURLSafe(url, encode_percentages) {
83 encode_percentages = encode_percentages || 0; // https://stackoverflow.com/questions/12797118/how-can-i-declare-optional-function-parameters-in-javascript
84
85 var url_encoded = url;
86 if(encode_percentages) { url_encoded = url_encoded.replace(/\%/g,"%25"); } // encode % first
87 //url_encoded = url_encoded.replace(/ /g, "%20").replace(/\"/g,"%22").replace(/\</g,"%3C").replace(/\>/g,"%3E").replace(/\#/g,"%23").replace(/\{/g,"%7B").replace(/\}/g,"%7D");
88 //url_encoded = url_encoded.replace(/\|/g,"%7C").replace(/\\/g,"%5C").replace(/\^/g,"%5E").replace(/\[/g,"%5B").replace(/\]/g,"%5D").replace(/\`/g,"%60");
89 // Should we handle ~, but then what is its URL encoded value? Because https://meyerweb.com/eric/tools/dencoder/ URLencodes ~ to ~.
90 //return url_encoded;
91 url_encoded = url_encoded.replace(/[\ \"\<\>\#\{\}\|\\^\~\[\]\`]/g, function(s) {
92 return urlEncodeChar(s);
93 });
94 return url_encoded;
95}
96
97/***************
98* MENU SCRIPTS *
99***************/
100function moveScroller() {
101 var move = function() {
102 var editbar = $("#editBar");
103 var st = $(window).scrollTop();
104 var fa = $("#float-anchor").offset().top;
105 if(st > fa) {
106
107 editbar.css({
108 position: "fixed",
109 top: "0px",
110 width: editbar.data("width"),
111 //width: "30%"
112 });
113 } else {
114 editbar.data("width", editbar.css("width"));
115 editbar.css({
116 position: "relative",
117 top: "",
118 width: ""
119 });
120 }
121 };
122 $(window).scroll(move);
123 move();
124}
125
126
127function floatMenu(enabled)
128{
129 var menu = $(".tableOfContentsContainer");
130 if(enabled)
131 {
132 menu.data("position", menu.css("position"));
133 menu.data("width", menu.css("width"));
134 menu.data("right", menu.css("right"));
135 menu.data("top", menu.css("top"));
136 menu.data("max-height", menu.css("max-height"));
137 menu.data("overflow", menu.css("overflow"));
138 menu.data("z-index", menu.css("z-index"));
139
140 menu.css("position", "fixed");
141 menu.css("width", "300px");
142 menu.css("right", "0px");
143 menu.css("top", "100px");
144 menu.css("max-height", "600px");
145 menu.css("overflow", "auto");
146 menu.css("z-index", "200");
147
148 $("#unfloatTOCButton").show();
149 }
150 else
151 {
152 menu.css("position", menu.data("position"));
153 menu.css("width", menu.data("width"));
154 menu.css("right", menu.data("right"));
155 menu.css("top", menu.data("top"));
156 menu.css("max-height", menu.data("max-height"));
157 menu.css("overflow", menu.data("overflow"));
158 menu.css("z-index", menu.data("z-index"));
159
160 $("#unfloatTOCButton").hide();
161 $("#floatTOCToggle").prop("checked", false);
162 }
163
164 var url = gs.xsltParams.library_name + "?a=d&ftoc=" + (enabled ? "1" : "0") + "&c=" + gs.cgiParams.c;
165
166 $.ajax(url);
167}
168
169// TK Label Scripts
170
171var tkMetadataSetStatus = "needs-to-be-loaded";
172var tkMetadataElements = null;
173
174
175function addTKLabelToImage(labelName, definition, name, comment) {
176 // lists of tkLabels and their corresponding codes, in order
177 let tkLabels = ["Attribution","Clan","Family","MultipleCommunities","CommunityVoice","Creative","Verified","NonVerified","Seasonal","WomenGeneral","MenGeneral",
178 "MenRestricted","WomenRestricted","CulturallySensitive","SecretSacred","OpenToCommercialization","NonCommercial","CommunityUseOnly","Outreach","OpenToCollaboration"];
179 let tkCodes = ["tk_a","tk_cl","tk_f","tk_mc","tk_cv","tk_cr","tk_v","tk_nv","tk_s","tk_wg","tk_mg","tk_mr","tk_wr","tk_cs","tk_ss","tk_oc","tk_nc","tk_co","tk_o","tk_cb"];
180 for (let i = 0; i < tkLabels.length; i++) {
181 if (labelName == tkLabels[i]) {
182 let labeldiv = document.querySelectorAll(".tklabels img");
183 for (image of labeldiv) {
184 let labelCode = image.src.substr(image.src.lastIndexOf("/") + 1).replace(".png", ""); // get tk label code from image file name
185 if (labelCode == tkCodes[i]) {
186 image.title = "TK " + name + ": " + definition + " Click for more details."; // set tooltip
187 if (image.parentElement.parentElement.parentElement.classList[0] != "tocSectionTitle") { // disable onclick event in favourites section
188 image.addEventListener("click", function(e) {
189 let currPopup = document.getElementsByClassName("tkPopup")[0];
190 if (currPopup == undefined || (currPopup != undefined && currPopup.id != labelCode)) {
191 let popup = document.createElement("div");
192 popup.classList.add("tkPopup");
193 popup.id = labelCode;
194 let popupText = document.createElement("span");
195 let heading = "<h1>Traditional Knowledge Label:<br><h2>" + name + "</h2></h1>";
196 let moreInformation = "<br> For more information about TK Labels, ";
197 let link = document.createElement("a");
198 link.innerHTML = "click here.";
199 link.href = "https://localcontexts.org/labels/traditional-knowledge-labels/";
200 link.target = "_blank";
201 popupText.innerHTML = heading + comment + moreInformation;
202 popupText.appendChild(link);
203 let closeButton = document.createElement("span");
204 closeButton.innerHTML = "&#215;";
205 closeButton.id = "tkCloseButton";
206 closeButton.title = "Click to close window."
207 closeButton.addEventListener("click", function(e) {
208 closeButton.parentElement.remove();
209 });
210 popup.appendChild(closeButton);
211 popup.appendChild(popupText);
212 e.target.parentElement.appendChild(popup);
213 }
214 if (currPopup) currPopup.remove(); // remove existing popup div
215 });
216 }
217 }
218 }
219 }
220 }
221}
222
223function addTKLabelsToImages(lang) {
224 if (tkMetadataElements == null) {
225 console.error("ajax call not yet loaded tk label metadata set");
226 } else {
227 for (label of tkMetadataElements) { // for each tklabel element in tk.mds
228 let tkLabelName = label.attributes.name.value; // Element name=""
229 let attributes = label.querySelectorAll("[code=" + lang + "] Attribute"); // gets attributes for selected language
230 let tkName = attributes[0].textContent; // name="label"
231 let tkDefinition = attributes[1].textContent; // name="definition"
232 let tkComment = attributes[2].textContent; // name="comment"
233 addTKLabelToImage(tkLabelName, tkDefinition, tkName, tkComment);
234 }
235 }
236}
237
238function loadTKMetadataSet(lang) {
239 tkMetadataSetStatus = "loading";
240 $.ajax({
241 url: gs.variables["tkMetadataURL"],
242 success: function(xml) {
243 tkMetadataSetStatus = "loaded";
244 let parser = new DOMParser();
245 let tkmds = parser.parseFromString(xml, "text/xml");
246 tkMetadataElements = tkmds.querySelectorAll("Element");
247 if (document.readyState === "complete") {
248 addTKLabelsToImages(lang);
249 } else {
250 window.onload = function() {
251 addTKLabelsToImages(lang);
252 }
253 }
254 },
255 error: function() {
256 tkMetadataSetStatus = "no-metadata-set-for-this-collection";
257 console.log("No TK Label Metadata-set found for this collection");
258 }
259 });
260};
261
262// Audio Scripts for Enriched Playback
263
264function loadAudio(audio, sectionData) {
265 var speakerObjects = [];
266 var uniqueSpeakers;
267 const inputFile = sectionData;
268 var itemType;
269
270 // var accentColour = "#4CA72D";
271 var accentColour = "#F8C537";
272 var regionTransparency = "59";
273
274 var waveformContainer = document.getElementById("waveform");
275
276 var wavesurfer = WaveSurfer.create({ // wavesurfer options
277 container: waveformContainer,
278 backend: "MediaElement",
279 backgroundColor: "rgb(54, 73, 78)",
280 waveColor: "white",
281 progressColor: accentColour,
282 barWidth: 2,
283 barHeight: 1.2,
284 barGap: 2,
285 barRadius: 1,
286 cursorColor: 'black',
287 plugins: [
288 WaveSurfer.regions.create(),
289 WaveSurfer.timeline.create({
290 container: "#wave-timeline",
291 secondaryColor: "white",
292 secondaryFontColor: "white",
293 notchPercentHeight: "0",
294 fontSize: "12"
295 }),
296 WaveSurfer.cursor.create({
297 showTime: true,
298 opacity: 1,
299 customShowTimeStyle: {
300 'background-color': '#000',
301 color: '#fff',
302 padding: '0.2em',
303 'font-size': '12px'
304 },
305 formatTimeCallback: (num) => { return minutize(num); }
306 }),
307 ],
308 });
309
310 wavesurfer.load(audio);
311
312 // wavesurfer events
313
314 wavesurfer.on('region-click', function(region, e) { // play region audio on click
315 e.stopPropagation();
316 handleRegionColours(region, true);
317 wavesurfer.play(region.start); // plays from start of region
318 // region.play(); // plays region only
319 });
320
321 wavesurfer.on('region-mouseenter', function(region) { handleRegionColours(region, true); });
322 wavesurfer.on('region-mouseleave', function(region) { if (!(wavesurfer.getCurrentTime() <= region.end && wavesurfer.getCurrentTime() >= region.start)) handleRegionColours(region, false); });
323 wavesurfer.on('region-in', function(region) {
324 handleRegionColours(region, true);
325 if (itemType == "chapter") {
326 document.getElementById("chapter" + region.id.replace("region", "")).scrollIntoView({
327 behavior: "smooth",
328 block: "nearest"
329 });
330 }
331 });
332 wavesurfer.on('region-out', function(region) { handleRegionColours(region, false); });
333
334 var loader = document.createElement("span"); // loading audio element
335 loader.innerHTML = "Loading audio";
336 loader.id = "waveform-loader";
337 document.querySelector("#waveform wave").prepend(loader);
338
339 wavesurfer.on('waveform-ready', function() { // retrieve regions once waveforms have loaded
340 if (inputFile.endsWith("csv")) { // diarization if csv
341 itemType = "chapter";
342 loadCSVFile(inputFile, ["speaker", "start", "end"]);
343 } else if (inputFile.endsWith("json")) { // transcription if json
344 itemType = "word";
345 loadJSONFile(inputFile);
346 } else {
347 console.log("Filetype of " + inputFile + " not supported.")
348 }
349 loader.remove(); // remove load text
350 });
351
352 function downloadURI(loc, name) {
353 var link = document.createElement("a");
354 link.download = name;
355 link.href = loc;
356 link.click();
357 }
358
359 // toolbar elements & event handlers
360 var chapters = document.getElementById("chapters");
361 var chapterButton = document.getElementById("chapterButton");
362 var backButton = document.getElementById("backButton");
363 var playPauseButton = document.getElementById("playPauseButton");
364 var forwardButton = document.getElementById("forwardButton");
365 var downloadButton = document.getElementById("downloadButton");
366 var muteButton = document.getElementById("muteButton");
367 var zoomSlider = document.getElementById("slider");
368 chapters.style.height = "0px";
369 chapterButton.addEventListener("click", function() { toggleChapters(); });
370 backButton.addEventListener("click", function() { wavesurfer.skipBackward(); });
371 playPauseButton.addEventListener("click", function() { wavesurfer.playPause() });
372 forwardButton.addEventListener("click", function() { wavesurfer.skipForward(); });
373 // audio = /greenstone3/library/sites/localsite/collect/tiriana-audio/index/assoc/HASHa6b7.dir/Te_Kakano_CH09_B.mp3
374 downloadButton.addEventListener("click", function() { downloadURI(audio, audio.split(".dir/")[1]) });
375 muteButton.addEventListener("click", function() { wavesurfer.toggleMute(); });
376 zoomSlider.style["accent-color"] = accentColour;
377
378 // path to toolbar images
379 var interface_bootstrap_images = "interfaces/" + gs.xsltParams.interface_name + "/images/bootstrap/";
380
381 wavesurfer.on("play", function() { playPauseButton.src = interface_bootstrap_images + "pause.svg"; });
382 wavesurfer.on("pause", function() { playPauseButton.src = interface_bootstrap_images + "play.svg"; });
383 wavesurfer.on("mute", function(mute) {
384 if (mute) {
385 muteButton.src = interface_bootstrap_images + "mute.svg";
386 muteButton.style.opacity = 0.6;
387 }
388 else {
389 muteButton.src = interface_bootstrap_images + "unmute.svg";
390 muteButton.style.opacity = 1;
391 }
392 });
393
394 zoomSlider.oninput = function () { // slider changes waveform zoom
395 wavesurfer.zoom(Number(this.value) / 4);
396 };
397 wavesurfer.zoom(zoomSlider.value / 4); // set default zoom point
398
399 var toggleChapters = function() { // show & hide chapter section
400 if (chapters.style.height == "0px") {
401 chapters.style.height = "30vh";
402 } else {
403 chapters.style.height = "0px";
404 }
405 }
406
407 function loadCSVFile(filename, manualHeader) { // based around: https://stackoverflow.com/questions/7431268/how-to-read-data-from-csv-file-using-javascript
408 $.ajax({
409 type: "GET",
410 url: filename,
411 dataType: "text",
412 }).then(function(data) {
413 var dataLines = data.split(/\r\n|\n/);
414 var headers;
415 var startIndex;
416 uniqueSpeakers = []; // used for obtaining unique colours
417 speakerObjects = []; // list of speaker items
418
419 if (manualHeader) { // headers for columns can be provided if not existent in csv
420 headers = manualHeader;
421 startIndex = 0;
422 } else {
423 headers = dataLines[0].split(',');
424 startIndex = 1;
425 }
426
427 for (var i = startIndex; i < dataLines.length; i++) {
428 var data = dataLines[i].split(',');
429 if (data.length == headers.length) {
430 var item = {};
431 for (var j = 0; j < headers.length; j++) {
432 item[headers[j]] = data[j];
433 if (j == 0) {
434 if (!uniqueSpeakers.includes(data[j])) {
435 uniqueSpeakers.push(data[j]);
436 }
437 }
438 }
439 speakerObjects.push(item);
440 }
441 }
442 populateChapters(speakerObjects);
443 });
444 }
445
446 function populateChapters(data) { // populates chapter section and adds regions to waveform
447 // colorbrewer is a web tool for guidance in choosing map colour schemes based on a variety of settings.
448 // this colour scheme is designed for qualitative data
449
450 if (uniqueSpeakers.length > 8) colourbrewerset = colorbrewer.Set2[8];
451 else if (uniqueSpeakers.length < 3) colourbrewerset = colorbrewer.Set2[3];
452 else colourbrewerset = colorbrewer.Set2[uniqueSpeakers.length];
453
454 for (var i = 0; i < data.length; i++) {
455 var chapter = document.createElement("div");
456 var speakerLetter = getLetter(data[i]);
457 chapter.id = "chapter" + i;
458 chapter.classList.add("chapter");
459 chapter.innerHTML = "Speaker " + speakerLetter + "<span class='speakerTime' id='" + "chapter" + i + "'>" + minutize(data[i].start) + " - " + minutize(data[i].end) + "</span>";
460 chapter.addEventListener("click", e => { chapterClicked(e.target.id) });
461 chapter.addEventListener("mouseover", e => { chapterEnter(e.target.id) });
462 chapter.addEventListener("mouseleave", e => { chapterLeave(e.target.id) });
463 chapters.appendChild(chapter);
464 // console.log("index: " + uniqueSpeakers.indexOf(data[i].speaker)%8);
465 // console.log("colour: " + colourbrewerset[uniqueSpeakers.indexOf(data[i].speaker)%8]);
466 wavesurfer.addRegion({
467 id: "region" + i,
468 start: data[i].start,
469 end: data[i].end,
470 drag: false,
471 resize: false,
472 color: colourbrewerset[uniqueSpeakers.indexOf(data[i].speaker)%8] + regionTransparency,
473 });
474 }
475 }
476
477 function loadJSONFile(filename) {
478 $.ajax({
479 type: "GET",
480 url: filename,
481 dataType: "text",
482 }).then(function(data){ populateWords(JSON.parse(data)) });
483 }
484
485 function populateWords(data) { // populates word section and adds regions to waveform
486 var transcription = data.transcription;
487 var words = data.words;
488 var wordContainer = document.createElement("div");
489 wordContainer.id = "word-container";
490 for (var i = 0; i < words.length; i++) {
491 var word = document.createElement("span");
492 word.id = "word" + i;
493 word.classList.add("word");
494 word.innerHTML = transcription.split(" ")[i];
495 word.addEventListener("click", e => { wordClicked(data, e.target.id) });
496 word.addEventListener("mouseover", e => { chapterEnter(e.target.id) });
497 word.addEventListener("mouseleave", e => { chapterLeave(e.target.id) });
498 wordContainer.appendChild(word);
499 wavesurfer.addRegion({
500 id: "region" + i,
501 start: words[i].startTime,
502 end: words[i].endTime,
503 drag: false,
504 resize: false,
505 color: "rgba(255, 255, 255, 0.1)",
506 });
507 }
508 chapters.appendChild(wordContainer);
509 }
510
511 var chapterClicked = function(id) { // plays audio from start of chapter
512 var index = id.replace("chapter", "");
513 var start = speakerObjects[index].start;
514 var end = speakerObjects[index].end;
515 // wavesurfer.play(start, end);
516 wavesurfer.play(start);
517 }
518
519 function wordClicked(data, id) { // plays audio from start of word
520 var index = id.replace("word", "");
521 var start = data.words[index].startTime;
522 var end = data.words[index].endTime;
523 // wavesurfer.play(start, end);
524 wavesurfer.play(start);
525 }
526
527 function chapterEnter(id) {
528 regionEnter(wavesurfer.regions.list["region" + id.replace(itemType, "")]);
529 }
530
531 function chapterLeave(id) {
532 regionLeave(wavesurfer.regions.list["region" + id.replace(itemType, "")]);
533 }
534
535 function handleRegionColours(region, highlight) { // handles region, chapter & word colours
536 var colour;
537 if (highlight) {
538 colour = "rgb(101, 116, 116)";
539 regionEnter(region);
540 } else {
541 colour = "";
542 regionLeave(region);
543 }
544 var regionIndex = region.id.replace("region","");
545 var corrItem = document.getElementById(itemType + regionIndex);
546 corrItem.style.backgroundColor = colour;
547 }
548
549 function regionEnter(region) {
550 region.update({ color: "rgba(255, 255, 255, 0.35)" });
551 }
552
553 function regionLeave(region) {
554 if (itemType == "chapter") {
555 if (!(wavesurfer.getCurrentTime() + 0.1 < region.end && wavesurfer.getCurrentTime() > region.start)) {
556 var index = region.id.replace("region", "");
557 region.update({ color: colourbrewerset[uniqueSpeakers.indexOf(speakerObjects[index].speaker)%8] + regionTransparency });
558 }
559 } else {
560 region.update({ color: "rgba(255, 255, 255, 0.1)" });
561 }
562 }
563
564 function minutize(num) { // converts seconds to m:ss for chapters & waveform hover
565 var seconds = Math.round(num % 60);
566 if (seconds.toString().length == 1) seconds = "0" + seconds;
567 return Math.floor(num / 60) + ":" + seconds;
568 }
569
570 function getLetter(val) {
571 var speakerNum = parseInt(val.speaker.replace("SPEAKER_",""));
572 return String.fromCharCode(65 + speakerNum); // 'A' == UTF-16 65
573 }
574}
575
576function formatAudioDuration(duration) {
577 console.log(duration);
578 var [hrs, mins, secs, ms] = duration.replace(".", ":").split(":");
579 return hrs + ":" + mins + ":" + secs;
580}
Note: See TracBrowser for help on using the repository browser.