diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 3fca7cebea525..d7435f6efdeab 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -11,6 +11,7 @@ import ( "io" "net/http" "net/url" + "path" "path/filepath" "strings" @@ -72,6 +73,7 @@ func MustEnableWiki(ctx *context.Context) { // PageMeta wiki page meta information type PageMeta struct { Name string + IsDir bool SubURL string GitEntryName string UpdatedUnix timeutil.TimeStamp @@ -178,7 +180,7 @@ func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_ return wikiContentsByEntry(ctx, entry), entry, gitFilename, noEntry } -func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { +func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry, string) { wikiRepo, commit, err := findWikiRepoCommit(ctx) if err != nil { if wikiRepo != nil { @@ -187,17 +189,43 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { if !git.IsErrNotExist(err) { ctx.ServerError("GetBranchCommit", err) } - return nil, nil + return nil, nil, "" + } + + reqPath := util.PathJoinRelX(ctx.PathParamRaw("*")) + p, err := url.PathUnescape(reqPath) + if err != nil { + ctx.ServerError("PathUnescape", err) + return nil, nil, "" + } + pTree, err := commit.GetTreeEntryByPath(wiki_service.WebDirPathToGitPath(wiki_service.WebPath(p))) + if err != nil && !git.IsErrNotExist(err) { + ctx.ServerError("SubTree", err) + return nil, nil, "" + } + if pTree != nil && pTree.IsDir() { + ctx.Redirect(ctx.Repo.RepoLink + fmt.Sprintf("/wiki/%s?action=_pages", p)) + return nil, nil, "" } + dirPath, pageName := path.Split(p) + pageName = strings.TrimSuffix(pageName, ".md") + treePath := wiki_service.WebDirPathToGitPath(wiki_service.WebPath(dirPath)) + tree, err := commit.SubTree(treePath) + if err != nil { + ctx.NotFoundOrServerError("SubTree", git.IsErrNotExist, err) + return nil, nil, "" + } + ctx.Data["dirPath"] = dirPath + // Get page list. - entries, err := commit.ListEntries() + entries, err := tree.ListEntries() if err != nil { if wikiRepo != nil { wikiRepo.Close() } ctx.ServerError("ListEntries", err) - return nil, nil + return nil, nil, "" } pages := make([]PageMeta, 0, len(entries)) for _, entry := range entries { @@ -213,27 +241,28 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { wikiRepo.Close() } ctx.ServerError("WikiFilenameToName", err) - return nil, nil + return nil, nil, "" } else if wikiName == "_Sidebar" || wikiName == "_Footer" { continue } _, displayName := wiki_service.WebPathToUserTitle(wikiName) pages = append(pages, PageMeta{ Name: displayName, - SubURL: wiki_service.WebPathToURLPath(wikiName), + SubURL: wiki_service.WebPathToURLPath(wiki_service.WebPath(dirPath) + wikiName), GitEntryName: entry.Name(), }) } ctx.Data["Pages"] = pages + ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(wiki_service.WebPath(p)) + ctx.Data["PageDir"] = wiki_service.WebPathToURLPath(wiki_service.WebPath(dirPath)) // get requested page name - pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*")) if len(pageName) == 0 { pageName = "Home" + p = "Home.md" } - _, displayName := wiki_service.WebPathToUserTitle(pageName) - ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName) + _, displayName := wiki_service.WebPathToUserTitle(wiki_service.WebPath(pageName)) ctx.Data["old_title"] = displayName ctx.Data["Title"] = displayName ctx.Data["title"] = displayName @@ -242,7 +271,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { isFooter := pageName == "_Footer" // lookup filename in wiki - get gitTree entry , real filename - entry, pageFilename, noEntry, isRaw := wikiEntryByName(ctx, commit, pageName) + entry, pageFilename, noEntry, isRaw := wikiEntryByName(ctx, commit, wiki_service.WebPath(p)) if noEntry { ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages") } @@ -253,7 +282,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { if wikiRepo != nil { wikiRepo.Close() } - return nil, nil + return nil, nil, "" } // get filecontent @@ -262,7 +291,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { if wikiRepo != nil { wikiRepo.Close() } - return nil, nil + return nil, nil, "" } var sidebarContent []byte @@ -272,7 +301,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { if wikiRepo != nil { wikiRepo.Close() } - return nil, nil + return nil, nil, "" } } else { sidebarContent = data @@ -285,7 +314,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { if wikiRepo != nil { wikiRepo.Close() } - return nil, nil + return nil, nil, "" } } else { footerContent = data @@ -318,7 +347,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { wikiRepo.Close() } ctx.ServerError("Render", err) - return nil, nil + return nil, nil, "" } if rctx.SidebarTocNode != nil { @@ -339,7 +368,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { wikiRepo.Close() } ctx.ServerError("Render", err) - return nil, nil + return nil, nil, "" } ctx.Data["sidebarPresent"] = sidebarContent != nil } else { @@ -354,7 +383,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { wikiRepo.Close() } ctx.ServerError("Render", err) - return nil, nil + return nil, nil, "" } ctx.Data["footerPresent"] = footerContent != nil } else { @@ -365,7 +394,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename) ctx.Data["CommitCount"] = commitsCount - return wikiRepo, entry + return wikiRepo, entry, pageFilename } func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { @@ -462,13 +491,14 @@ func renderEditPage(ctx *context.Context) { } // get requested pagename - pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*")) + pageName := wiki_service.WebPath(util.PathJoinRelX(ctx.PathParamRaw("*"))) if len(pageName) == 0 { pageName = "Home" } _, displayName := wiki_service.WebPathToUserTitle(pageName) ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName) + ctx.Data["PageDir"] = wiki_service.WebPathToURLPath(wiki_service.WebPath(path.Dir(string(pageName)))) ctx.Data["old_title"] = displayName ctx.Data["Title"] = displayName ctx.Data["title"] = displayName @@ -557,7 +587,7 @@ func Wiki(ctx *context.Context) { return } - wikiRepo, entry := renderViewPage(ctx) + wikiRepo, entry, wikiPath := renderViewPage(ctx) defer func() { if wikiRepo != nil { wikiRepo.Close() @@ -572,7 +602,6 @@ func Wiki(ctx *context.Context) { return } - wikiPath := entry.Name() if markup.DetectMarkupTypeByFileName(wikiPath) != markdown.MarkupName { ext := strings.ToUpper(filepath.Ext(wikiPath)) ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext) @@ -647,7 +676,8 @@ func WikiPages(ctx *context.Context) { return } - treePath := "" // To support list sub folders' pages in the future + dirPath := util.PathJoinRelX(ctx.PathParamRaw("*")) + treePath := wiki_service.WebDirPathToGitPath(wiki_service.WebPath(dirPath)) tree, err := commit.SubTree(treePath) if err != nil { ctx.ServerError("SubTree", err) @@ -669,26 +699,39 @@ func WikiPages(ctx *context.Context) { pages := make([]PageMeta, 0, len(entries)) for _, entry := range entries { - if !entry.Entry.IsRegular() { + if !entry.Entry.IsRegular() && !entry.Entry.IsDir() { continue } - wikiName, err := wiki_service.GitPathToWebPath(entry.Entry.Name()) - if err != nil { - if repo_model.IsErrWikiInvalidFileName(err) { - continue + var wikiName wiki_service.WebPath + if entry.Entry.IsDir() { + wikiName, err = wiki_service.GitDirPathToWebPath(entry.Entry.Name()) + if err != nil { + ctx.ServerError("GitDirPathToWebPath", err) + return + } + } else { + wikiName, err = wiki_service.GitPathToWebPath(entry.Entry.Name()) + if err != nil { + if repo_model.IsErrWikiInvalidFileName(err) { + continue + } + ctx.ServerError("GitPathToWebPath", err) + return } - ctx.ServerError("WikiFilenameToName", err) - return } _, displayName := wiki_service.WebPathToUserTitle(wikiName) + urlPath := path.Join(string(dirPath), string(wikiName)) pages = append(pages, PageMeta{ Name: displayName, - SubURL: wiki_service.WebPathToURLPath(wikiName), - GitEntryName: entry.Entry.Name(), + IsDir: entry.Entry.IsDir(), + SubURL: wiki_service.WebPathToURLPath(wiki_service.WebPath(urlPath)), + GitEntryName: path.Join(string(dirPath), entry.Entry.Name()), UpdatedUnix: timeutil.TimeStamp(entry.Commit.Author.When.Unix()), }) } ctx.Data["Pages"] = pages + ctx.Data["PageDir"] = ctx.PathParamRaw("*") + ctx.Data["ParentPageDir"] = path.Dir(ctx.PathParamRaw("*")) ctx.HTML(http.StatusOK, tplWikiPages) } @@ -772,7 +815,7 @@ func NewWikiPost(ctx *context.Context) { return } - wikiName := wiki_service.UserTitleToWebPath("", form.Title) + wikiName := wiki_service.UserTitleToWebPath(ctx.PathParam("*"), form.Title) if len(form.Message) == 0 { form.Message = ctx.Locale.TrString("repo.editor.add", form.Title) @@ -823,8 +866,9 @@ func EditWikiPost(ctx *context.Context) { return } - oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*")) - newWikiName := wiki_service.UserTitleToWebPath("", form.Title) + p := util.PathJoinRelX(ctx.PathParamRaw("*")) + oldWikiName := wiki_service.WebPath(p) + newWikiName := wiki_service.UserTitleToWebPath(path.Dir(p), form.Title) if len(form.Message) == 0 { form.Message = ctx.Locale.TrString("repo.editor.update", form.Title) @@ -842,7 +886,7 @@ func EditWikiPost(ctx *context.Context) { // DeleteWikiPagePost delete wiki page func DeleteWikiPagePost(ctx *context.Context) { - wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*")) + wikiName := wiki_service.WebPath(util.PathJoinRelX(ctx.PathParamRaw("*"))) if len(wikiName) == 0 { wikiName = "Home" } diff --git a/services/wiki/wiki_path.go b/services/wiki/wiki_path.go index 212a35ea2551e..73d51bbc2e27d 100644 --- a/services/wiki/wiki_path.go +++ b/services/wiki/wiki_path.go @@ -99,6 +99,10 @@ func WebPathToGitPath(s WebPath) string { return util.PathJoinRelX(ret) } + return WebDirPathToGitPath(s) + ".md" +} + +func WebDirPathToGitPath(s WebPath) string { a := strings.Split(string(s), "/") for i := range a { shouldAddDashMarker := hasDashMarker(a[i]) @@ -107,7 +111,7 @@ func WebPathToGitPath(s WebPath) string { a[i] = strings.ReplaceAll(a[i], "%20", " ") // space is safe to be kept in git path a[i] = strings.ReplaceAll(a[i], "+", " ") } - return strings.Join(a, "/") + ".md" + return strings.Join(a, "/") } func GitPathToWebPath(s string) (wp WebPath, err error) { @@ -126,6 +130,18 @@ func GitPathToWebPath(s string) (wp WebPath, err error) { return WebPath(strings.Join(a, "/")), nil } +func GitDirPathToWebPath(s string) (wp WebPath, err error) { + a := strings.Split(s, "/") + for i := range a { + shouldAddDashMarker := hasDashMarker(a[i]) + if a[i], err = unescapeSegment(a[i]); err != nil { + return "", err + } + a[i] = escapeSegToWeb(a[i], shouldAddDashMarker) + } + return WebPath(strings.Join(a, "/")), nil +} + func WebPathToUserTitle(s WebPath) (dir, display string) { dir = path.Dir(string(s)) display = path.Base(string(s)) diff --git a/templates/repo/wiki/new.tmpl b/templates/repo/wiki/new.tmpl index ea2913c0fdcae..8a113e571e282 100644 --- a/templates/repo/wiki/new.tmpl +++ b/templates/repo/wiki/new.tmpl @@ -6,7 +6,7 @@
{{ctx.Locale.Tr "repo.wiki.new_page"}} {{if .PageIsWikiEdit}} - {{ctx.Locale.Tr "repo.wiki.new_page_button"}} + {{ctx.Locale.Tr "repo.wiki.new_page_button"}} {{end}}
diff --git a/templates/repo/wiki/pages.tmpl b/templates/repo/wiki/pages.tmpl index 38d6d6f944642..cb7e1f2d85682 100644 --- a/templates/repo/wiki/pages.tmpl +++ b/templates/repo/wiki/pages.tmpl @@ -6,19 +6,28 @@ {{ctx.Locale.Tr "repo.wiki.pages"}} {{if and .CanWriteWiki (not .Repository.IsMirror)}} - {{ctx.Locale.Tr "repo.wiki.new_page_button"}} + {{ctx.Locale.Tr "repo.wiki.new_page_button"}} {{end}} {{if .IsRepositoryAdmin}}
{{ctx.Locale.Tr "repo.default_branch"}}: {{.Repository.DefaultWikiBranch}}
{{end}} + {{if .PageDir}} + + + + + {{end}} {{range .Pages}} {{$timeSince := DateUtils.TimeSince .UpdatedUnix}} diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl index 843a977e3e57c..4abee3642fb8a 100644 --- a/templates/repo/wiki/view.tmpl +++ b/templates/repo/wiki/view.tmpl @@ -49,7 +49,7 @@ {{end}} {{if and .CanWriteWiki (not .Repository.IsMirror)}} {{ctx.Locale.Tr "repo.wiki.edit_page_button"}} - {{ctx.Locale.Tr "repo.wiki.new_page_button"}} + {{ctx.Locale.Tr "repo.wiki.new_page_button"}}{{ctx.Locale.Tr "repo.wiki.delete_page_button"}} {{end}} diff --git a/web_src/css/repo/wiki.css b/web_src/css/repo/wiki.css index ca59dadb9c806..418216fcfea6f 100644 --- a/web_src/css/repo/wiki.css +++ b/web_src/css/repo/wiki.css @@ -11,6 +11,10 @@ display: inline-block; } +.repository.wiki .wiki-pages-list .svg.octicon-file-directory-fill { + color: var(--color-primary); +} + .repository.wiki .markup { overflow: visible; }
+ {{svg "octicon-file-directory-fill"}} + .. +
- {{svg "octicon-file"}} - {{.Name}} - {{svg "octicon-chevron-right"}} + {{if .IsDir}}{{svg "octicon-file-directory-fill"}}{{else}}{{svg "octicon-file"}}{{end}} + {{.Name}} + {{svg "octicon-chevron-right"}} {{ctx.Locale.Tr "repo.wiki.last_updated" $timeSince}}