diff options
Diffstat (limited to 'assets')
| -rw-r--r-- | assets/cgit.css | 90 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | assets/cgit.js | 161 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
2 files changed, 251 insertions, 0 deletions
diff --git a/assets/cgit.css b/assets/cgit.css index 96192e4..10fcf66 100644 --- a/assets/cgit.css +++ b/assets/cgit.css @@ -496,6 +496,96 @@ div#cgit div#summary pre { overflow-x: auto; } +div#cgit .markdown { + line-height: 1.6; + overflow-wrap: break-word; +} + +div#cgit .markdown > :first-child { + margin-top: 0; +} + +div#cgit .markdown > :last-child { + margin-bottom: 0; +} + +div#cgit .markdown h1, +div#cgit .markdown h2, +div#cgit .markdown h3, +div#cgit .markdown h4, +div#cgit .markdown h5, +div#cgit .markdown h6 { + margin: 1.2em 0 0.5em; + font-weight: bold; + line-height: 1.25; +} + +div#cgit .markdown h1 { font-size: 1.6em; } +div#cgit .markdown h2 { + font-size: 1.35em; + border-bottom: 1px solid var(--border); + padding-bottom: 0.2em; +} +div#cgit .markdown h3 { font-size: 1.15em; } +div#cgit .markdown h4 { font-size: 1em; } +div#cgit .markdown h5, +div#cgit .markdown h6 { font-size: 0.9em; color: var(--muted); } + +div#cgit .markdown p { margin: 0.7em 0; } +div#cgit .markdown a { color: var(--link); } + +div#cgit .markdown ul, +div#cgit .markdown ol { margin: 0.7em 0; padding-left: 2em; } +div#cgit .markdown li { margin: 0.2em 0; } + +div#cgit .markdown blockquote { + margin: 0.7em 0; + padding: 0.1em 1em; + color: var(--muted); + border-left: 3px solid var(--border-mid); +} + +div#cgit .markdown hr { + border: none; + border-top: 1px solid var(--border); + margin: 1.2em 0; +} + +div#cgit .markdown code { + background: var(--surface-2); + border: 1px solid var(--border); + border-radius: 3px; + padding: 0.1em 0.35em; + font-size: 0.95em; +} + +div#cgit .markdown pre { + background: var(--surface-2); + border: 1px solid var(--border); + border-radius: 4px; + padding: 0.7em 0.9em; + overflow-x: auto; +} + +div#cgit .markdown pre code { + background: none; + border: none; + padding: 0; +} + +div#cgit .markdown table { + border-collapse: collapse; + margin: 0.7em 0; +} + +div#cgit .markdown th, +div#cgit .markdown td { + border: 1px solid var(--border); + padding: 0.35em 0.7em; +} + +div#cgit .markdown th { background: var(--surface-2); } + div#cgit table#downloads { float: right; border-collapse: collapse; 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, "&").replace(/</g, "<").replace(/>/g, ">"); +} + +function escAttr(s) { + return esc(s).replace(/"/g, """).replace(/'/g, "'"); +} + +/* 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 |
