diff --git i/internal/bull/browse.go w/internal/bull/browse.go index ea260e3..e207bfa 100644 --- i/internal/bull/browse.go +++ w/internal/bull/browse.go @@ -4,9 +4,12 @@ import ( "bytes" "fmt" "net/http" + "net/url" + "path" "sort" "strings" "sync" + "time" ) func (b *bullServer) browse(w http.ResponseWriter, r *http.Request) error { @@ -31,8 +34,8 @@ func (b *bullServer) browse(w http.ResponseWriter, r *http.Request) error { readg.Wait() dir := r.FormValue("dir") + prefix := dir + "/" if dir != "" { - prefix := dir + "/" filtered := make([]page, 0, len(pages)) for _, page := range pages { if !strings.HasPrefix(page.FileName, prefix) { @@ -77,6 +80,72 @@ func (b *bullServer) browse(w http.ResponseWriter, r *http.Request) error { return fmt.Errorf("unknown ?sort parameter") } + // collapse directory entries + seen := make(map[string]time.Time) + for _, pg := range pages { + pgdir := path.Dir(pg.PageName) + if pgdir == dir { + // do not hide any entries in the directory we are listing + continue + } + latest, ok := seen[pgdir] + if ok && latest.After(pg.ModTime) { + continue + } + seen[pgdir] = pg.ModTime + } + skip := func(rel string) (string, time.Time, bool) { + // Skip this directory if we have seen it or any of its parents. + var chomped string + for { + idx := strings.IndexByte(rel, '/') + if idx == -1 { + return "", time.Time{}, false + } + head := rel[:idx] + component := chomped + head + if latest, ok := seen[component]; ok { + return component, latest, true + } + chomped += head + "/" + rel = rel[idx+1:] + } + } + lines := make([]string, 0, len(pages)) + for _, pg := range pages { + rel := strings.TrimPrefix(pg.PageName, prefix) + if strings.Contains(rel, "/") { + dir, latest, skip := skip(pg.PageName) + if skip && !latest.IsZero() { + // This is the first time we encounter a page within this + // directory, so produce an entry for the directory. + q := url.Values{ + "dir": []string{dir}, + "sort": []string{r.FormValue("sort")}, + "sortorder": []string{sortorder}, + } + target := (&url.URL{ + Path: bullURLPrefix + "browse", + RawQuery: q.Encode(), + }).String() + line := fmt.Sprintf("| [%s/](%s) | %s |\n", + dir, + target, + latest.Format("2006-01-02 15:04:05 Z07:00")) + lines = append(lines, line) + seen[dir] = time.Time{} // still present, but printed + } + if skip { + continue + } + } + + line := fmt.Sprintf("| [[%s]] | %s |\n", + pg.PageName, + pg.ModTime.Format("2006-01-02 15:04:05 Z07:00")) + lines = append(lines, line) + } + var buf bytes.Buffer if dir != "" { fmt.Fprintf(&buf, "# page browser: %s\n", dir) @@ -86,10 +155,8 @@ func (b *bullServer) browse(w http.ResponseWriter, r *http.Request) error { fmt.Fprintf(&buf, "| file name [↑](/_bull/browse?sort=pagename) [↓](/_bull/browse?sort=pagename&sortorder=desc) | last modified [↑](/_bull/browse?sort=modtime) [↓](/_bull/browse?sort=modtime&sortorder=desc) |\n") fmt.Fprintf(&buf, "|-----------|---------------|\n") // TODO: link to .. if dir != "" - for _, pg := range pages { - fmt.Fprintf(&buf, "| [[%s]] | %s |\n", - pg.PageName, - pg.ModTime.Format("2006-01-02 15:04:05 Z07:00")) + for _, line := range lines { + buf.Write([]byte(line)) } return b.renderBullMarkdown(w, r, "browse", buf) }