1const textarea = document.getElementById("markdown"); 2const preview = document.getElementById("preview"); 3 4populateSrc( 5 `## Load a splits file to generate timestamps 6 \nYou can copy the source and paste it under your speedrun.com run description.` 7); 8 9function handleCopyLocation() { 10 navigator.clipboard.writeText(textarea.value); 11 document.getElementById("copy-tooltip").style.visibility = "visible"; 12 setTimeout(() => { 13 document.getElementById("copy-tooltip").style.visibility = "hidden"; 14 }, 1000); 15} 16 17function populateSrc(value) { 18 textarea.value = value; 19 populatePreview(value); 20} 21 22function populatePreview(value) { 23 preview.innerHTML = marked.parse(value.replaceAll("â", " ")); 24} 25 26function onloadSplitsFile(e) { 27 var file = e.target.files[0]; 28 if (file) { 29 readFile(file); 30 } 31} 32 33function onChangeSettings() { 34 var file = document.getElementById("fileInput").files[0]; 35 if (file) { 36 readFile(file); 37 } 38} 39 40function readFile(file) { 41 let reader = new FileReader(); 42 reader.readAsText(file, "UTF-8"); 43 reader.onload = function (evt) { 44 parseXML(evt.target.result); 45 }; 46 reader.onerror = function (evt) { 47 populateSrc("error reading file"); 48 }; 49} 50 51function parseStime(stime) { 52 const [hh, mm, ssFraction] = stime.split(":"); 53 return ( 54 parseInt(hh) * 3600000 + 55 parseInt(mm) * 60000 + 56 parseFloat(ssFraction) * 1000 57 ); 58} 59 60function formatMilliseconds(ms, decimals) { 61 const factor = 10 ** decimals; 62 const rounded = Math.round(ms * factor) / factor; 63 64 let totalSeconds = Math.floor(rounded / 1000); 65 let fractional = Math.round((rounded % 1000) * factor / 1000); 66 67 if (fractional === factor) { 68 totalSeconds += 1; 69 fractional = 0; 70 } 71 72 const hours = Math.floor(totalSeconds / 3600); 73 const minutes = Math.floor((totalSeconds % 3600) / 60); 74 const seconds = totalSeconds % 60; 75 76 const pad = (n, len = 2) => n.toString().padStart(len, "0"); 77 78 let time = 79 hours > 0 80 ? `${hours}:${pad(minutes)}:${pad(seconds)}` 81 : minutes > 0 82 ? `${minutes}:${pad(seconds)}` 83 : `${seconds}`; 84 85 if (decimals > 0) { 86 time += `.${fractional.toString().padStart(decimals, "0")}`; 87 } 88 89 return time; 90} 91 92function parseXML(xmlString) { 93 let parser = new DOMParser(); 94 let xmlDoc = parser.parseFromString(xmlString, "text/xml"); 95 96 const spaceValue = parseInt(document.getElementById("spacing").value); 97 const decimals = parseInt(document.getElementById("decimals").value); 98 const timingMethod = document.getElementById("timing-method").value; 99 100 let result = ""; 101 let count = 1; 102 let prevTime = 0; 103 result += "## [Time Splits](https://gmpranav.github.io/src-splits-generator/)\n\n"; 104 const spaceStr = "â".repeat(spaceValue); 105 result += `| **#** |â| **Name** |â${spaceStr}| **Finished At** |â${spaceStr}| **Split** |\n`; 106 result += "|---|---|---|---|:---:|---|:---:|\n"; 107 108 xmlDoc.querySelectorAll("Run Segments Segment").forEach((segment) => { 109 const splitName = segment 110 .querySelector("Name") 111 .textContent.split("}") 112 .pop() 113 .replace(/^(\s|-)+/, ""); 114 const realTime = segment.querySelector( 115 `SplitTimes SplitTime[name="Personal Best"] ${timingMethod}` 116 )?.textContent; 117 118 if (realTime) { 119 const parsedTime = parseStime(realTime); 120 const splitTime = parsedTime - prevTime; 121 122 result += `| ${count} || ${splitName} || ${formatMilliseconds(parsedTime, decimals)} || ${formatMilliseconds(splitTime, decimals)} |\n`; 123 prevTime = parsedTime; 124 } 125 else { 126 result += `| ${count} || ${splitName} || - || - |\n`; 127 prevTime = "-"; 128 } 129 count++; 130 }); 131 132 populateSrc(result); 133}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.