diff --git a/models/repo/repo.go b/models/repo/repo.go index 819356dfad342..2c864bc4d5e2f 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -164,6 +164,7 @@ type Repository struct { OriginalURL string `xorm:"VARCHAR(2048)"` DefaultBranch string DefaultWikiBranch string + DefaultWikiFormat string NumWatches int NumStars int diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 90c4f22ad2e3f..8c499774f4ff1 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -48,6 +48,7 @@ var ( DisableMigrations bool DisableStars bool `ini:"DISABLE_STARS"` DefaultBranch string + DefaultWikiFormat string AllowAdoptionOfUnadoptedRepositories bool AllowDeleteOfUnadoptedRepositories bool DisableDownloadSourceArchives bool @@ -172,6 +173,7 @@ var ( DisableMigrations: false, DisableStars: false, DefaultBranch: "main", + DefaultWikiFormat: "markdown", AllowForkWithoutMaximumLimit: true, StreamArchives: true, @@ -284,6 +286,7 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { Repository.GoGetCloneURLProtocol = sec.Key("GO_GET_CLONE_URL_PROTOCOL").MustString("https") Repository.MaxCreationLimit = sec.Key("MAX_CREATION_LIMIT").MustInt(-1) Repository.DefaultBranch = sec.Key("DEFAULT_BRANCH").MustString(Repository.DefaultBranch) + Repository.DefaultWikiFormat = sec.Key("WIKI_FORMAT").MustString(Repository.DefaultWikiFormat) RepoRootPath = sec.Key("ROOT").MustString(filepath.Join(AppDataPath, "gitea-repositories")) if !filepath.IsAbs(RepoRootPath) { RepoRootPath = filepath.Join(AppWorkPath, RepoRootPath) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b5b90b31a53cb..21bc15480d9f1 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2223,6 +2223,10 @@ settings.advanced_settings = Advanced Settings settings.wiki_desc = Enable Repository Wiki settings.use_internal_wiki = Use Built-In Wiki settings.default_wiki_branch_name = Default Wiki Branch Name +settings.default_wiki_format = Default Wiki Format +settings.default_wiki_format.markdown = Markdown +settings.default_wiki_format.org = Org-mode +settings.default_wiki_format.both = Both settings.failed_to_change_default_wiki_branch = Failed to change the default wiki branch. settings.use_external_wiki = Use External Wiki settings.external_wiki_url = External Wiki URL diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index 8e24ffa465c14..49263a4994a29 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -510,7 +510,8 @@ func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string { // 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.APIContext, commit *git.Commit, wikiName wiki_service.WebPath, isSidebarOrFooter bool) (string, string) { - gitFilename := wiki_service.WebPathToGitPath(wikiName) + repoDefaultWikiFormat := ctx.Repo.Repository.DefaultWikiFormat + gitFilename := wiki_service.WebPathToGitPath(wikiName, repoDefaultWikiFormat) entry, err := findEntryForFile(commit, gitFilename) if err != nil { if git.IsErrNotExist(err) { diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 60eb35f56d154..d2e7f7e7e0766 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -554,6 +554,18 @@ func handleSettingsPostAdvanced(ctx *context.Context) { } } + // Update DefaultWikiFormat if wiki is enabled + if form.EnableWiki && !form.EnableExternalWiki { + defaultWikiFormat := form.DefaultWikiFormat + if defaultWikiFormat == "" { + defaultWikiFormat = setting.Repository.DefaultWikiFormat + } + if repo.DefaultWikiFormat != defaultWikiFormat { + repo.DefaultWikiFormat = defaultWikiFormat + repoChanged = true + } + } + if form.EnableIssues && form.EnableExternalTracker && !unit_model.TypeExternalTracker.UnitGlobalDisabled() { if !validation.IsValidExternalURL(form.ExternalTrackerURL) { ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error")) diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 542ac9c7316a3..d847a322c15b8 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -146,21 +146,68 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte { // The last return value indicates whether the file should be returned as a raw file func wikiEntryByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) (*git.TreeEntry, string, bool, bool) { isRaw := false - gitFilename := wiki_service.WebPathToGitPath(wikiName) + repoDefaultWikiFormat := ctx.Repo.Repository.DefaultWikiFormat + gitFilename := wiki_service.WebPathToGitPath(wikiName, repoDefaultWikiFormat) entry, err := findEntryForFile(commit, gitFilename) if err != nil && !git.IsErrNotExist(err) { ctx.ServerError("findEntryForFile", err) return nil, "", false, false } if entry == nil { - // check if the file without ".md" suffix exists - gitFilename := strings.TrimSuffix(gitFilename, ".md") - entry, err = findEntryForFile(commit, gitFilename) - if err != nil && !git.IsErrNotExist(err) { - ctx.ServerError("findEntryForFile", err) - return nil, "", false, false + // Get default wiki format from repository, using global setting as fallback + defaultWikiFormat := ctx.Repo.Repository.DefaultWikiFormat + if defaultWikiFormat == "" { + defaultWikiFormat = setting.Repository.DefaultWikiFormat + } + + // Check if gitFilename already has .md or .org extension and extract base filename + baseFilename, hasMdSuffix := strings.CutSuffix(gitFilename, ".md") + var hasOrgSuffix bool + if !hasMdSuffix { + baseFilename, hasOrgSuffix = strings.CutSuffix(gitFilename, ".org") + if !hasOrgSuffix { + baseFilename = gitFilename + } + } + + // Try alternative formats based on DefaultWikiFormat setting + if defaultWikiFormat == "markdown" || defaultWikiFormat == "both" { + if !hasMdSuffix && !hasOrgSuffix { + entry, err = findEntryForFile(commit, baseFilename+".md") + if err != nil && !git.IsErrNotExist(err) { + ctx.ServerError("findEntryForFile", err) + return nil, "", false, false + } + if entry != nil { + gitFilename = baseFilename + ".md" + } + } + } + + if entry == nil && (defaultWikiFormat == "org" || defaultWikiFormat == "both") { + if !hasMdSuffix && !hasOrgSuffix { + entry, err = findEntryForFile(commit, baseFilename+".org") + if err != nil && !git.IsErrNotExist(err) { + ctx.ServerError("findEntryForFile", err) + return nil, "", false, false + } + if entry != nil { + gitFilename = baseFilename + ".org" + } + } + } + + if entry == nil { + entry, err = findEntryForFile(commit, baseFilename) + if err != nil && !git.IsErrNotExist(err) { + ctx.ServerError("findEntryForFile", err) + return nil, "", false, false + } + if entry != nil { + gitFilename = baseFilename + isRaw = true + } } - isRaw = true } if entry == nil { return nil, "", true, false @@ -193,12 +240,31 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { ctx.ServerError("ListEntries", err) return nil, nil } + + // Get default wiki format from repository, using global setting as fallback + defaultWikiFormat := ctx.Repo.Repository.DefaultWikiFormat + if defaultWikiFormat == "" { + defaultWikiFormat = setting.Repository.DefaultWikiFormat + } + pages := make([]PageMeta, 0, len(entries)) for _, entry := range entries { if !entry.IsRegular() { continue } - wikiName, err := wiki_service.GitPathToWebPath(entry.Name()) + entryName := entry.Name() + + // Filter by DefaultWikiFormat + hasMdSuffix := strings.HasSuffix(entryName, ".md") + hasOrgSuffix := strings.HasSuffix(entryName, ".org") + if defaultWikiFormat == "markdown" && hasOrgSuffix { + continue + } + if defaultWikiFormat == "org" && hasMdSuffix { + continue + } + + wikiName, err := wiki_service.GitPathToWebPath(entryName) if err != nil { if repo_model.IsErrWikiInvalidFileName(err) { continue @@ -212,7 +278,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { pages = append(pages, PageMeta{ Name: displayName, SubURL: wiki_service.WebPathToURLPath(wikiName), - GitEntryName: entry.Name(), + GitEntryName: entryName, }) } ctx.Data["Pages"] = pages @@ -252,7 +318,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { rctx := renderhelper.NewRenderContextRepoWiki(ctx, ctx.Repo.Repository) - renderFn := func(data []byte) (escaped *charset.EscapeStatus, output template.HTML, err error) { + renderFn := func(data []byte, filename string) (escaped *charset.EscapeStatus, output template.HTML, err error) { buf := &strings.Builder{} markupRd, markupWr := io.Pipe() defer markupWr.Close() @@ -265,13 +331,21 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { close(done) }() - err = markdown.Render(rctx, bytes.NewReader(data), markupWr) + // Detect markup type from filename and use the appropriate renderer + // (.md -> markdown, .org -> orgmode) + markupType := markup.DetectMarkupTypeByFileName(filename) + if markupType == "" { + // Default to markdown if detection fails + markupType = markdown.MarkupName + } + fileRctx := rctx.WithMarkupType(markupType).WithRelativePath(filename) + err = markup.Render(fileRctx, bytes.NewReader(data), markupWr) _ = markupWr.CloseWithError(err) <-done return escaped, output, err } - ctx.Data["EscapeStatus"], ctx.Data["WikiContentHTML"], err = renderFn(data) + ctx.Data["EscapeStatus"], ctx.Data["WikiContentHTML"], err = renderFn(data, pageFilename) if err != nil { ctx.ServerError("Render", err) return nil, nil @@ -286,26 +360,38 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { } if !isSideBar { - sidebarContent, _, _, _ := wikiContentsByName(ctx, commit, "_Sidebar") + sidebarContent, sidebarEntry, sidebarFilename, _ := wikiContentsByName(ctx, commit, "_Sidebar") if ctx.Written() { return nil, nil } - ctx.Data["WikiSidebarEscapeStatus"], ctx.Data["WikiSidebarHTML"], err = renderFn(sidebarContent) - if err != nil { - ctx.ServerError("Render", err) - return nil, nil + if sidebarEntry != nil && sidebarContent != nil { + // Use the git filename returned by wikiContentsByName, which correctly handles .org files + if sidebarFilename == "" { + sidebarFilename = sidebarEntry.Name() + } + ctx.Data["WikiSidebarEscapeStatus"], ctx.Data["WikiSidebarHTML"], err = renderFn(sidebarContent, sidebarFilename) + if err != nil { + ctx.ServerError("Render", err) + return nil, nil + } } } if !isFooter { - footerContent, _, _, _ := wikiContentsByName(ctx, commit, "_Footer") + footerContent, footerEntry, footerFilename, _ := wikiContentsByName(ctx, commit, "_Footer") if ctx.Written() { return nil, nil } - ctx.Data["WikiFooterEscapeStatus"], ctx.Data["WikiFooterHTML"], err = renderFn(footerContent) - if err != nil { - ctx.ServerError("Render", err) - return nil, nil + if footerEntry != nil && footerContent != nil { + // Use git filename here too to handle .org files correctly + if footerFilename == "" { + footerFilename = footerEntry.Name() + } + ctx.Data["WikiFooterEscapeStatus"], ctx.Data["WikiFooterHTML"], err = renderFn(footerContent, footerFilename) + if err != nil { + ctx.ServerError("Render", err) + return nil, nil + } } } @@ -492,7 +578,9 @@ func Wiki(ctx *context.Context) { } wikiPath := entry.Name() - if markup.DetectMarkupTypeByFileName(wikiPath) != markdown.MarkupName { + detectedMarkupType := markup.DetectMarkupTypeByFileName(wikiPath) + if detectedMarkupType == "" { + // Only show warning if no renderer was found for this file type ext := strings.ToUpper(filepath.Ext(wikiPath)) ctx.Data["FormatWarning"] = ext + " rendering is not supported at the moment. Rendered as Markdown." } @@ -575,12 +663,30 @@ func WikiPages(ctx *context.Context) { return } + // Get default wiki format from repository, using global setting as fallback + defaultWikiFormat := ctx.Repo.Repository.DefaultWikiFormat + if defaultWikiFormat == "" { + defaultWikiFormat = setting.Repository.DefaultWikiFormat + } + pages := make([]PageMeta, 0, len(entries)) for _, entry := range entries { if !entry.Entry.IsRegular() { continue } - wikiName, err := wiki_service.GitPathToWebPath(entry.Entry.Name()) + entryName := entry.Entry.Name() + + // Filter by DefaultWikiFormat + hasMdSuffix := strings.HasSuffix(entryName, ".md") + hasOrgSuffix := strings.HasSuffix(entryName, ".org") + if defaultWikiFormat == "markdown" && hasOrgSuffix { + continue + } + if defaultWikiFormat == "org" && hasMdSuffix { + continue + } + + wikiName, err := wiki_service.GitPathToWebPath(entryName) if err != nil { if repo_model.IsErrWikiInvalidFileName(err) { continue @@ -592,7 +698,7 @@ func WikiPages(ctx *context.Context) { pages = append(pages, PageMeta{ Name: displayName, SubURL: wiki_service.WebPathToURLPath(wikiName), - GitEntryName: entry.Entry.Name(), + GitEntryName: entryName, UpdatedUnix: timeutil.TimeStamp(entry.Commit.Author.When.Unix()), }) } @@ -614,7 +720,8 @@ func WikiRaw(ctx *context.Context) { } providedWebPath := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*")) - providedGitPath := wiki_service.WebPathToGitPath(providedWebPath) + repoDefaultWikiFormat := ctx.Repo.Repository.DefaultWikiFormat + providedGitPath := wiki_service.WebPathToGitPath(providedWebPath, repoDefaultWikiFormat) var entry *git.TreeEntry if commit != nil { // Try to find a file with that name @@ -625,12 +732,40 @@ func WikiRaw(ctx *context.Context) { } if entry == nil { - // Try to find a wiki page with that name - providedGitPath = strings.TrimSuffix(providedGitPath, ".md") - entry, err = findEntryForFile(commit, providedGitPath) - if err != nil && !git.IsErrNotExist(err) { - ctx.ServerError("findFile", err) - return + // Get default wiki format from repository, using global setting as fallback + defaultWikiFormat := ctx.Repo.Repository.DefaultWikiFormat + if defaultWikiFormat == "" { + defaultWikiFormat = setting.Repository.DefaultWikiFormat + } + + // Try to find a wiki page with that name based on DefaultWikiFormat + basePath, _ := strings.CutSuffix(providedGitPath, ".md") + if basePath == providedGitPath { + basePath, _ = strings.CutSuffix(providedGitPath, ".org") + } + + if defaultWikiFormat == "markdown" || defaultWikiFormat == "both" { + entry, err = findEntryForFile(commit, basePath+".md") + if err != nil && !git.IsErrNotExist(err) { + ctx.ServerError("findFile", err) + return + } + } + + if entry == nil && (defaultWikiFormat == "org" || defaultWikiFormat == "both") { + entry, err = findEntryForFile(commit, basePath+".org") + if err != nil && !git.IsErrNotExist(err) { + ctx.ServerError("findFile", err) + return + } + } + + if entry == nil { + entry, err = findEntryForFile(commit, basePath) + if err != nil && !git.IsErrNotExist(err) { + ctx.ServerError("findFile", err) + return + } } } } diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go index 409d7c9a05bca..5426ce749f1da 100644 --- a/routers/web/repo/wiki_test.go +++ b/routers/web/repo/wiki_test.go @@ -37,7 +37,7 @@ func wikiEntry(t *testing.T, repo *repo_model.Repository, wikiName wiki_service. entries, err := commit.ListEntries() assert.NoError(t, err) for _, entry := range entries { - if entry.Name() == wiki_service.WebPathToGitPath(wikiName) { + if entry.Name() == wiki_service.WebPathToGitPath(wikiName, repo.DefaultWikiFormat) { return entry } } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 6820521ba3de2..08a932aa3c0ef 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -110,6 +110,7 @@ type RepoSettingForm struct { // Advanced settings EnableCode bool + DefaultWikiFormat string EnableWiki bool EnableExternalWiki bool DefaultWikiBranch string diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index a9dc7269829f3..899c3d48f9e3a 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" asymkey_service "code.gitea.io/gitea/services/asymkey" repo_service "code.gitea.io/gitea/services/repository" ) @@ -54,12 +55,26 @@ func InitWiki(ctx context.Context, repo *repo_model.Repository) error { // prepareGitPath try to find a suitable file path with file name by the given raw wiki name. // return: existence, prepared file path with name, error -func prepareGitPath(gitRepo *git.Repository, defaultWikiBranch string, wikiPath WebPath) (bool, string, error) { - unescaped := string(wikiPath) + ".md" - gitPath := WebPathToGitPath(wikiPath) +func prepareGitPath(gitRepo *git.Repository, defaultWikiBranch string, wikiPath WebPath, defaultWikiFormat string) (bool, string, error) { + unescapedMd := string(wikiPath) + ".md" + unescapedOrg := string(wikiPath) + ".org" + gitPath := WebPathToGitPath(wikiPath, defaultWikiFormat) - // Look for both files - filesInIndex, err := gitRepo.LsTree(defaultWikiBranch, unescaped, gitPath) + // Build list of files to look for based on defaultWikiFormat + var filesToCheck []string + checkMarkdown := defaultWikiFormat == "markdown" || defaultWikiFormat == "both" + checkOrg := defaultWikiFormat == "org" || defaultWikiFormat == "both" + + if checkMarkdown { + filesToCheck = append(filesToCheck, unescapedMd) + } + if checkOrg { + filesToCheck = append(filesToCheck, unescapedOrg) + } + filesToCheck = append(filesToCheck, gitPath) + + // Look for files based on format setting + filesInIndex, err := gitRepo.LsTree(defaultWikiBranch, filesToCheck...) if err != nil { if strings.Contains(err.Error(), "Not a valid object name") { return false, gitPath, nil // branch doesn't exist @@ -71,9 +86,15 @@ func prepareGitPath(gitRepo *git.Repository, defaultWikiBranch string, wikiPath foundEscaped := false for _, filename := range filesInIndex { switch filename { - case unescaped: - // if we find the unescaped file return it - return true, unescaped, nil + // if we find unescaped file (.md or .org) return it + case unescapedMd: + if checkMarkdown { + return true, unescapedMd, nil + } + case unescapedOrg: + if checkOrg { + return true, unescapedOrg, nil + } case gitPath: foundEscaped = true } @@ -139,7 +160,11 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model } } - isWikiExist, newWikiPath, err := prepareGitPath(gitRepo, repo.DefaultWikiBranch, newWikiName) + defaultWikiFormat := repo.DefaultWikiFormat + if defaultWikiFormat == "" { + defaultWikiFormat = setting.Repository.DefaultWikiFormat + } + isWikiExist, newWikiPath, err := prepareGitPath(gitRepo, repo.DefaultWikiBranch, newWikiName, defaultWikiFormat) if err != nil { return err } @@ -155,7 +180,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model isOldWikiExist := true oldWikiPath := newWikiPath if oldWikiName != newWikiName { - isOldWikiExist, oldWikiPath, err = prepareGitPath(gitRepo, repo.DefaultWikiBranch, oldWikiName) + isOldWikiExist, oldWikiPath, err = prepareGitPath(gitRepo, repo.DefaultWikiBranch, oldWikiName, defaultWikiFormat) if err != nil { return err } @@ -291,7 +316,11 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model return fmt.Errorf("unable to read HEAD tree to index in: %s %w", basePath, err) } - found, wikiPath, err := prepareGitPath(gitRepo, repo.DefaultWikiBranch, wikiName) + defaultWikiFormat := repo.DefaultWikiFormat + if defaultWikiFormat == "" { + defaultWikiFormat = setting.Repository.DefaultWikiFormat + } + found, wikiPath, err := prepareGitPath(gitRepo, repo.DefaultWikiBranch, wikiName, defaultWikiFormat) if err != nil { return err } diff --git a/services/wiki/wiki_path.go b/services/wiki/wiki_path.go index fc032244b5cee..2b055865952e8 100644 --- a/services/wiki/wiki_path.go +++ b/services/wiki/wiki_path.go @@ -10,6 +10,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/convert" @@ -76,7 +77,7 @@ func unescapeSegment(s string) (string, error) { } func escapeSegToWeb(s string, hadDashMarker bool) string { - if hadDashMarker || strings.Contains(s, "-") || strings.HasSuffix(s, ".md") { + if hadDashMarker || strings.Contains(s, "-") || strings.HasSuffix(s, ".md") || strings.HasSuffix(s, ".org") { s = addDashMarker(s) } else { s = strings.ReplaceAll(s, " ", "-") @@ -93,12 +94,20 @@ func WebPathSegments(s WebPath) []string { return a } -func WebPathToGitPath(s WebPath) string { - if strings.HasSuffix(string(s), ".md") { - ret, _ := url.PathUnescape(string(s)) +func WebPathToGitPath(s WebPath, repoDefaultWikiFormat string) string { + str := string(s) + // Accept only .md or .org directly + if strings.HasSuffix(str, ".md") || strings.HasSuffix(str, ".org") { + ret, _ := url.PathUnescape(str) return util.PathJoinRelX(ret) } + // Prioritize repository's DefaultWikiFormat, fallback to global setting if empty + defaultWikiFormat := repoDefaultWikiFormat + if defaultWikiFormat == "" { + defaultWikiFormat = setting.Repository.DefaultWikiFormat + } + a := strings.Split(string(s), "/") for i := range a { shouldAddDashMarker := hasDashMarker(a[i]) @@ -107,14 +116,26 @@ 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" + basePath := strings.Join(a, "/") + + // Determine extension based on format setting + if defaultWikiFormat == "org" { + return basePath + ".org" + } + // For "both" or "markdown", default to .md + return basePath + ".md" } func GitPathToWebPath(s string) (wp WebPath, err error) { - if !strings.HasSuffix(s, ".md") { + // Trim .md or .org suffix if present + if before, ok := strings.CutSuffix(s, ".md"); ok { + s = before + } else if before, ok := strings.CutSuffix(s, ".org"); ok { + s = before + } else { + // If it doesn't end with .md or .org, it's not a valid wiki file return "", repo_model.ErrWikiInvalidFileName{FileName: s} } - s = strings.TrimSuffix(s, ".md") a := strings.Split(s, "/") for i := range a { shouldAddDashMarker := hasDashMarker(a[i]) @@ -132,6 +153,9 @@ func WebPathToUserTitle(s WebPath) (dir, display string) { if before, ok := strings.CutSuffix(display, ".md"); ok { display = before display, _ = url.PathUnescape(display) + } else if before, ok := strings.CutSuffix(display, ".org"); ok { + display = before + display, _ = url.PathUnescape(display) } display, _ = unescapeSegment(display) return dir, display diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go index e571e093b6fe3..23bc65f790f89 100644 --- a/services/wiki/wiki_test.go +++ b/services/wiki/wiki_test.go @@ -81,7 +81,7 @@ func TestWebPathToGitPath(t *testing.T) { {"2000-01-02-meeting.md", "2000-01-02+meeting"}, {"2000-01-02 meeting.-.md", "2000-01-02%20meeting.-"}, } { - assert.Equal(t, test.Expected, WebPathToGitPath(test.WikiName)) + assert.Equal(t, test.Expected, WebPathToGitPath(test.WikiName, "markdown")) } } @@ -129,11 +129,11 @@ func TestUserWebGitPathConsistency(t *testing.T) { continue } webPath := UserTitleToWebPath("", userTitle) - gitPath := WebPathToGitPath(webPath) + gitPath := WebPathToGitPath(webPath, "markdown") webPath1, _ := GitPathToWebPath(gitPath) _, userTitle1 := WebPathToUserTitle(webPath1) - gitPath1 := WebPathToGitPath(webPath1) + gitPath1 := WebPathToGitPath(webPath1, "markdown") assert.Equal(t, userTitle, userTitle1, "UserTitle for userTitle: %q", userTitle) assert.Equal(t, webPath, webPath1, "WebPath for userTitle: %q", userTitle) @@ -173,7 +173,7 @@ func TestRepository_AddWikiPage(t *testing.T) { defer gitRepo.Close() masterTree, err := gitRepo.GetTree(repo.DefaultWikiBranch) assert.NoError(t, err) - gitPath := WebPathToGitPath(webPath) + gitPath := WebPathToGitPath(webPath, repo.DefaultWikiFormat) entry, err := masterTree.GetTreeEntryByPath(gitPath) assert.NoError(t, err) assert.Equal(t, gitPath, entry.Name(), "%s not added correctly", userTitle) @@ -218,7 +218,7 @@ func TestRepository_EditWikiPage(t *testing.T) { assert.NoError(t, err) masterTree, err := gitRepo.GetTree(repo.DefaultWikiBranch) assert.NoError(t, err) - gitPath := WebPathToGitPath(webPath) + gitPath := WebPathToGitPath(webPath, repo.DefaultWikiFormat) entry, err := masterTree.GetTreeEntryByPath(gitPath) assert.NoError(t, err) assert.Equal(t, gitPath, entry.Name(), "%s not edited correctly", newWikiName) @@ -244,7 +244,7 @@ func TestRepository_DeleteWikiPage(t *testing.T) { defer gitRepo.Close() masterTree, err := gitRepo.GetTree(repo.DefaultWikiBranch) assert.NoError(t, err) - gitPath := WebPathToGitPath("Home") + gitPath := WebPathToGitPath("Home", repo.DefaultWikiFormat) _, err = masterTree.GetTreeEntryByPath(gitPath) assert.Error(t, err) } @@ -279,7 +279,7 @@ func TestPrepareWikiFileName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { webPath := UserTitleToWebPath("", tt.arg) - existence, newWikiPath, err := prepareGitPath(gitRepo, repo.DefaultWikiBranch, webPath) + existence, newWikiPath, err := prepareGitPath(gitRepo, repo.DefaultWikiBranch, webPath, "both") if (err != nil) != tt.wantErr { assert.NoError(t, err) return @@ -310,7 +310,7 @@ func TestPrepareWikiFileName_FirstPage(t *testing.T) { defer gitRepo.Close() - existence, newWikiPath, err := prepareGitPath(gitRepo, "master", "Home") + existence, newWikiPath, err := prepareGitPath(gitRepo, "master", "Home", "both") assert.False(t, existence) assert.NoError(t, err) assert.Equal(t, "Home.md", newWikiPath) diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index b4680431b8acf..eaec0fa7e792f 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -337,6 +337,34 @@ +
+ + {{$defaultWikiFormat := .Repository.DefaultWikiFormat}} + +