diff options
| -rw-r--r-- | assets/cgit.css | 90 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | assets/cgit.js | 161 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | cgitrc.5.txt | 17 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rwxr-xr-x | extensions/html-converters/md2html | 304 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rwxr-xr-x | extensions/html-converters/rst2html | 2 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/cgit.c | 3 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/cgit.h | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-blob.c | 7 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-blob.h | 2 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-summary.c | 48 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
10 files changed, 313 insertions, 322 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 diff --git a/cgitrc.5.txt b/cgitrc.5.txt index fd420f1..b5315cc 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -31,8 +31,9 @@ about-filter:: about pages (both top-level and for each repository). The command will get the content of the about-file on its STDIN, the name of the file as the first argument, and the STDOUT from the command will be - included verbatim on the about page. Default value: none. See - also: "FILTER API". + included verbatim on the about page. Default value: none. When no + about-filter is set, a markdown readme is instead rendered by the + bundled cgit.js (see enable-markdown). See also: "FILTER API". agefile:: Specifies a path, relative to each repository path, which can be used @@ -229,6 +230,14 @@ enable-tree-group-dirs:: in git's own order, with directories and files intermixed by name. Default value: "0". +enable-markdown:: + Flag which, when set to "1", renders a markdown readme on the about + page. cgit escapes the source and a small renderer bundled in cgit.js + formats it in the browser, so the page stays readable as plain text + when scripting is off. It applies only when no about-filter handles the + file, and only to names ending in .md, .markdown, .mkd or .mdown. + Default value: "1". + favicon:: Url used as link to a shortcut icon for cgit. It is suggested to use the value "/favicon.ico" since certain browsers will ignore other @@ -918,8 +927,8 @@ mimetype.svg=image/svg+xml # Source code is highlighted in the browser by the bundled cgit.js, so no # source-filter is needed here. Set one only to override that. -# Format markdown, restructuredtext, manpages, text files, and html files -# through the right converters +# Markdown about pages render client-side by default (see enable-markdown), +# so a filter is only needed for other formats such as manpages. about-filter=/var/www/cgit/filters/about-formatting.sh ## diff --git a/extensions/html-converters/md2html b/extensions/html-converters/md2html deleted file mode 100755 index 59f43a8..0000000 --- a/extensions/html-converters/md2html +++ /dev/null @@ -1,304 +0,0 @@ -#!/usr/bin/env python3 -import markdown -import sys -import io -from pygments.formatters import HtmlFormatter -from markdown.extensions.toc import TocExtension -sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8') -sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') -sys.stdout.write(''' -<style> -.markdown-body { - font-size: 14px; - line-height: 1.6; - overflow: hidden; -} -.markdown-body>*:first-child { - margin-top: 0 !important; -} -.markdown-body>*:last-child { - margin-bottom: 0 !important; -} -.markdown-body a.absent { - color: #c00; -} -.markdown-body a.anchor { - display: block; - padding-left: 30px; - margin-left: -30px; - cursor: pointer; - position: absolute; - top: 0; - left: 0; - bottom: 0; -} -.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { - margin: 20px 0 10px; - padding: 0; - font-weight: bold; - -webkit-font-smoothing: antialiased; - cursor: text; - position: relative; -} -.markdown-body h1 .mini-icon-link, .markdown-body h2 .mini-icon-link, .markdown-body h3 .mini-icon-link, .markdown-body h4 .mini-icon-link, .markdown-body h5 .mini-icon-link, .markdown-body h6 .mini-icon-link { - display: none; - color: #000; -} -.markdown-body h1:hover a.anchor, .markdown-body h2:hover a.anchor, .markdown-body h3:hover a.anchor, .markdown-body h4:hover a.anchor, .markdown-body h5:hover a.anchor, .markdown-body h6:hover a.anchor { - text-decoration: none; - line-height: 1; - padding-left: 0; - margin-left: -22px; - top: 15%; -} -.markdown-body h1:hover a.anchor .mini-icon-link, .markdown-body h2:hover a.anchor .mini-icon-link, .markdown-body h3:hover a.anchor .mini-icon-link, .markdown-body h4:hover a.anchor .mini-icon-link, .markdown-body h5:hover a.anchor .mini-icon-link, .markdown-body h6:hover a.anchor .mini-icon-link { - display: inline-block; -} -div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#cgit .markdown-body h3 a.toclink, div#cgit .markdown-body h4 a.toclink, div#cgit .markdown-body h5 a.toclink, div#cgit .markdown-body h6 a.toclink { - color: black; -} -.markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code { - font-size: inherit; -} -.markdown-body h1 { - font-size: 28px; - color: #000; -} -.markdown-body h2 { - font-size: 24px; - border-bottom: 1px solid #ccc; - color: #000; -} -.markdown-body h3 { - font-size: 18px; -} -.markdown-body h4 { - font-size: 16px; -} -.markdown-body h5 { - font-size: 14px; -} -.markdown-body h6 { - color: #777; - font-size: 14px; -} -.markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre { - margin: 15px 0; -} -.markdown-body hr { - border: 2px solid #ccc; -} -.markdown-body>h2:first-child, .markdown-body>h1:first-child, .markdown-body>h1:first-child+h2, .markdown-body>h3:first-child, .markdown-body>h4:first-child, .markdown-body>h5:first-child, .markdown-body>h6:first-child { - margin-top: 0; - padding-top: 0; -} -.markdown-body a:first-child h1, .markdown-body a:first-child h2, .markdown-body a:first-child h3, .markdown-body a:first-child h4, .markdown-body a:first-child h5, .markdown-body a:first-child h6 { - margin-top: 0; - padding-top: 0; -} -.markdown-body h1+p, .markdown-body h2+p, .markdown-body h3+p, .markdown-body h4+p, .markdown-body h5+p, .markdown-body h6+p { - margin-top: 0; -} -.markdown-body li p.first { - display: inline-block; -} -.markdown-body ul, .markdown-body ol { - padding-left: 30px; -} -.markdown-body ul.no-list, .markdown-body ol.no-list { - list-style-type: none; - padding: 0; -} -.markdown-body ul li>:first-child, .markdown-body ul li ul:first-of-type, .markdown-body ul li ol:first-of-type, .markdown-body ol li>:first-child, .markdown-body ol li ul:first-of-type, .markdown-body ol li ol:first-of-type { - margin-top: 0px; -} -.markdown-body ul li p:last-of-type, .markdown-body ol li p:last-of-type { - margin-bottom: 0; -} -.markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul { - margin-bottom: 0; -} -.markdown-body dl { - padding: 0; -} -.markdown-body dl dt { - font-size: 14px; - font-weight: bold; - font-style: italic; - padding: 0; - margin: 15px 0 5px; -} -.markdown-body dl dt:first-child { - padding: 0; -} -.markdown-body dl dt>:first-child { - margin-top: 0px; -} -.markdown-body dl dt>:last-child { - margin-bottom: 0px; -} -.markdown-body dl dd { - margin: 0 0 15px; - padding: 0 15px; -} -.markdown-body dl dd>:first-child { - margin-top: 0px; -} -.markdown-body dl dd>:last-child { - margin-bottom: 0px; -} -.markdown-body blockquote { - border-left: 4px solid #DDD; - padding: 0 15px; - color: #777; -} -.markdown-body blockquote>:first-child { - margin-top: 0px; -} -.markdown-body blockquote>:last-child { - margin-bottom: 0px; -} -.markdown-body table th { - font-weight: bold; -} -.markdown-body table th, .markdown-body table td { - border: 1px solid #ccc; - padding: 6px 13px; -} -.markdown-body table tr { - border-top: 1px solid #ccc; - background-color: #fff; -} -.markdown-body table tr:nth-child(2n) { - background-color: #f8f8f8; -} -.markdown-body img { - max-width: 100%; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -.markdown-body span.frame { - display: block; - overflow: hidden; -} -.markdown-body span.frame>span { - border: 1px solid #ddd; - display: block; - float: left; - overflow: hidden; - margin: 13px 0 0; - padding: 7px; - width: auto; -} -.markdown-body span.frame span img { - display: block; - float: left; -} -.markdown-body span.frame span span { - clear: both; - color: #333; - display: block; - padding: 5px 0 0; -} -.markdown-body span.align-center { - display: block; - overflow: hidden; - clear: both; -} -.markdown-body span.align-center>span { - display: block; - overflow: hidden; - margin: 13px auto 0; - text-align: center; -} -.markdown-body span.align-center span img { - margin: 0 auto; - text-align: center; -} -.markdown-body span.align-right { - display: block; - overflow: hidden; - clear: both; -} -.markdown-body span.align-right>span { - display: block; - overflow: hidden; - margin: 13px 0 0; - text-align: right; -} -.markdown-body span.align-right span img { - margin: 0; - text-align: right; -} -.markdown-body span.float-left { - display: block; - margin-right: 13px; - overflow: hidden; - float: left; -} -.markdown-body span.float-left span { - margin: 13px 0 0; -} -.markdown-body span.float-right { - display: block; - margin-left: 13px; - overflow: hidden; - float: right; -} -.markdown-body span.float-right>span { - display: block; - overflow: hidden; - margin: 13px auto 0; - text-align: right; -} -.markdown-body code, .markdown-body tt { - margin: 0 2px; - padding: 0px 5px; - border: 1px solid #eaeaea; - background-color: #f8f8f8; - border-radius: 3px; -} -.markdown-body code { - white-space: nowrap; -} -.markdown-body pre>code { - margin: 0; - padding: 0; - white-space: pre; - border: none; - background: transparent; -} -.markdown-body .highlight pre, .markdown-body pre { - background-color: #f8f8f8; - border: 1px solid #ccc; - font-size: 13px; - line-height: 19px; - overflow: auto; - padding: 6px 10px; - border-radius: 3px; -} -.markdown-body pre code, .markdown-body pre tt { - margin: 0; - padding: 0; - background-color: transparent; - border: none; -} -''') -sys.stdout.write(HtmlFormatter(style='pastie').get_style_defs('.highlight')) -sys.stdout.write(''' -</style> -''') -sys.stdout.write("<div class='markdown-body'>") -sys.stdout.flush() -# Note: you may want to run this through bleach for sanitization -markdown.markdownFromFile( - output_format="html5", - extensions=[ - "markdown.extensions.fenced_code", - "markdown.extensions.codehilite", - "markdown.extensions.tables", - "markdown.extensions.sane_lists", - TocExtension(anchorlink=True)], - extension_configs={ - "markdown.extensions.codehilite":{"css_class":"highlight"}}) -sys.stdout.write("</div>") diff --git a/extensions/html-converters/rst2html b/extensions/html-converters/rst2html deleted file mode 100755 index 02d90f8..0000000 --- a/extensions/html-converters/rst2html +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -exec rst2html.py --template <(echo -e "%(stylesheet)s\n%(body_pre_docinfo)s\n%(docinfo)s\n%(body)s") diff --git a/source/cgit.c b/source/cgit.c index 29b2131..8a7027c 100644 --- a/source/cgit.c +++ b/source/cgit.c @@ -201,6 +201,8 @@ static void config_cb(const char *name, const char *value) ctx.cfg.enable_tree_linenumbers = atoi(value); else if (!strcmp(name, "enable-tree-group-dirs")) ctx.cfg.enable_tree_group_dirs = atoi(value); + else if (!strcmp(name, "enable-markdown")) + ctx.cfg.enable_markdown = atoi(value); else if (!strcmp(name, "enable-git-config")) ctx.cfg.enable_git_config = atoi(value); else if (!strcmp(name, "enable-cache-list")) @@ -394,6 +396,7 @@ static void prepare_context(void) ctx.cfg.enable_http_clone = 1; ctx.cfg.enable_index_owner = 1; ctx.cfg.enable_tree_linenumbers = 1; + ctx.cfg.enable_markdown = 1; ctx.cfg.enable_git_config = 0; ctx.cfg.max_repo_count = 50; ctx.cfg.max_commit_count = 50; diff --git a/source/cgit.h b/source/cgit.h index ab7b284..3cd0317 100644 --- a/source/cgit.h +++ b/source/cgit.h @@ -243,6 +243,7 @@ struct cgit_config { int enable_html_serving; int enable_tree_linenumbers; int enable_tree_group_dirs; + int enable_markdown; int enable_git_config; int enable_cache_list; int local_time; diff --git a/source/ui-blob.c b/source/ui-blob.c index bc91656..7720a28 100644 --- a/source/ui-blob.c +++ b/source/ui-blob.c @@ -67,7 +67,7 @@ done: return walk_tree_ctx.found_path; } -int cgit_print_file(char *path, const char *head, int file_only) +int cgit_print_file(char *path, const char *head, int file_only, int html_escape) { struct object_id oid; enum object_type type; @@ -106,7 +106,10 @@ int cgit_print_file(char *path, const char *head, int file_only) if (!buf) return -1; buf[size] = '\0'; - html_raw(buf, size); + if (html_escape) + html_txt(buf); + else + html_raw(buf, size); free(buf); return 0; } diff --git a/source/ui-blob.h b/source/ui-blob.h index 16847b2..efbc94e 100644 --- a/source/ui-blob.h +++ b/source/ui-blob.h @@ -2,7 +2,7 @@ #define UI_BLOB_H extern int cgit_ref_path_exists(const char *path, const char *ref, int file_only); -extern int cgit_print_file(char *path, const char *head, int file_only); +extern int cgit_print_file(char *path, const char *head, int file_only, int html_escape); extern void cgit_print_blob(const char *hex, char *path, const char *head, int file_only); #endif /* UI_BLOB_H */ diff --git a/source/ui-summary.c b/source/ui-summary.c index 947812a..7e533e1 100644 --- a/source/ui-summary.c +++ b/source/ui-summary.c @@ -99,6 +99,17 @@ static char* append_readme_path(const char *filename, const char *ref, const cha return full_path; } +static int readme_is_markdown(const char *filename) +{ + const char *ext = strrchr(filename, '.'); + + if (!ext || !ext[1]) + return 0; + ext++; + return !strcasecmp(ext, "md") || !strcasecmp(ext, "markdown") || + !strcasecmp(ext, "mkd") || !strcasecmp(ext, "mdown"); +} + void cgit_print_repo_readme(const char *path) { char *filename, *ref, *mimetype; @@ -128,16 +139,35 @@ void cgit_print_repo_readme(const char *path) goto done; } - /* Print the calculated readme, either from the git repo or from the - * filesystem, while applying the about-filter. - */ html("<div id='summary'>"); - cgit_open_filter(ctx.repo->about_filter, filename); - if (ref) - cgit_print_file(filename, ref, 1); - else - html_include(filename); - cgit_close_filter(ctx.repo->about_filter); + if (!ctx.repo->about_filter && ctx.cfg.enable_markdown && + readme_is_markdown(filename)) { + /* No about-filter is set, so hand the markdown source to the + * built-in client-side renderer in cgit.js. The source is + * escaped here and rendered in the browser, and it degrades to + * readable plain text when scripting is off. + */ + html("<div class='markdown' data-markdown>"); + if (ref) { + cgit_print_file(filename, ref, 1, 1); + } else { + struct strbuf sb = STRBUF_INIT; + if (strbuf_read_file(&sb, filename, 0) >= 0) + html_txt(sb.buf); + strbuf_release(&sb); + } + html("</div>"); + } else { + /* Otherwise print the readme through the about-filter, or raw + * when none is configured. + */ + cgit_open_filter(ctx.repo->about_filter, filename); + if (ref) + cgit_print_file(filename, ref, 1, 0); + else + html_include(filename); + cgit_close_filter(ctx.repo->about_filter); + } html("</div>"); if (free_filename) |
