render.gno
2.82 Kb · 113 lines
1package tablesort
2
3import (
4 "net/url"
5 "strings"
6
7 "gno.land/p/mason/md"
8 "gno.land/p/nt/ufmt"
9)
10
11// Table holds the headings and rows for rendering.
12// Each row must have the same number of cells as there are headings.
13type Table struct {
14 Headings []string // ["A", "B", "C"]
15 Rows [][]string // [["a1","b1","c1"], ["a2","b2","c2"], ...]
16}
17
18// Render generates a Markdown table from a Table struct with sortable columns based on URL params.
19// paramPrefix is an optional prefix for in the URL to identify the tablesort Renders (e.g. "members-").
20func Render(u *url.URL, table *Table, paramPrefix string) string {
21 direction := ""
22 currentHeading := ""
23 if h := u.Query().Get(paramPrefix + "sort-asc"); h != "" {
24 direction = "asc"
25 currentHeading = h
26 } else if h := u.Query().Get(paramPrefix + "sort-desc"); h != "" {
27 direction = "desc"
28 currentHeading = h
29 }
30
31 var sb strings.Builder
32
33 // Find the index of the column to sort
34 colIndex := -1
35 for i, h := range table.Headings {
36 if h == currentHeading {
37 colIndex = i
38 break
39 }
40 }
41
42 // Sort rows if necessary
43 if colIndex != -1 {
44 SortRows(table.Rows, colIndex, direction == "asc")
45 }
46
47 // Build header
48 sb.WriteString(buildHeader(u, table.Headings, currentHeading, direction, paramPrefix))
49 sb.WriteString("\n")
50
51 numCols := len(table.Headings)
52
53 // Build rows
54 for i, row := range table.Rows {
55 // Validate row length
56 if len(row) != numCols {
57 return "tablesort fails: row " + ufmt.Sprintf("%d", i+1) + " has " +
58 ufmt.Sprintf("%d", len(row)) + " cells, expected " +
59 ufmt.Sprintf("%d", numCols) + ", because there are " + ufmt.Sprintf("%d", numCols) + " columns.\n"
60 }
61
62 sb.WriteString("|")
63 for _, cell := range row {
64 sb.WriteString(" " + cell + " |")
65 }
66 sb.WriteString("\n")
67 }
68
69 return sb.String()
70}
71
72// buildHeader builds the Markdown header row with clickable links and arrows
73func buildHeader(u *url.URL, headings []string, currentHeading, direction string, paramPrefix string) string {
74 var sb strings.Builder
75 sb.WriteString("|")
76 for _, h := range headings {
77 arrow := ""
78 if h == currentHeading {
79 if direction == "asc" {
80 arrow = " ↑"
81 } else if direction == "desc" {
82 arrow = " ↓"
83 }
84 }
85
86 // Build URL for the header link with toggle logic
87 newURL := *u
88 q := newURL.Query()
89 if h == currentHeading {
90 // Toggle sort direction
91 if direction == "asc" {
92 q.Del(paramPrefix + "sort-asc")
93 q.Set(paramPrefix+"sort-desc", h)
94 } else {
95 q.Del(paramPrefix + "sort-desc")
96 q.Set(paramPrefix+"sort-asc", h)
97 }
98 } else {
99 // First click defaults to descending
100 q.Del(paramPrefix + "sort-asc")
101 q.Set(paramPrefix+"sort-desc", h)
102 }
103 newURL.RawQuery = q.Encode()
104 link := md.Link(h+arrow, newURL.String())
105 sb.WriteString(" " + link + " |")
106 }
107
108 sb.WriteString("\n|")
109 for range headings {
110 sb.WriteString(" --- |")
111 }
112 return sb.String()
113}