repos/stagit

fork of stagit, a static git page generator
Commits Files Refs README LICENSE
commit 59ed33a4ee177fcff9ce9dfe8082778a772b1547
parent 245296de1a76e46d65ee8ba10ce155c0c4aa56c8
Author: Martin Kloeckner <mjkloeckner@gmail.com>
Date:   Wed,  2 Sep 2026 20:50:52 -0300

Add markdown renderer for files other tha `README*`

Diffstat:
Mstagit.c | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
1 file changed, 90 insertions(+), 39 deletions(-)
diff --git a/stagit.c b/stagit.c
@@ -556,7 +556,7 @@ writeheader(FILE *fp, const char *title)
     fputs("</td></tr></table>", fp);
 
     fputs("<div id=\"repo-top-buttons\">\n", fp);
-    fprintf(fp, "<a <a href=\"https://git.kloeckner.com.ar\">Index</a>  ", relpath);
+    // fprintf(fp, "<a <a href=\"https://git.kloeckner.com.ar\">Index</a>  ", relpath);
     fprintf(fp, "<a href=\"%slog.html\">Commits</a>  ", relpath);
     fprintf(fp, "<a href=\"%sfiles.html\">Files</a>  ", relpath);
     fprintf(fp, "<a href=\"%srefs.html\">Refs</a>", relpath);
@@ -1024,6 +1024,57 @@ writeblobraw(const git_blob *blob, const char *fpath, const char *filename, git_
     fclose(fp);
 }
 
+void
+process_output_md(const char* text, unsigned int size, void* fp)
+{
+    fprintf((FILE *)fp, "%.*s", size, text);
+}
+
+int
+ismarkdownfile(const char *name)
+{
+    const char *exts[] = { ".md", ".markdown" };
+    const char *dot;
+    size_t i, len;
+
+    if (!name)
+        return 0;
+    dot = strrchr(name, '.');
+    if (!dot)
+        return 0;
+    for (i = 0; i < LEN(exts); i++) {
+        len = strlen(exts[i]);
+        if (!strncasecmp(dot, exts[i], len))
+            return 1;
+    }
+    return 0;
+}
+
+void
+writeblobmd(FILE *fp, const git_blob *blob, const char *rawprefix)
+{
+    const char *s = git_blob_rawcontent(blob);
+    git_off_t len = git_blob_rawsize(blob);
+
+    fputs("<div id=\"md-content\">", fp);
+    if (md_html(s, len, process_output_md, fp,
+            MD_FLAG_TABLES |
+            MD_FLAG_TASKLISTS |
+            MD_FLAG_PERMISSIVEEMAILAUTOLINKS |
+            MD_FLAG_PERMISSIVEURLAUTOLINKS, 0))
+        err(1, "error parsing markdown");
+    fputs("</div>\n", fp);
+    fputs("<script>"
+            "document.querySelectorAll('#md-content img').forEach(img => {"
+            "const src = img.getAttribute('src');"
+            "if (src && !src.startsWith('/') && !src.startsWith('http'))"
+            "img.src = '", fp);
+    fputs(rawprefix, fp);
+    fputs("raw/' + src;"
+            "});"
+            "</script>\n", fp);
+}
+
 size_t
 writeblob(git_object *obj, const char *fpath, const char *rpath, const char *filename, const char *path, size_t filesize)
 {
@@ -1087,9 +1138,9 @@ writeblob(git_object *obj, const char *fpath, const char *rpath, const char *fil
               "</script>", fp);
 
     fputs("<div id=\"open-file-header\"><div id=\"open-file-name\"><a id=\"file-parent-path\" href=\"", fp);
+    // xmlencode(fp, relpath, strlen(relpath));
     // printf("relpath is '%s'\n", relpath);
-    // fputs(relpath, fp);
-    xmlencode(fp, relpath, strlen(relpath));
+    fputs(relpath, fp);
     fputs("file/", fp);
     xmlencode(fp, path, strlen(path));
     fputs(".html\">", fp);
@@ -1102,6 +1153,12 @@ writeblob(git_object *obj, const char *fpath, const char *rpath, const char *fil
         "id=\"line-checkbox\" onchange=\"toggleLineNumbers()\" checked>", fp);
     fputs("<label for=\"line-checkbox\">Line numbers</label></div>", fp);
 
+    if (ismarkdownfile(filename)) {
+        fputs("<div id=\"md-checkbox-div\"><input type=\"checkbox\""
+            "id=\"md-checkbox\" onchange=\"toggleMdView()\" checked>", fp);
+        fputs("<label for=\"md-checkbox\">Render</label></div>", fp);
+    }
+
     // fputs("<p id=\"openfile-name\"> ", fp);
     // xmlencode(fp, filename, strlen(filename));
     // fprintf(fp, " (%zuB)", filesize);
@@ -1111,7 +1168,34 @@ writeblob(git_object *obj, const char *fpath, const char *rpath, const char *fil
 
     if (git_blob_is_binary((git_blob *)obj))
         fputs("<p id=\"binary-file\">Binary file.</p>\n", fp);
-    else
+    else if (ismarkdownfile(filename)) {
+        fputs("<div id=\"md-view\">", fp);
+        writeblobmd(fp, (git_blob *)obj, relpath);
+        fputs("</div>\n", fp);
+        fputs("<div id=\"source-view\">", fp);
+        lc = writeblobhtml(fp, (git_blob *)obj);
+        fputs("</div>\n", fp);
+        fputs("<script>"
+              "function toggleMdView() {"
+              "  var mdCheckbox = document.getElementById('md-checkbox');"
+              "  var mdView = document.getElementById('md-view');"
+              "  var sourceView = document.getElementById('source-view');"
+              "  if (mdView && sourceView) {"
+              "    mdView.style.display = mdCheckbox.checked ? 'block' : 'none';"
+              "    sourceView.style.display = mdCheckbox.checked ? 'none' : 'block';"
+              "  }"
+              "  localStorage.setItem('md-checkbox', mdCheckbox.checked);"
+              "}"
+              "document.addEventListener('DOMContentLoaded', function() {"
+              "  var mdCheckbox = document.getElementById('md-checkbox');"
+              "  var savedState = localStorage.getItem('md-checkbox');"
+              "  if (savedState !== null) {"
+              "    mdCheckbox.checked = savedState === 'true';"
+              "  }"
+              "  toggleMdView();"
+              "});"
+              "</script>", fp);
+    } else
         lc = writeblobhtml(fp, (git_blob *)obj);
 
     writefooter(fp);
@@ -1500,12 +1584,6 @@ usage(char *argv0)
     exit(1);
 }
 
-void
-process_output_md(const char* text, unsigned int size, void* fp)
-{
-    fprintf((FILE *)fp, "%.*s", size, text);
-}
-
 int
 main(int argc, char *argv[])
 {
@@ -1655,22 +1733,7 @@ main(int argc, char *argv[])
         git_revparse_single(&obj, repo, readmefiles[r]);
         const char *s = git_blob_rawcontent((git_blob *)obj);
         if (r == 1) {
-            git_off_t len = git_blob_rawsize((git_blob *)obj);
-            fputs("<div id=\"readme\">", fp);
-            if (md_html(s, len, process_output_md, fp,
-                        MD_FLAG_TABLES |
-                        MD_FLAG_TASKLISTS |
-                        MD_FLAG_PERMISSIVEEMAILAUTOLINKS |
-                        MD_FLAG_PERMISSIVEURLAUTOLINKS, 0))
-                err(1, "error parsing markdown");
-            fputs("</div>\n", fp);
-            fputs("<script>"
-                    "document.querySelectorAll('#readme img').forEach(img => {"
-                    "const src = img.getAttribute('src');"
-                    "if (src && !src.startsWith('/') && !src.startsWith('http'))"
-                    "img.src = 'raw/' + src;"
-                    "});"
-                    "</script>\n", fp);
+            writeblobmd(fp, (git_blob *)obj, "");
         } else {
             fputs("<pre id=\"readme\">", fp);
             xmlencode(fp, s, strlen(s));
@@ -1749,19 +1812,7 @@ main(int argc, char *argv[])
         git_revparse_single(&obj, repo, readmefiles[r]);
         const char *s = git_blob_rawcontent((git_blob *)obj);
         if (r == 1) {
-            git_off_t len = git_blob_rawsize((git_blob *)obj);
-            fputs("<div id=\"readme\">", fp);
-            if (md_html(s, len, process_output_md, fp, MD_FLAG_TABLES | MD_FLAG_TASKLISTS |
-                MD_FLAG_PERMISSIVEEMAILAUTOLINKS | MD_FLAG_PERMISSIVEURLAUTOLINKS, 0))
-                err(1, "error parsing markdown");
-            fputs("</div>\n", fp);
-            fputs("<script>"
-                    "document.querySelectorAll('#readme img').forEach(img => {"
-                    "const src = img.getAttribute('src');"
-                    "if (src && !src.startsWith('/') && !src.startsWith('http'))"
-                    "img.src = 'raw/' + src;"
-                    "});"
-                    "</script>\n", fp);
+            writeblobmd(fp, (git_blob *)obj, "");
         } else {
             fputs("<pre id=\"readme\">", fp);
             xmlencode(fp, s, strlen(s));