diff options
context:
space:
mode:
Diffstat (limited to 'assets/cgit.js')
-rw-r--r--assets/cgit.js161
1 file changed, 161 insertions, 0 deletions
diff --git a/assets/cgit.js b/assets/cgit.js
index cda167e..4a0d088 100644
--- a/assets/cgit.js
+++ b/assets/cgit.js
@@ -123,6 +123,167 @@ document.addEventListener("DOMContentLoaded", function () {
})();
+/* Built-in Markdown rendering for the about page. When no about-filter is
+ * configured, cgit escapes a markdown readme into a data-markdown container
+ * (see cgit_print_repo_readme) and this renders a deliberately small, safe
+ * subset client-side: headings, lists, blockquotes, rules, fenced and inline
+ * code, pipe tables, links, images and emphasis. Every run of text is escaped
+ * before any markup is added, and link and image URLs are restricted to http,
+ * https, mailto and relative targets, so a hostile readme cannot inject markup
+ * or scripts. Fenced code keeps its data-lang so the highlighter below styles
+ * it. Without JavaScript the escaped source stays readable as plain text.
+ *
+ * This is intentionally a subset, not CommonMark: no reference links, raw HTML
+ * passthrough, nested lists or setext headings. Configure an about-filter to
+ * replace it, or set enable-markdown=0 to turn it off. */
+
+(function () {
+
+var MAX_BYTES = 400000;
+
+function esc(s) {
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+}
+
+function escAttr(s) {
+ return esc(s).replace(/"/g, "&quot;").replace(/'/g, "&#39;");
+}
+
+/* Return the url if its scheme is safe, else "". Whitespace and control bytes
+ * are stripped before the scheme is read because browsers ignore them when
+ * resolving it, so "java\nscript:..." must still be caught as javascript. */
+function safeUrl(url) {
+ url = (url || "").replace(/[\u0000-\u0020]+/g, "");
+ var scheme = /^([a-z][a-z0-9+.\-]*):/i.exec(url);
+ if (scheme && !/^(https?|mailto)$/i.test(scheme[1]))
+ return "";
+ return url;
+}
+
+function link(text, url, image) {
+ var u = safeUrl(url);
+ if (!u)
+ return image ? esc("![" + text + "]") : inline(text);
+ if (image)
+ return "<img src='" + escAttr(u) + "' alt='" + escAttr(text) + "'/>";
+ return "<a href='" + escAttr(u) + "'>" + inline(text) + "</a>";
+}
+
+/* Inline rendering over one block of text. Scans to the next marker character
+ * and bulk-escapes the plain text in between, so it stays roughly linear. */
+function inline(s) {
+ var out = "", i = 0, n = s.length, marker = /[`!\[*_]/g, m, rest;
+ while (i < n) {
+ marker.lastIndex = i;
+ m = marker.exec(s);
+ if (!m) { out += esc(s.slice(i)); break; }
+ if (m.index > i) { out += esc(s.slice(i, m.index)); i = m.index; }
+ rest = s.slice(i);
+ if ((m = /^`([^`]+)`/.exec(rest)))
+ out += "<code>" + esc(m[1]) + "</code>";
+ else if ((m = /^!\[([^\]]*)\]\(\s*([^)\s]+)[^)]*\)/.exec(rest)))
+ out += link(m[1], m[2], true);
+ else if ((m = /^\[([^\]]*)\]\(\s*([^)\s]+)[^)]*\)/.exec(rest)))
+ out += link(m[1], m[2], false);
+ else if ((m = /^(\*\*|__)([\s\S]+?)\1/.exec(rest)))
+ out += "<strong>" + inline(m[2]) + "</strong>";
+ else if ((m = /^(\*|_)([^\s][\s\S]*?)\1/.exec(rest)))
+ out += "<em>" + inline(m[2]) + "</em>";
+ else { out += esc(s.charAt(i)); i++; continue; }
+ i += m[0].length;
+ }
+ return out;
+}
+
+function cells(row) {
+ return row.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map(function (c) {
+ return c.trim();
+ });
+}
+
+function render(src) {
+ var lines = src.replace(/\r\n?/g, "\n").split("\n");
+ var out = "", i = 0, n = lines.length, line, m, k;
+ while (i < n) {
+ line = lines[i];
+ if (/^\s*$/.test(line)) { i++; continue; }
+ if ((m = /^\s*(`{3,}|~{3,})\s*([\w.+#-]*)/.exec(line))) {
+ var fence = m[1].charAt(0) === "`" ? /^\s*`{3,}\s*$/ : /^\s*~{3,}\s*$/;
+ var lang = m[2], code = "";
+ for (i++; i < n && !fence.test(lines[i]); i++)
+ code += lines[i] + "\n";
+ i++;
+ out += "<pre><code" + (lang ? " data-lang='" + escAttr(lang) + "'" : "") +
+ ">" + esc(code) + "</code></pre>";
+ continue;
+ }
+ if ((m = /^(#{1,6})\s+(.*?)\s*#*\s*$/.exec(line))) {
+ k = m[1].length;
+ out += "<h" + k + ">" + inline(m[2]) + "</h" + k + ">";
+ i++; continue;
+ }
+ if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) { out += "<hr/>"; i++; continue; }
+ if (/^\s*>/.test(line)) {
+ var q = "";
+ for (; i < n && /^\s*>/.test(lines[i]); i++)
+ q += lines[i].replace(/^\s*>\s?/, "") + "\n";
+ out += "<blockquote>" + render(q) + "</blockquote>";
+ continue;
+ }
+ if (line.indexOf("|") >= 0 && i + 1 < n &&
+ /^\s*\|?(\s*:?-+:?\s*\|)+\s*:?-+:?\s*\|?\s*$/.test(lines[i + 1])) {
+ var head = cells(line), t = "<table><thead><tr>";
+ for (k = 0; k < head.length; k++)
+ t += "<th>" + inline(head[k]) + "</th>";
+ t += "</tr></thead><tbody>";
+ for (i += 2; i < n && lines[i].indexOf("|") >= 0 && !/^\s*$/.test(lines[i]); i++) {
+ var row = cells(lines[i]);
+ t += "<tr>";
+ for (k = 0; k < row.length; k++)
+ t += "<td>" + inline(row[k]) + "</td>";
+ t += "</tr>";
+ }
+ out += t + "</tbody></table>";
+ continue;
+ }
+ if (/^\s*([-*+]|\d+[.)])\s+/.test(line)) {
+ var ordered = /^\s*\d/.test(line), tag = ordered ? "ol" : "ul";
+ out += "<" + tag + ">";
+ for (; i < n && (m = /^\s*([-*+]|\d+[.)])\s+(.*)$/.exec(lines[i])); i++) {
+ if ((/\d/.test(m[1])) !== ordered) break;
+ out += "<li>" + inline(m[2]) + "</li>";
+ }
+ out += "</" + tag + ">";
+ continue;
+ }
+ /* Always consume the current line so i advances even when it
+ * matched none of the block branches above. */
+ var para = lines[i++];
+ for (; i < n && !/^\s*$/.test(lines[i]) &&
+ !/^\s*(#{1,6}\s|>|`{3,}|~{3,}|([-*+]|\d+[.)])\s)/.test(lines[i]); i++)
+ para += "\n" + lines[i];
+ out += "<p>" + inline(para).replace(/\n/g, "<br/>") + "</p>";
+ }
+ return out;
+}
+
+document.addEventListener("DOMContentLoaded", function () {
+ var nodes = document.querySelectorAll("div#cgit [data-markdown]"), i, el, text;
+ for (i = 0; i < nodes.length; i++) {
+ el = nodes[i];
+ text = el.textContent;
+ if (!text || text.length > MAX_BYTES)
+ continue;
+ try {
+ el.innerHTML = render(text);
+ } catch (e) {
+ /* leave the escaped source in place on any failure */
+ }
+ }
+}, false);
+
+})();
+
/* Built-in syntax highlighting for the blob view. When no server-side
* source filter is configured, cgit tags the <code> element with
* data-lang set to the file's extension (or bare name, so Makefile and