Ignore:
Timestamp:
2022-05-26T15:06:05+12:00 (2 years ago)
Author:
davidb
Message:

various improvements to enriched audio player

File:
1 edited

Legend:

Unmodified
Added
Removed
  • main/trunk/greenstone3/web/interfaces/default/js/utility_scripts.js

    r36196 r36217  
    1111*/
    1212function 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;
     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;
    2121}
    2222
    2323/*
    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.
     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.
    5757*/
    5858
    5959/* 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()
     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()
    6565*/
    6666function 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;
     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;
    7676}
    7777
    7878/*
    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.
     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.
    8181*/
    8282function 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;
     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;
    9595}
    9696
     
    9999***************/
    100100function 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();
     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();
    124124}
    125125
     
    127127function floatMenu(enabled)
    128128{
    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);
     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);
    167167}
    168168
     
    174174
    175175function 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   }
     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   }
    221221}
    222222
    223223function 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   }
     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   }
    236236}
    237237
    238238function 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   });
     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   });
    260260};
    261261
     
    263263
    264264function loadAudio(audio, sectionData) {
    265   var speakerObjects = [];
    266   var uniqueSpeakers;
    267   const inputFile = sectionData;
    268   var itemType;
    269 
    270   var accentColour = "#4CA72D";
    271   var regionTransparency = "59";
    272 
    273   var waveformContainer = document.getElementById("waveform");
    274  
    275   var wavesurfer = WaveSurfer.create({ // wavesurfer options
    276      container: waveformContainer,
    277      backend: "MediaElement",
    278      backgroundColor: "rgb(54, 73, 78)",
    279      waveColor: "white",
    280      progressColor: ["white", accentColour],
    281      barWidth: 2,
    282      barHeight: 1.2,
    283      barGap: 2,
    284      barRadius: 1,
    285      cursorColor: 'black',
    286      plugins: [
    287         WaveSurfer.regions.create(),
    288         WaveSurfer.timeline.create({
    289            container: "#wave-timeline",
    290            primaryColor: "white",
    291            secondaryColor: "white",
    292            primaryFontColor: "white",
    293            secondaryFontColor: "white"
    294         }),
    295         WaveSurfer.cursor.create({
    296            showTime: true,
    297            opacity: 1,
    298            customShowTimeStyle: {
    299                'background-color': '#000',
    300                color: '#fff',
    301                padding: '0.2em',
    302                'font-size': '12px'
    303            },
    304            formatTimeCallback: (num) => {
    305               return minutize(num);
    306            }
    307         }),
    308      ],
    309   });
    310 
    311   wavesurfer.load(audio);
    312 
    313   // wavesurfer events
    314 
    315   wavesurfer.on('region-click', function(region, e) { // play region audio on click
    316      e.stopPropagation();
    317      regionMouseEvent(region, true);
    318      wavesurfer.play(region.start); // plays from start of region
    319      // region.play(); // plays region only
    320   });
    321 
    322   wavesurfer.on('region-mouseenter', function(region) { regionMouseEvent(region, true); });
    323   wavesurfer.on('region-mouseleave', function(region) { if (!(wavesurfer.getCurrentTime() <= region.end && wavesurfer.getCurrentTime() >= region.start)) regionMouseEvent(region, false); });
    324   wavesurfer.on('region-in', function(region) {
    325      regionMouseEvent(region, true);
    326      if (itemType == "chapter") {
    327         document.getElementById("chapter" + region.id.replace("region", "")).scrollIntoView({
    328            behavior: "smooth",
    329            block: "center"
    330         });
    331      }
    332   });
    333   wavesurfer.on('region-out', function(region) { regionMouseEvent(region, false); });
    334 
    335   var loader = document.createElement("span"); // loading audio element
    336   loader.innerHTML = "Loading audio";
    337   loader.id = "waveform-loader";
    338   document.querySelector("#waveform wave").prepend(loader);
    339 
    340   wavesurfer.on('waveform-ready', function() { // retrieve regions once waveforms have loaded
    341      if (inputFile.endsWith("csv")) { // diarization if csv
    342         itemType = "chapter";
    343         loadCSVFile(inputFile, ["speaker", "start", "end"]);
    344      } else if (inputFile.endsWith("json")) { // transcription if json
    345         itemType = "word";
    346         loadJSONFile(inputFile);
    347      } else {
    348         console.log("Filetype of " + inputFile + " not supported.")
    349      }
    350      loader.remove(); // remove load text
    351   })
    352 
    353   // toolbar elements & event handlers
    354   var chapters = document.getElementById("chapters");
    355   var chapterButton = document.getElementById("chapterButton");
    356   var backButton = document.getElementById("backButton");
    357   var playPauseButton = document.getElementById("playPauseButton");
    358   var forwardButton = document.getElementById("forwardButton");
    359   var muteButton = document.getElementById("muteButton");
    360   var zoomSlider = document.getElementById("slider");
    361   chapters.style.height = "0px";
    362   chapterButton.addEventListener("click", function() { toggleChapters(); });
    363   backButton.addEventListener("click", function() { wavesurfer.skipBackward(); });
    364   playPauseButton.addEventListener("click", function() { wavesurfer.playPause() });
    365   forwardButton.addEventListener("click", function() { wavesurfer.skipForward(); });
    366   muteButton.addEventListener("click", function() { wavesurfer.toggleMute(); });
    367   zoomSlider.style["accent-color"] = accentColour;
    368 
    369   // path to toolbar images
    370   var interface_bootstrap_images = "interfaces/" + gs.xsltParams.interface_name + "/images/bootstrap/";
    371 
    372   wavesurfer.on("play", function() { playPauseButton.src = interface_bootstrap_images + "pause.svg"; });
    373   wavesurfer.on("pause", function() { playPauseButton.src = interface_bootstrap_images + "play.svg"; });
    374   wavesurfer.on("mute", function(mute) {
    375      if (mute) {
    376         muteButton.src = interface_bootstrap_images + "mute.svg";
    377         muteButton.style.opacity = 0.6;
    378      }
    379      else {
    380         muteButton.src = interface_bootstrap_images + "unmute.svg";
    381         muteButton.style.opacity = 1;
    382      }
    383   });
    384 
    385   zoomSlider.oninput = function () { // slider changes waveform zoom
    386      wavesurfer.zoom(Number(this.value) / 4);
    387   };
    388   wavesurfer.zoom(zoomSlider.value / 4); // set default zoom point
    389 
    390   var toggleChapters = function() { // show & hide chapter section
    391      if (chapters.style.height == "0px") {
    392         chapters.style.height = "30vh";
    393      } else {
    394         chapters.style.height = "0px";
    395      }
    396   }
    397 
    398   function loadCSVFile(filename, manualHeader) { // based around: https://stackoverflow.com/questions/7431268/how-to-read-data-from-csv-file-using-javascript
    399      $.ajax({
    400         type: "GET",
    401         url: filename,
    402         dataType: "text",
    403      }).then(function(data) {
    404         var dataLines = data.split(/\r\n|\n/);
    405         var headers;
    406         var startIndex;
    407         uniqueSpeakers = []; // used for obtaining unique colours
    408         speakerObjects = []; // list of speaker items
    409 
    410         if (manualHeader) { // headers for columns can be provided if not existent in csv
    411            headers = manualHeader;
    412            startIndex = 0;
    413         } else {
    414            headers = dataLines[0].split(',');
    415            startIndex = 1;
    416         }
    417 
    418         for (var i = startIndex; i < dataLines.length; i++) {
    419            var data = dataLines[i].split(',');
    420            if (data.length == headers.length) {
    421               var item = {};
    422               for (var j = 0; j < headers.length; j++) {
    423                  item[headers[j]] = data[j];
    424                  if (j == 0) {
    425                     if (!uniqueSpeakers.includes(data[j])) {
    426                        uniqueSpeakers.push(data[j]);
    427                     }
    428                  }
    429               }
    430               speakerObjects.push(item);
    431            }
    432         }
    433         populateChapters(speakerObjects);
    434      });
    435   }
    436 
    437   function populateChapters(data) { // populates chapter section and adds regions to waveform
    438      // colorbrewer is a web tool for guidance in choosing map colour schemes based on a variety of settings.
    439      // this colour scheme is designed for qualitative data
    440      colourbrewerset = colorbrewer.Set2[uniqueSpeakers.length];
    441      for (var i = 0; i < data.length; i++) {
    442         var chapter = document.createElement("div");
    443         chapter.id = "chapter" + i;
    444         chapter.classList.add("chapter");
    445         chapter.innerHTML = data[i].speaker + "<span class='speakerTime' id='" + "chapter" + i + "'>" + minutize(data[i].start) + " - " + minutize(data[i].end) + "</span>";
    446         chapter.addEventListener("click", e => { chapterClicked(e.target.id) });
    447         chapter.addEventListener("mouseover", e => { chapterEnter(e.target.id) });
    448         chapter.addEventListener("mouseleave", e => { chapterLeave(e.target.id) });
    449         chapters.appendChild(chapter);
    450         wavesurfer.addRegion({
    451            id: "region" + i,
    452            start: data[i].start,
    453            end: data[i].end,
    454            drag: false,
    455            resize: false,
    456            color: colourbrewerset[uniqueSpeakers.indexOf(data[i].speaker)] + regionTransparency,
    457         });
    458      }
    459   }
    460 
    461   function loadJSONFile(filename) {
    462      $.ajax({
    463         type: "GET",
    464         url: filename,
    465         dataType: "text",
    466      }).then(function(data){ populateWords(JSON.parse(data)) });
    467   }
    468 
    469   function populateWords(data) { // populates word section and adds regions to waveform
    470      var transcription = data.transcription;
    471      var words = data.words;
    472      var wordContainer = document.createElement("div");
    473      wordContainer.id = "word-container";
    474      for (var i = 0; i < words.length; i++) {
    475         var word = document.createElement("span");
    476         word.id = "word" + i;
    477         word.classList.add("word");
    478         word.innerHTML = transcription.split(" ")[i];
    479         word.addEventListener("click", e => { wordClicked(data, e.target.id) });
    480         word.addEventListener("mouseover", e => { chapterEnter(e.target.id) });
    481         word.addEventListener("mouseleave", e => { chapterLeave(e.target.id) });
    482         wordContainer.appendChild(word);
    483         wavesurfer.addRegion({
    484            id: "region" + i,
    485            start: words[i].startTime,
    486            end: words[i].endTime,
    487            drag: false,
    488            resize: false,
    489            color: "rgba(255, 255, 255, 0.1)",
    490         });
    491      }
    492      chapters.appendChild(wordContainer);
    493   }
    494 
    495   var chapterClicked = function(id) { // plays audio from start of chapter
    496      var index = id.replace("chapter", "");
    497      var start = speakerObjects[index].start;
    498      var end = speakerObjects[index].end;
    499      // wavesurfer.play(start, end);
    500      wavesurfer.play(start);
    501   }
    502 
    503   function wordClicked(data, id) { // plays audio from start of word
    504      var index = id.replace("word", "");
    505      var start = data.words[index].startTime;
    506      var end = data.words[index].endTime;
    507      // wavesurfer.play(start, end);
    508      wavesurfer.play(start);
    509   }
    510 
    511   function chapterEnter(id) {
    512      regionEnter(wavesurfer.regions.list["region" + id.replace(itemType, "")]);
    513   }
    514 
    515   function chapterLeave(id) {
    516      regionLeave(wavesurfer.regions.list["region" + id.replace(itemType, "")]);
    517   }
    518 
    519   function regionMouseEvent(region, highlight) { // handles region, chapter & word colours
    520      var colour;
    521      if (highlight) {
    522         colour = "rgb(101, 116, 116)";
    523         regionEnter(region);
    524      } else {
    525         colour = "";
    526         regionLeave(region);
    527      }
    528      var regionIndex = region.id.replace("region","");
    529      var corrItem = document.getElementById(itemType + regionIndex);
    530      corrItem.style.backgroundColor = colour;
    531   }
    532 
    533   function regionEnter(region) {
    534      region.update({ color: "rgba(255, 255, 255, 0.35)" });
    535   }
    536 
    537   function regionLeave(region) {
    538      if (itemType == "chapter") {
    539         if (!(wavesurfer.getCurrentTime() + 0.1 < region.end && wavesurfer.getCurrentTime() > region.start)) {
    540            var index = region.id.replace("region", "");
    541            region.update({ color: colourbrewerset[uniqueSpeakers.indexOf(speakerObjects[index].speaker)] + regionTransparency });
    542         }
    543      } else {
    544         region.update({ color: "rgba(255, 255, 255, 0.1)" });
    545      }
    546   }
    547 
    548   function minutize(num) { // converts seconds to m:ss
    549      var seconds = Math.round(num % 60);
    550      if (seconds.toString().length == 1) seconds = "0" + seconds;
    551      return Math.floor(num / 60) + ":" + seconds;
    552   }
    553 }
     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: ["white", 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: "center"
     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   {
     354      var link = document.createElement("a");
     355      link.download = name;
     356      link.href = loc;
     357      link.click();
     358   }
     359
     360   // toolbar elements & event handlers
     361   var chapters = document.getElementById("chapters");
     362   var chapterButton = document.getElementById("chapterButton");
     363   var backButton = document.getElementById("backButton");
     364   var playPauseButton = document.getElementById("playPauseButton");
     365   var forwardButton = document.getElementById("forwardButton");
     366   var downloadButton = document.getElementById("downloadButton");
     367   var muteButton = document.getElementById("muteButton");
     368   var zoomSlider = document.getElementById("slider");
     369   chapters.style.height = "0px";
     370   chapterButton.addEventListener("click", function() { toggleChapters(); });
     371   backButton.addEventListener("click", function() { wavesurfer.skipBackward(); });
     372   playPauseButton.addEventListener("click", function() { wavesurfer.playPause() });
     373   forwardButton.addEventListener("click", function() { wavesurfer.skipForward(); });
     374   // audio = /greenstone3/library/sites/localsite/collect/tiriana-audio/index/assoc/HASHa6b7.dir/Te_Kakano_CH09_B.mp3
     375   downloadButton.addEventListener("click", function() { downloadURI(audio, audio.split(".dir/")[1]) });
     376   muteButton.addEventListener("click", function() { wavesurfer.toggleMute(); });
     377   zoomSlider.style["accent-color"] = accentColour;
     378
     379   // path to toolbar images
     380   var interface_bootstrap_images = "interfaces/" + gs.xsltParams.interface_name + "/images/bootstrap/";
     381
     382   wavesurfer.on("play", function() { playPauseButton.src = interface_bootstrap_images + "pause.svg"; });
     383   wavesurfer.on("pause", function() { playPauseButton.src = interface_bootstrap_images + "play.svg"; });
     384   wavesurfer.on("mute", function(mute) {
     385      if (mute) {
     386         muteButton.src = interface_bootstrap_images + "mute.svg";
     387         muteButton.style.opacity = 0.6;
     388      }
     389      else {
     390         muteButton.src = interface_bootstrap_images + "unmute.svg";
     391         muteButton.style.opacity = 1;
     392      }
     393   });
     394
     395   zoomSlider.oninput = function () { // slider changes waveform zoom
     396      wavesurfer.zoom(Number(this.value) / 4);
     397   };
     398   wavesurfer.zoom(zoomSlider.value / 4); // set default zoom point
     399
     400   var toggleChapters = function() { // show & hide chapter section
     401      if (chapters.style.height == "0px") {
     402         chapters.style.height = "30vh";
     403      } else {
     404         chapters.style.height = "0px";
     405      }
     406   }
     407
     408   function loadCSVFile(filename, manualHeader) { // based around: https://stackoverflow.com/questions/7431268/how-to-read-data-from-csv-file-using-javascript
     409      $.ajax({
     410         type: "GET",
     411         url: filename,
     412         dataType: "text",
     413      }).then(function(data) {
     414         var dataLines = data.split(/\r\n|\n/);
     415         var headers;
     416         var startIndex;
     417         uniqueSpeakers = []; // used for obtaining unique colours
     418         speakerObjects = []; // list of speaker items
     419
     420         if (manualHeader) { // headers for columns can be provided if not existent in csv
     421            headers = manualHeader;
     422            startIndex = 0;
     423         } else {
     424            headers = dataLines[0].split(',');
     425            startIndex = 1;
     426         }
     427
     428         for (var i = startIndex; i < dataLines.length; i++) {
     429            var data = dataLines[i].split(',');
     430            if (data.length == headers.length) {
     431               var item = {};
     432               for (var j = 0; j < headers.length; j++) {
     433                  item[headers[j]] = data[j];
     434                  if (j == 0) {
     435                     if (!uniqueSpeakers.includes(data[j])) {
     436                        uniqueSpeakers.push(data[j]);
     437                     }
     438                  }
     439               }
     440               speakerObjects.push(item);
     441            }
     442         }
     443         populateChapters(speakerObjects);
     444      });
     445   }
     446
     447   function populateChapters(data) { // populates chapter section and adds regions to waveform
     448      // colorbrewer is a web tool for guidance in choosing map colour schemes based on a variety of settings.
     449      // this colour scheme is designed for qualitative data
     450
     451      if (uniqueSpeakers.length > 8) colourbrewerset = colorbrewer.Set2[8];
     452      else if (uniqueSpeakers.length < 3) colourbrewerset = colorbrewer.Set2[3];
     453      else  colourbrewerset = colorbrewer.Set2[uniqueSpeakers.length];
     454
     455      for (var i = 0; i < data.length; i++) {
     456         var chapter = document.createElement("div");
     457         var speakerLetter = getLetter(data[i]);
     458         chapter.id = "chapter" + i;
     459         chapter.classList.add("chapter");
     460         chapter.innerHTML = "Speaker " + speakerLetter + "<span class='speakerTime' id='" + "chapter" + i + "'>" + minutize(data[i].start) + " - " + minutize(data[i].end) + "</span>";
     461         chapter.addEventListener("click", e => { chapterClicked(e.target.id) });
     462         chapter.addEventListener("mouseover", e => { chapterEnter(e.target.id) });
     463         chapter.addEventListener("mouseleave", e => { chapterLeave(e.target.id) });
     464         chapters.appendChild(chapter);
     465         // console.log("index: " + uniqueSpeakers.indexOf(data[i].speaker)%8);
     466         // console.log("colour: " + colourbrewerset[uniqueSpeakers.indexOf(data[i].speaker)%8]);
     467         wavesurfer.addRegion({
     468            id: "region" + i,
     469            start: data[i].start,
     470            end: data[i].end,
     471            drag: false,
     472            resize: false,
     473            color: colourbrewerset[uniqueSpeakers.indexOf(data[i].speaker)%8] + regionTransparency,
     474         });
     475      }
     476   }
     477
     478   function loadJSONFile(filename) {
     479      $.ajax({
     480         type: "GET",
     481         url: filename,
     482         dataType: "text",
     483      }).then(function(data){ populateWords(JSON.parse(data)) });
     484   }
     485
     486   function populateWords(data) { // populates word section and adds regions to waveform
     487      var transcription = data.transcription;
     488      var words = data.words;
     489      var wordContainer = document.createElement("div");
     490      wordContainer.id = "word-container";
     491      for (var i = 0; i < words.length; i++) {
     492         var word = document.createElement("span");
     493         word.id = "word" + i;
     494         word.classList.add("word");
     495         word.innerHTML = transcription.split(" ")[i];
     496         word.addEventListener("click", e => { wordClicked(data, e.target.id) });
     497         word.addEventListener("mouseover", e => { chapterEnter(e.target.id) });
     498         word.addEventListener("mouseleave", e => { chapterLeave(e.target.id) });
     499         wordContainer.appendChild(word);
     500         wavesurfer.addRegion({
     501            id: "region" + i,
     502            start: words[i].startTime,
     503            end: words[i].endTime,
     504            drag: false,
     505            resize: false,
     506            color: "rgba(255, 255, 255, 0.1)",
     507         });
     508      }
     509      chapters.appendChild(wordContainer);
     510   }
     511
     512   var chapterClicked = function(id) { // plays audio from start of chapter
     513      var index = id.replace("chapter", "");
     514      var start = speakerObjects[index].start;
     515      var end = speakerObjects[index].end;
     516      // wavesurfer.play(start, end);
     517      wavesurfer.play(start);
     518   }
     519
     520   function wordClicked(data, id) { // plays audio from start of word
     521      var index = id.replace("word", "");
     522      var start = data.words[index].startTime;
     523      var end = data.words[index].endTime;
     524      // wavesurfer.play(start, end);
     525      wavesurfer.play(start);
     526   }
     527
     528   function chapterEnter(id) {
     529      regionEnter(wavesurfer.regions.list["region" + id.replace(itemType, "")]);
     530   }
     531
     532   function chapterLeave(id) {
     533      regionLeave(wavesurfer.regions.list["region" + id.replace(itemType, "")]);
     534   }
     535
     536   function handleRegionColours(region, highlight) { // handles region, chapter & word colours
     537      var colour;
     538      if (highlight) {
     539         colour = "rgb(101, 116, 116)";
     540         regionEnter(region);
     541      } else {
     542         colour = "";
     543         regionLeave(region);
     544      }
     545      var regionIndex = region.id.replace("region","");
     546      var corrItem = document.getElementById(itemType + regionIndex);
     547      corrItem.style.backgroundColor = colour;
     548   }
     549
     550   function regionEnter(region) {
     551      region.update({ color: "rgba(255, 255, 255, 0.35)" });
     552   }
     553
     554   function regionLeave(region) {
     555      if (itemType == "chapter") {
     556         if (!(wavesurfer.getCurrentTime() + 0.1 < region.end && wavesurfer.getCurrentTime() > region.start)) {
     557            var index = region.id.replace("region", "");
     558            region.update({ color: colourbrewerset[uniqueSpeakers.indexOf(speakerObjects[index].speaker)%8] + regionTransparency });
     559         }
     560      } else {
     561         region.update({ color: "rgba(255, 255, 255, 0.1)" });
     562      }
     563   }
     564
     565   function minutize(num) { // converts seconds to m:ss for chapters & waveform hover
     566      var seconds = Math.round(num % 60);
     567      if (seconds.toString().length == 1) seconds = "0" + seconds;
     568      return Math.floor(num / 60) + ":" + seconds;
     569   }
     570
     571   function getLetter(val) {
     572      var speakerNum = parseInt(val.speaker.replace("SPEAKER_",""));
     573      return String.fromCharCode(65 + speakerNum); // 'A' == UTF-16 65
     574   }
     575}
     576
     577function formatAudioDuration(duration) {
     578   console.log(duration);
     579   var [hrs, mins, secs, ms] = duration.replace(".", ":").split(":");
     580   return hrs + ":" + mins + ":" + secs;
     581}
Note: See TracChangeset for help on using the changeset viewer.