Make wiki title supports dashes and improve wiki name related features (#24143)

Close #7570


1. Clearly define the wiki path behaviors, see
`services/wiki/wiki_path.go` and tests
2. Keep compatibility with old contents
3. Allow to use dashes in titles, eg: "2000-01-02 Meeting record"
4. Add a "Pages" link in the dropdown, otherwise users can't go to the
Pages page easily.
5. Add a "View original git file" link in the Pages list, even if some
file names are broken, users still have a chance to edit or remove it,
without cloning the wiki repo to local.
6. Fix 500 error when the name contains prefix spaces.


This PR also introduces the ability to support sub-directories, but it
can't be done at the moment due to there are a lot of legacy wiki data,
which use "%2F" in file names.



![image](https://user-images.githubusercontent.com/2114189/232239004-3359d7b9-7bf3-4ff3-8446-bfb0e79645dd.png)


![image](https://user-images.githubusercontent.com/2114189/232239020-74b92c72-bf73-4377-a319-1c85609f82b1.png)

Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
wxiaoguang 2023-04-20 01:50:10 +08:00 committed by GitHub
parent 738f2af527
commit b39a5bbbd6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 400 additions and 256 deletions

View file

@ -68,9 +68,10 @@ func MustEnableWiki(ctx *context.Context) {
// PageMeta wiki page meta information
type PageMeta struct {
Name string
SubURL string
UpdatedUnix timeutil.TimeStamp
Name string
SubURL string
GitEntryName string
UpdatedUnix timeutil.TimeStamp
}
// findEntryForFile finds the tree entry for a target filepath.
@ -83,7 +84,7 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
return entry, nil
}
// Then the unescaped, shortest alternative
// Then the unescaped, the shortest alternative
var unescapedTarget string
if unescapedTarget, err = url.QueryUnescape(target); err != nil {
return nil, err
@ -124,16 +125,16 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
// wikiContentsByName returns the contents of a wiki page, along with a boolean
// indicating whether the page exists. Writes to ctx if an error occurs.
func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, *git.TreeEntry, string, bool) {
pageFilename := wiki_service.NameToFilename(wikiName)
entry, err := findEntryForFile(commit, pageFilename)
func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) ([]byte, *git.TreeEntry, string, bool) {
gitFilename := wiki_service.WebPathToGitPath(wikiName)
entry, err := findEntryForFile(commit, gitFilename)
if err != nil && !git.IsErrNotExist(err) {
ctx.ServerError("findEntryForFile", err)
return nil, nil, "", false
} else if entry == nil {
return nil, nil, "", true
}
return wikiContentsByEntry(ctx, entry), entry, pageFilename, false
return wikiContentsByEntry(ctx, entry), entry, gitFilename, false
}
func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
@ -162,7 +163,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
if !entry.IsRegular() {
continue
}
wikiName, err := wiki_service.FilenameToName(entry.Name())
wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
if err != nil {
if repo_model.IsErrWikiInvalidFileName(err) {
continue
@ -175,22 +176,26 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
} else if wikiName == "_Sidebar" || wikiName == "_Footer" {
continue
}
_, displayName := wiki_service.WebPathToUserTitle(wikiName)
pages = append(pages, PageMeta{
Name: wikiName,
SubURL: wiki_service.NameToSubURL(wikiName),
Name: displayName,
SubURL: wiki_service.WebPathToURLPath(wikiName),
GitEntryName: entry.Name(),
})
}
ctx.Data["Pages"] = pages
// get requested pagename
pageName := wiki_service.NormalizeWikiName(ctx.Params("*"))
// get requested page name
pageName := wiki_service.WebPathFromRequest(ctx.Params("*"))
if len(pageName) == 0 {
pageName = "Home"
}
ctx.Data["PageURL"] = wiki_service.NameToSubURL(pageName)
ctx.Data["old_title"] = pageName
ctx.Data["Title"] = pageName
ctx.Data["title"] = pageName
_, displayName := wiki_service.WebPathToUserTitle(pageName)
ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
ctx.Data["old_title"] = displayName
ctx.Data["Title"] = displayName
ctx.Data["title"] = displayName
isSideBar := pageName == "_Sidebar"
isFooter := pageName == "_Footer"
@ -328,14 +333,17 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
}
// get requested pagename
pageName := wiki_service.NormalizeWikiName(ctx.Params("*"))
pageName := wiki_service.WebPathFromRequest(ctx.Params("*"))
if len(pageName) == 0 {
pageName = "Home"
}
ctx.Data["PageURL"] = wiki_service.NameToSubURL(pageName)
ctx.Data["old_title"] = pageName
ctx.Data["Title"] = pageName
ctx.Data["title"] = pageName
_, displayName := wiki_service.WebPathToUserTitle(pageName)
ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
ctx.Data["old_title"] = displayName
ctx.Data["Title"] = displayName
ctx.Data["title"] = displayName
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
@ -403,14 +411,16 @@ func renderEditPage(ctx *context.Context) {
}()
// get requested pagename
pageName := wiki_service.NormalizeWikiName(ctx.Params("*"))
pageName := wiki_service.WebPathFromRequest(ctx.Params("*"))
if len(pageName) == 0 {
pageName = "Home"
}
ctx.Data["PageURL"] = wiki_service.NameToSubURL(pageName)
ctx.Data["old_title"] = pageName
ctx.Data["Title"] = pageName
ctx.Data["title"] = pageName
_, displayName := wiki_service.WebPathToUserTitle(pageName)
ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
ctx.Data["old_title"] = displayName
ctx.Data["Title"] = displayName
ctx.Data["title"] = displayName
// lookup filename in wiki - get filecontent, gitTree entry , real filename
data, entry, _, noEntry := wikiContentsByName(ctx, commit, pageName)
@ -594,7 +604,7 @@ func WikiPages(ctx *context.Context) {
ctx.ServerError("GetCommit", err)
return
}
wikiName, err := wiki_service.FilenameToName(entry.Name())
wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
if err != nil {
if repo_model.IsErrWikiInvalidFileName(err) {
continue
@ -602,10 +612,12 @@ func WikiPages(ctx *context.Context) {
ctx.ServerError("WikiFilenameToName", err)
return
}
_, displayName := wiki_service.WebPathToUserTitle(wikiName)
pages = append(pages, PageMeta{
Name: wikiName,
SubURL: wiki_service.NameToSubURL(wikiName),
UpdatedUnix: timeutil.TimeStamp(c.Author.When.Unix()),
Name: displayName,
SubURL: wiki_service.WebPathToURLPath(wikiName),
GitEntryName: entry.Name(),
UpdatedUnix: timeutil.TimeStamp(c.Author.When.Unix()),
})
}
ctx.Data["Pages"] = pages
@ -631,12 +643,12 @@ func WikiRaw(ctx *context.Context) {
return
}
providedPath := ctx.Params("*")
providedWebPath := wiki_service.WebPathFromRequest(ctx.Params("*"))
providedGitPath := wiki_service.WebPathToGitPath(providedWebPath)
var entry *git.TreeEntry
if commit != nil {
// Try to find a file with that name
entry, err = findEntryForFile(commit, providedPath)
entry, err = findEntryForFile(commit, providedGitPath)
if err != nil && !git.IsErrNotExist(err) {
ctx.ServerError("findFile", err)
return
@ -644,10 +656,8 @@ func WikiRaw(ctx *context.Context) {
if entry == nil {
// Try to find a wiki page with that name
providedPath = strings.TrimSuffix(providedPath, ".md")
wikiPath := wiki_service.NameToFilename(providedPath)
entry, err = findEntryForFile(commit, wikiPath)
providedGitPath = strings.TrimSuffix(providedGitPath, ".md")
entry, err = findEntryForFile(commit, providedGitPath)
if err != nil && !git.IsErrNotExist(err) {
ctx.ServerError("findFile", err)
return
@ -694,7 +704,7 @@ func NewWikiPost(ctx *context.Context) {
return
}
wikiName := wiki_service.NormalizeWikiName(form.Title)
wikiName := wiki_service.UserTitleToWebPath("", form.Title)
if len(form.Message) == 0 {
form.Message = ctx.Tr("repo.editor.add", form.Title)
@ -713,9 +723,9 @@ func NewWikiPost(ctx *context.Context) {
return
}
notification.NotifyNewWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName, form.Message)
notification.NotifyNewWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName), form.Message)
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.NameToSubURL(wikiName))
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(wikiName))
}
// EditWiki render wiki modify page
@ -745,8 +755,8 @@ func EditWikiPost(ctx *context.Context) {
return
}
oldWikiName := wiki_service.NormalizeWikiName(ctx.Params("*"))
newWikiName := wiki_service.NormalizeWikiName(form.Title)
oldWikiName := wiki_service.WebPathFromRequest(ctx.Params("*"))
newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
if len(form.Message) == 0 {
form.Message = ctx.Tr("repo.editor.update", form.Title)
@ -757,14 +767,14 @@ func EditWikiPost(ctx *context.Context) {
return
}
notification.NotifyEditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, newWikiName, form.Message)
notification.NotifyEditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(newWikiName), form.Message)
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.NameToSubURL(newWikiName))
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(newWikiName))
}
// DeleteWikiPagePost delete wiki page
func DeleteWikiPagePost(ctx *context.Context) {
wikiName := wiki_service.NormalizeWikiName(ctx.Params("*"))
wikiName := wiki_service.WebPathFromRequest(ctx.Params("*"))
if len(wikiName) == 0 {
wikiName = "Home"
}
@ -774,7 +784,7 @@ func DeleteWikiPagePost(ctx *context.Context) {
return
}
notification.NotifyDeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName)
notification.NotifyDeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName))
ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": ctx.Repo.RepoLink + "/wiki/",

View file

@ -6,6 +6,7 @@ package repo
import (
"io"
"net/http"
"net/url"
"testing"
repo_model "code.gitea.io/gitea/models/repo"
@ -24,7 +25,7 @@ const (
message = "Wiki commit message for unit tests"
)
func wikiEntry(t *testing.T, repo *repo_model.Repository, wikiName string) *git.TreeEntry {
func wikiEntry(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) *git.TreeEntry {
wikiRepo, err := git.OpenRepository(git.DefaultContext, repo.WikiPath())
assert.NoError(t, err)
defer wikiRepo.Close()
@ -33,14 +34,14 @@ func wikiEntry(t *testing.T, repo *repo_model.Repository, wikiName string) *git.
entries, err := commit.ListEntries()
assert.NoError(t, err)
for _, entry := range entries {
if entry.Name() == wiki_service.NameToFilename(wikiName) {
if entry.Name() == wiki_service.WebPathToGitPath(wikiName) {
return entry
}
}
return nil
}
func wikiContent(t *testing.T, repo *repo_model.Repository, wikiName string) string {
func wikiContent(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) string {
entry := wikiEntry(t, repo, wikiName)
if !assert.NotNil(t, entry) {
return ""
@ -53,11 +54,11 @@ func wikiContent(t *testing.T, repo *repo_model.Repository, wikiName string) str
return string(bytes)
}
func assertWikiExists(t *testing.T, repo *repo_model.Repository, wikiName string) {
func assertWikiExists(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) {
assert.NotNil(t, wikiEntry(t, repo, wikiName))
}
func assertWikiNotExists(t *testing.T, repo *repo_model.Repository, wikiName string) {
func assertWikiNotExists(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) {
assert.Nil(t, wikiEntry(t, repo, wikiName))
}
@ -124,8 +125,8 @@ func TestNewWikiPost(t *testing.T) {
})
NewWikiPost(ctx)
assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status())
assertWikiExists(t, ctx.Repo.Repository, title)
assert.Equal(t, wikiContent(t, ctx.Repo.Repository, title), content)
assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))
assert.Equal(t, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)), content)
}
}
@ -176,8 +177,8 @@ func TestEditWikiPost(t *testing.T) {
})
EditWikiPost(ctx)
assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status())
assertWikiExists(t, ctx.Repo.Repository, title)
assert.Equal(t, wikiContent(t, ctx.Repo.Repository, title), content)
assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))
assert.Equal(t, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)), content)
if title != "Home" {
assertWikiNotExists(t, ctx.Repo.Repository, "Home")
}
@ -201,17 +202,21 @@ func TestWikiRaw(t *testing.T) {
"images/jpeg.jpg": "image/jpeg",
"Page With Spaced Name": "text/plain; charset=utf-8",
"Page-With-Spaced-Name": "text/plain; charset=utf-8",
"Page With Spaced Name.md": "text/plain; charset=utf-8",
"Page With Spaced Name.md": "", // there is no "Page With Spaced Name.md" in repo
"Page-With-Spaced-Name.md": "text/plain; charset=utf-8",
} {
unittest.PrepareTestEnv(t)
ctx := test.MockContext(t, "user2/repo1/wiki/raw/"+filepath)
ctx := test.MockContext(t, "user2/repo1/wiki/raw/"+url.PathEscape(filepath))
ctx.SetParams("*", filepath)
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1)
WikiRaw(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
assert.EqualValues(t, filetype, ctx.Resp.Header().Get("Content-Type"))
if filetype == "" {
assert.EqualValues(t, http.StatusNotFound, ctx.Resp.Status(), "filepath: %s", filepath)
} else {
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status(), "filepath: %s", filepath)
assert.EqualValues(t, filetype, ctx.Resp.Header().Get("Content-Type"), "filepath: %s", filepath)
}
}
}