gitshark

Clone repository

git clone https://gitshark.de/git/workaround/Gitshark.git
git clone git@gitshark.de:workaround/Gitshark.git

← Commits

✨ (markdown): Render GFM pipe tables in Markdown

c27c3ccdfea804b930b186084fbfe1c4bf676d66 · Michael Hainz · 2026-07-09T13:43:36Z

Changes

5 files changed, +62 -2

MODIFY README.md +1 -1
diff --git a/README.md b/README.md
index a631a9a..aa5a1da 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
14 14 - push and private read authenticate with **personal access tokens** (HTTP Basic password)
15 15 - Clone/fetch/push over `ssh://git@<host>:2222/<owner>/<repo>.git`
16 16 - public-key authentication only; keys managed per user in the UI
17 -- Web UI: an auth-aware header nav (a "Log in" button for visitors; for signed-in users a top-level Following link plus an Account dropdown holding Profile, SSH keys, Access tokens, and Logout — a JS-free `<details>` menu), landing page with login CTA for visitors (`/`), repository list for authenticated users (`/`), public repository browse at `/explore`, file/tree browser with self-hosted syntax highlighting (extension-based language detection, falls back to plain text for unknown extensions and binary files; Markdown files render to HTML by default with a Rendered/Code toggle), a rendered README (commonmark-java, XSS-safe) shown below the file list on the repository overview page, commit log (paginated), branches, tags (own dedicated page, separate from branches), one-time handle selection (`/onboarding`), profile settings (`/settings/profile`). Every repository sub-page shows a persistent left sidebar with repo identity, a Clone button opening the clone dialog, a pin toggle, and section navigation (Code, Commits, Branches, Tags, Issues, Merge requests, plus Collaborators for the owner) with per-section counts and active-section highlighting, and the clone panel has copy-to-clipboard buttons for the HTTP and SSH `git clone` commands. Keyboard shortcuts are an optional, progressive enhancement (`?` opens a help overlay, `Escape` closes it, `g h` goes home) — every page works fully without JavaScript
17 +- Web UI: an auth-aware header nav (a "Log in" button for visitors; for signed-in users a top-level Following link plus an Account dropdown holding Profile, SSH keys, Access tokens, and Logout — a JS-free `<details>` menu), landing page with login CTA for visitors (`/`), repository list for authenticated users (`/`), public repository browse at `/explore`, file/tree browser with self-hosted syntax highlighting (extension-based language detection, falls back to plain text for unknown extensions and binary files; Markdown files render to HTML by default with a Rendered/Code toggle), a rendered README (commonmark-java with GFM tables, XSS-safe) shown below the file list on the repository overview page, commit log (paginated), branches, tags (own dedicated page, separate from branches), one-time handle selection (`/onboarding`), profile settings (`/settings/profile`). Every repository sub-page shows a persistent left sidebar with repo identity, a Clone button opening the clone dialog, a pin toggle, and section navigation (Code, Commits, Branches, Tags, Issues, Merge requests, plus Collaborators for the owner) with per-section counts and active-section highlighting, and the clone panel has copy-to-clipboard buttons for the HTTP and SSH `git clone` commands. Keyboard shortcuts are an optional, progressive enhancement (`?` opens a help overlay, `Escape` closes it, `g h` goes home) — every page works fully without JavaScript
18 18 - Per-repository issues: title, optional description (rendered as Markdown, XSS-safe), per-repo sequential number (`#1`, `#2`, …), and author; created and managed by the repo owner and collaborators, readable by anyone who can read the repo, via a dedicated "New issue" page
19 19 - Issues move through a fixed lifecycle (Planned → In development → Done); the repo navigation shows the open (Planned + In development) issue count, and Done issues collapse into an "Archive" section on the issues page
20 20 - Issues auto-close from pushed commit messages, GitHub-style (`close(s|d)`/`fix(es|ed)`/`resolve(s|d)` + `#<number>`, e.g. `fixes #12`), over both HTTP and SSH pushes
MODIFY pom.xml +5 -0
diff --git a/pom.xml b/pom.xml
index 0d0b5a3..38ee007 100644
--- a/pom.xml
+++ b/pom.xml
@@ -90,6 +90,11 @@
90 90 <version>${commonmark.version}</version>
91 91 </dependency>
92 92 <dependency>
93 + <groupId>org.commonmark</groupId>
94 + <artifactId>commonmark-ext-gfm-tables</artifactId>
95 + <version>${commonmark.version}</version>
96 + </dependency>
97 + <dependency>
93 98 <groupId>org.apache.sshd</groupId>
94 99 <artifactId>sshd-core</artifactId>
95 100 <version>${sshd.version}</version>
MODIFY src/main/java/de/workaround/web/Markdown.java +8 -1
diff --git a/src/main/java/de/workaround/web/Markdown.java b/src/main/java/de/workaround/web/Markdown.java
index 00347b8..97e4132 100644
--- a/src/main/java/de/workaround/web/Markdown.java
+++ b/src/main/java/de/workaround/web/Markdown.java
@@ -1,16 +1,23 @@
1 1 package de.workaround.web;
2 2
3 +import java.util.List;
4 +
5 +import org.commonmark.Extension;
6 +import org.commonmark.ext.gfm.tables.TablesExtension;
3 7 import org.commonmark.parser.Parser;
4 8 import org.commonmark.renderer.html.HtmlRenderer;
5 9
6 10 /** Shared Markdown-to-HTML rendering for server-rendered pages (README, markdown blobs, issue descriptions). */
7 11 final class Markdown
8 12 {
9 - private static final Parser PARSER = Parser.builder().build();
13 + private static final List<Extension> EXTENSIONS = List.of(TablesExtension.create());
14 +
15 + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build();
10 16
11 17 // escapeHtml + sanitizeUrls: markdown content is untrusted user input rendered into our page,
12 18 // so raw HTML blocks are escaped and javascript:/data: link targets are stripped.
13 19 private static final HtmlRenderer RENDERER = HtmlRenderer.builder()
20 + .extensions(EXTENSIONS)
14 21 .escapeHtml(true)
15 22 .sanitizeUrls(true)
16 23 .build();
MODIFY src/main/resources/META-INF/resources/shark.css +6 -0
diff --git a/src/main/resources/META-INF/resources/shark.css b/src/main/resources/META-INF/resources/shark.css
index 24c0ff2..b32d96f 100644
--- a/src/main/resources/META-INF/resources/shark.css
+++ b/src/main/resources/META-INF/resources/shark.css
@@ -1783,6 +1783,12 @@
1783 1783 margin: 0 0 .9em;
1784 1784 }
1785 1785
1786 +/* GFM tables: the global table style sizes to the container; inside prose only be as wide as the content */
1787 +.readme-body table {
1788 + margin: 0 0 .9em;
1789 + width: auto;
1790 +}
1791 +
1786 1792 .readme-body ul,
1787 1793 .readme-body ol {
1788 1794 padding-left: 1.6em;
ADD src/test/java/de/workaround/web/MarkdownTest.java +42 -0
diff --git a/src/test/java/de/workaround/web/MarkdownTest.java b/src/test/java/de/workaround/web/MarkdownTest.java
new file mode 100644
index 0000000..75388bb
--- /dev/null
+++ b/src/test/java/de/workaround/web/MarkdownTest.java
@@ -0,0 +1,42 @@
1 +package de.workaround.web;
2 +
3 +import org.junit.jupiter.api.Test;
4 +
5 +import static org.junit.jupiter.api.Assertions.assertFalse;
6 +import static org.junit.jupiter.api.Assertions.assertTrue;
7 +
8 +class MarkdownTest
9 +{
10 +
11 + @Test
12 + void rendersGfmPipeTablesAsHtmlTables()
13 + {
14 + String html = Markdown.render("""
15 + | Column A | Column B |
16 + |----------|----------|
17 + | foo | bar |
18 + """);
19 + assertTrue(html.contains("<table>"), () -> "expected a <table> element, got: " + html);
20 + assertTrue(html.contains("<th>Column A</th>"), () -> "expected a header cell, got: " + html);
21 + assertTrue(html.contains("<td>foo</td>"), () -> "expected a body cell, got: " + html);
22 + }
23 +
24 + @Test
25 + void tableCellContentStaysHtmlEscaped()
26 + {
27 + String html = Markdown.render("""
28 + | A |
29 + |---|
30 + | <script>alert('xss')</script> |
31 + """);
32 + assertFalse(html.contains("<script>"), () -> "raw HTML must stay escaped, got: " + html);
33 + }
34 +
35 + @Test
36 + void basicMarkdownStillRenders()
37 + {
38 + String html = Markdown.render("Some **bold** text");
39 + assertTrue(html.contains("<strong>bold</strong>"));
40 + }
41 +
42 +}

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog