manual-racket.js (8947B)
1 /* For the Racket manual style */ 2 3 AddOnLoad(function() { 4 /* Look for header elements that have x-source-module and x-part tag. 5 For those elements, add a hidden element that explains how to 6 link to the section, and set the element's onclick() to display 7 the explanation. */ 8 var tag_names = ["h1", "h2", "h3", "h4", "h5"]; 9 for (var j = 0; j < tag_names.length; j++) { 10 elems = document.getElementsByTagName(tag_names[j]); 11 for (var i = 0; i < elems.length; i++) { 12 var elem = elems.item(i); 13 AddPartTitleOnClick(elem); 14 } 15 } 16 }) 17 18 // cache of source urls 19 var cache = {}; 20 21 function ParseSource(source, mod_path, single_collection) { 22 23 var source_url = new URL(source); 24 25 if (source_url.protocol == "github:") { 26 // browser URL parser only works with http(s) URLs 27 source_url = new URL("https" + source.substring(6)); 28 var host = source_url.host; 29 var url_path = source_url.pathname.substring(1).split("/"); 30 if (!(url_path.length >= 2)) return null; 31 var user = url_path.shift(); 32 var repo = url_path.shift(); 33 var branch = url_path.shift(); 34 var source_path = url_path.join("/"); 35 } 36 else if (("https:" == source_url.protocol) || ("git:" == source_url.protocol)) { 37 // browser URL parser only works with http(s) URLs 38 if ("git:" == source_url.protocol) 39 source_url = new URL("https" + source.substring(3)); 40 41 var host = source_url.host; 42 var source_path = source_url.searchParams.get("path"); 43 var branch = (source_url.hash || "#master").substring(1); 44 var url_path = source_url.pathname.substring(1).split("/"); 45 if (url_path.length < 2) throw [source_url.pathname, url_path]; 46 var user = url_path.shift(); 47 var repo = url_path.shift(); 48 var mtch = repo.match(/(.*)\.git$/); 49 if (mtch) repo = mtch[1]; 50 51 } 52 else return null; 53 54 var mod_path_re = /^\(lib "(.+)"\)$/; 55 56 var mod_path_elems = mod_path && mod_path.match(mod_path_re)[1].split("/"); 57 58 if (!user || !repo || !mod_path_elems) 59 return null; 60 if (single_collection) 61 mod_path_elems.shift(); 62 63 var file_path = mod_path_elems.join("/"); 64 65 66 if (source_path) { 67 file_path = source_path + "/" + file_path; 68 } 69 70 return { user: user, 71 repo: repo, 72 file_path: file_path, 73 branch: branch, 74 host: host }; 75 } 76 77 function AddSourceElement(pkg_url, info) { 78 info.appendChild(document.createTextNode("Document source ")); 79 var url_line = document.createElement("div"); 80 var a = document.createElement("a"); 81 a.href = pkg_url; 82 a.style.whiteSpace = "nowrap"; 83 a.appendChild(document.createTextNode(pkg_url)); 84 addSpan(url_line, "\xA0", "RktRdr"); 85 url_line.appendChild(a); 86 info.appendChild(url_line); 87 } 88 89 var prefixes = { "github.com": "tree", 90 "gitlab.com": "-/blob" }; 91 92 93 function AddSourceUrl(source, mod_path, collection, info) { 94 // multi is encoded as an array, empty as false 95 single_collection = (typeof collection === "string"); 96 97 var parsed = source && mod_path && ParseSource(source, mod_path, single_collection); 98 99 if (!parsed) return; 100 101 prefix = prefixes.hasOwnProperty(parsed.host) && prefixes[parsed.host]; 102 if (!prefix) return; 103 104 var correct_url = "https://" + [parsed.host, parsed.user, parsed.repo, prefix, parsed.branch, parsed.file_path].join("/"); 105 106 if (info) AddSourceElement(correct_url, info); 107 } 108 109 function addSpan(dest, str, cn) { 110 var s = document.createElement("span"); 111 s.className = cn; 112 s.style.whiteSpace = "nowrap"; 113 s.appendChild(document.createTextNode(str)); 114 dest.appendChild(s); 115 } 116 117 118 // test cases 119 if (false) { 120 console.log(ParseSource("git://gitlab.com/benn/foo?path=xxx", 121 '(lib "asn1/scribblings/asn1.scrbl")', 122 false)) 123 console.log(ParseSource("github://github.com/carl-eastlund/mischief/master", 124 '(lib "asn1/scribblings/asn1.scrbl")', 125 false)) 126 console.log(ParseSource("github://github.com/carl-eastlund/mischief/stable/dir", 127 '(lib "asn1/scribblings/asn1.scrbl")', 128 false)) 129 130 console.log(ParseSource("git://github.com/racket/racket/?path=pkgs/racket-doc", 131 '(lib "asn1/scribblings/asn1.scrbl")', 132 false)); 133 134 console.log(ParseSource("git://github.com/rmculpepper/asn1.git?path=asn1-doc", 135 '(lib "asn1/scribblings/asn1.scrbl")', 136 true)); 137 console.log(ParseSource("git://github.com/rmculpepper/asn1", 138 '(lib "asn1/scribblings/asn1.scrbl")', 139 true)); 140 console.log(ParseSource("git://github.com/rmculpepper/asn1", 141 '(lib "asn1/scribblings/asn1.scrbl")', 142 false)); 143 } 144 145 function AddPartTitleOnClick(elem) { 146 var mod_path = elem.getAttribute("x-source-module"); 147 var tag = elem.getAttribute("x-part-tag"); 148 var source_pkg = elem.getAttribute("x-source-pkg"); 149 150 // create here to share 151 var info = document.createElement("div"); 152 153 154 // tag is not needed, but this way we can add the element in only one place 155 // avoid failing on browser that don't have `fetch` 156 if (mod_path && source_pkg && tag && window.fetch) { 157 158 var cached = cache[mod_path] 159 if (cached) { 160 AddSourceElement(cached[0], mod_path, cached[1], info); 161 } 162 else { 163 fetch("https://pkgs.racket-lang.org/pkg/" + source_pkg + ".json") 164 .then(function (response) { return response.json(); }) 165 .then(function (data) { 166 var vers = data["versions"] || {}; 167 var def = vers["default"] || {}; 168 var source = def["source"] || undefined; 169 var collection = data["collection"]; 170 if (source) { 171 cache[mod_path] = [source, collection]; 172 AddSourceUrl(source, mod_path, collection, info); 173 } 174 }); 175 } 176 } 177 178 if (mod_path && tag) { 179 // Might not be present: 180 var prefixes = elem.getAttribute("x-part-prefixes"); 181 182 info.className = "RPartExplain"; 183 184 /* The "top" tag refers to a whole document: */ 185 var is_top = (tag == "\"top\""); 186 info.appendChild(document.createTextNode("Link to this " 187 + (is_top ? "document" : "section") 188 + " with ")); 189 190 /* Break `secref` into two lines if the module path and tag 191 are long enough: */ 192 var is_long = (is_top ? false : ((mod_path.length 193 + tag.length 194 + (prefixes ? (16 + prefixes.length) : 0)) 195 > 60)); 196 197 var line1 = document.createElement("div"); 198 var line1x = ((is_long && prefixes) ? document.createElement("div") : line1); 199 var line2 = (is_long ? document.createElement("div") : line1); 200 201 /* Construct a `secref` call with suitable syntax coloring: */ 202 addSpan(line1, "\xA0@", "RktRdr"); 203 addSpan(line1, (is_top ? "other-doc" : "secref"), "RktSym"); 204 addSpan(line1, "[", "RktPn"); 205 if (!is_top) 206 addSpan(line1, tag, "RktVal"); 207 if (is_long) { 208 /* indent additional lines: */ 209 if (prefixes) 210 addSpan(line1x, "\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0", "RktPn"); 211 addSpan(line2, "\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0", "RktPn"); 212 } 213 if (prefixes) { 214 addSpan(line1x, " #:tag-prefixes ", "RktPn"); 215 addSpan(line1x, "'", "RktVal"); 216 addSpan(line1x, prefixes, "RktVal"); 217 } 218 if (!is_top) 219 addSpan(line2, " #:doc ", "RktPn"); 220 addSpan(line2, "'", "RktVal"); 221 addSpan(line2, mod_path, "RktVal"); 222 addSpan(line2, "]", "RktPn"); 223 224 info.appendChild(line1); 225 if (is_long) 226 info.appendChild(line1x); 227 if (is_long) 228 info.appendChild(line2); 229 230 info.style.display = "none"; 231 232 /* Add the new element afterthe header: */ 233 var n = elem.nextSibling; 234 if (n) 235 elem.parentNode.insertBefore(info, n); 236 else 237 elem.parentNode.appendChild(info); 238 239 /* Clicking the header shows the explanation element: */ 240 elem.onclick = function () { 241 if (info.style.display == "none") 242 info.style.display = "block"; 243 else 244 info.style.display = "none"; 245 } 246 } 247 }