From a777f8ae75d96da4222f58a3806ba72cb7a78b91 Mon Sep 17 00:00:00 2001 From: zeripath Date: Sun, 24 May 2020 11:45:56 +0100 Subject: [PATCH] Allow different HardBreaks settings for documents and comments (#11515) (#11599) GH has different HardBreaks behaviour for markdown comments and documents. Comments have hard breaks and documents have soft breaks - therefore Gitea's rendering will always be different from GH's if we only provide one setting. Here we split the setting in to two - one for documents and one for comments and other things. Signed-off-by: Andrew Thornton art27@cantab.net Changes to index.js as per @silverwind Co-authored-by: silverwind Changes to docs as per @guillep2k Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> --- custom/conf/app.ini.sample | 5 +++- .../doc/advanced/config-cheat-sheet.en-us.md | 5 +++- models/repo.go | 23 +++++++++++++++---- modules/markup/markdown/goldmark.go | 10 ++++++++ modules/markup/markdown/markdown.go | 20 +++++++++------- modules/setting/setting.go | 12 ++++++---- routers/api/v1/misc/markdown.go | 14 +++++++++-- routers/api/v1/misc/markdown_test.go | 2 +- routers/repo/view.go | 6 ++--- routers/repo/wiki.go | 2 +- templates/repo/editor/edit.tmpl | 2 +- web_src/js/index.js | 5 ++-- 12 files changed, 77 insertions(+), 29 deletions(-) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index c8797ca56a..c50dd68f1f 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -216,7 +216,10 @@ EVENT_SOURCE_UPDATE_TIME = 10s ; Render soft line breaks as hard line breaks, which means a single newline character between ; paragraphs will cause a line break and adding trailing whitespace to paragraphs is not ; necessary to force a line break. -ENABLE_HARD_LINE_BREAK = true +; Render soft line breaks as hard line breaks for comments +ENABLE_HARD_LINE_BREAK_IN_COMMENTS = true +; Render soft line breaks as hard line breaks for markdown documents +ENABLE_HARD_LINE_BREAK_IN_DOCUMENTS = false ; Comma separated list of custom URL-Schemes that are allowed as links when rendering Markdown ; for example git,magnet,ftp (more at https://en.wikipedia.org/wiki/List_of_URI_schemes) ; URLs starting with http and https are always displayed, whatever is put in this entry. diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 3ad24776ff..e3ff2deb37 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -152,7 +152,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. ## Markdown (`markdown`) -- `ENABLE_HARD_LINE_BREAK`: **true**: Render soft line breaks as hard line breaks, which +- `ENABLE_HARD_LINE_BREAK_IN_COMMENTS`: **true**: Render soft line breaks as hard line breaks in comments, which + means a single newline character between paragraphs will cause a line break and adding + trailing whitespace to paragraphs is not necessary to force a line break. +- `ENABLE_HARD_LINE_BREAK_IN_DOCUMENTS`: **false**: Render soft line breaks as hard line breaks in documents, which means a single newline character between paragraphs will cause a line break and adding trailing whitespace to paragraphs is not necessary to force a line break. - `CUSTOM_URL_SCHEMES`: Use a comma separated list (ftp,git,svn) to indicate additional diff --git a/models/repo.go b/models/repo.go index f79740e747..13830c67f0 100644 --- a/models/repo.go +++ b/models/repo.go @@ -174,9 +174,10 @@ type Repository struct { *Mirror `xorm:"-"` Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"` - RenderingMetas map[string]string `xorm:"-"` - Units []*RepoUnit `xorm:"-"` - PrimaryLanguage *LanguageStat `xorm:"-"` + RenderingMetas map[string]string `xorm:"-"` + DocumentRenderingMetas map[string]string `xorm:"-"` + Units []*RepoUnit `xorm:"-"` + PrimaryLanguage *LanguageStat `xorm:"-"` IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"` ForkID int64 `xorm:"INDEX"` @@ -534,11 +535,12 @@ func (repo *Repository) mustOwner(e Engine) *User { // ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers. func (repo *Repository) ComposeMetas() map[string]string { - if repo.RenderingMetas == nil { + if len(repo.RenderingMetas) == 0 { metas := map[string]string{ "user": repo.OwnerName, "repo": repo.Name, "repoPath": repo.RepoPath(), + "mode": "comment", } unit, err := repo.GetUnit(UnitTypeExternalTracker) @@ -570,6 +572,19 @@ func (repo *Repository) ComposeMetas() map[string]string { return repo.RenderingMetas } +// ComposeDocumentMetas composes a map of metas for properly rendering documents +func (repo *Repository) ComposeDocumentMetas() map[string]string { + if len(repo.DocumentRenderingMetas) == 0 { + metas := map[string]string{} + for k, v := range repo.ComposeMetas() { + metas[k] = v + } + metas["mode"] = "document" + repo.DocumentRenderingMetas = metas + } + return repo.DocumentRenderingMetas +} + // DeleteWiki removes the actual and local copy of repository wiki. func (repo *Repository) DeleteWiki() error { return repo.deleteWiki(x) diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index bf02a22d5e..9447424644 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -151,6 +151,16 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa v.AppendChild(v, newChild) } } + case *ast.Text: + if v.SoftLineBreak() && !v.HardLineBreak() { + renderMetas := pc.Get(renderMetasKey).(map[string]string) + mode := renderMetas["mode"] + if mode != "document" { + v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments) + } else { + v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments) + } + } } return ast.WalkContinue, nil }) diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index e50301ffe4..515d684686 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -29,17 +29,19 @@ var once = sync.Once{} var urlPrefixKey = parser.NewContextKey() var isWikiKey = parser.NewContextKey() +var renderMetasKey = parser.NewContextKey() // NewGiteaParseContext creates a parser.Context with the gitea context set -func NewGiteaParseContext(urlPrefix string, isWiki bool) parser.Context { +func NewGiteaParseContext(urlPrefix string, metas map[string]string, isWiki bool) parser.Context { pc := parser.NewContext(parser.WithIDs(newPrefixedIDs())) pc.Set(urlPrefixKey, urlPrefix) pc.Set(isWikiKey, isWiki) + pc.Set(renderMetasKey, metas) return pc } -// RenderRaw renders Markdown to HTML without handling special links. -func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte { +// render renders Markdown to HTML without handling special links. +func render(body []byte, urlPrefix string, metas map[string]string, wikiMarkdown bool) []byte { once.Do(func() { converter = goldmark.New( goldmark.WithExtensions(extension.Table, @@ -75,12 +77,9 @@ func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte { ), ) - if setting.Markdown.EnableHardLineBreak { - converter.Renderer().AddOptions(html.WithHardWraps()) - } }) - pc := NewGiteaParseContext(urlPrefix, wikiMarkdown) + pc := NewGiteaParseContext(urlPrefix, metas, wikiMarkdown) var buf bytes.Buffer if err := converter.Convert(giteautil.NormalizeEOL(body), &buf, parser.WithContext(pc)); err != nil { log.Error("Unable to render: %v", err) @@ -112,7 +111,7 @@ func (Parser) Extensions() []string { // Render implements markup.Parser func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte { - return RenderRaw(rawBytes, urlPrefix, isWiki) + return render(rawBytes, urlPrefix, metas, isWiki) } // Render renders Markdown to HTML with all specific handling stuff. @@ -120,6 +119,11 @@ func Render(rawBytes []byte, urlPrefix string, metas map[string]string) []byte { return markup.Render("a.md", rawBytes, urlPrefix, metas) } +// RenderRaw renders Markdown to HTML without handling special links. +func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte { + return render(body, urlPrefix, map[string]string{}, wikiMarkdown) +} + // RenderString renders Markdown to HTML with special links and returns string type. func RenderString(raw, urlPrefix string, metas map[string]string) string { return markup.RenderString("a.md", raw, urlPrefix, metas) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 9507d46930..b8ec957535 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -256,12 +256,14 @@ var ( // Markdown settings Markdown = struct { - EnableHardLineBreak bool - CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"` - FileExtensions []string + EnableHardLineBreakInComments bool + EnableHardLineBreakInDocuments bool + CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"` + FileExtensions []string }{ - EnableHardLineBreak: true, - FileExtensions: strings.Split(".md,.markdown,.mdown,.mkd", ","), + EnableHardLineBreakInComments: true, + EnableHardLineBreakInDocuments: false, + FileExtensions: strings.Split(".md,.markdown,.mdown,.mkd", ","), } // Admin settings diff --git a/routers/api/v1/misc/markdown.go b/routers/api/v1/misc/markdown.go index 5a44db5e8b..a10c288df4 100644 --- a/routers/api/v1/misc/markdown.go +++ b/routers/api/v1/misc/markdown.go @@ -48,10 +48,12 @@ func Markdown(ctx *context.APIContext, form api.MarkdownOption) { } switch form.Mode { + case "comment": + fallthrough case "gfm": md := []byte(form.Text) urlPrefix := form.Context - var meta map[string]string + meta := map[string]string{} if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) { // check if urlPrefix is already set to a URL linkRegex, _ := xurls.StrictMatchingScheme("https?://") @@ -61,7 +63,15 @@ func Markdown(ctx *context.APIContext, form api.MarkdownOption) { } } if ctx.Repo != nil && ctx.Repo.Repository != nil { - meta = ctx.Repo.Repository.ComposeMetas() + // "gfm" = Github Flavored Markdown - set this to render as a document + if form.Mode == "gfm" { + meta = ctx.Repo.Repository.ComposeDocumentMetas() + } else { + meta = ctx.Repo.Repository.ComposeMetas() + } + } + if form.Mode == "gfm" { + meta["mode"] = "document" } if form.Wiki { _, err := ctx.Write([]byte(markdown.RenderWiki(md, urlPrefix, meta))) diff --git a/routers/api/v1/misc/markdown_test.go b/routers/api/v1/misc/markdown_test.go index 264a6010f9..6c81ec8eb4 100644 --- a/routers/api/v1/misc/markdown_test.go +++ b/routers/api/v1/misc/markdown_test.go @@ -94,7 +94,7 @@ Here are some links to the most important topics. You can find the full list of

Wine Staging on website wine-staging.com.

Here are some links to the most important topics. You can find the full list of pages at the sidebar.

-

Configuration
+

Configuration images/icon-bug.png

`, // Guard wiki sidebar: special syntax diff --git a/routers/repo/view.go b/routers/repo/view.go index 9c9cdc06bf..0f4e2d838c 100644 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -319,7 +319,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { if markupType := markup.Type(readmeFile.name); markupType != "" { ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = string(markupType) - ctx.Data["FileContent"] = string(markup.Render(readmeFile.name, buf, readmeTreelink, ctx.Repo.Repository.ComposeMetas())) + ctx.Data["FileContent"] = string(markup.Render(readmeFile.name, buf, readmeTreelink, ctx.Repo.Repository.ComposeDocumentMetas())) } else { ctx.Data["IsRenderedHTML"] = true ctx.Data["FileContent"] = strings.Replace( @@ -459,7 +459,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st if markupType := markup.Type(blob.Name()); markupType != "" { ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType - ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas())) + ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeDocumentMetas())) } else if readmeExist { ctx.Data["IsRenderedHTML"] = true ctx.Data["FileContent"] = strings.Replace( @@ -538,7 +538,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st buf = append(buf, d...) ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType - ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas())) + ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeDocumentMetas())) } } diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go index 5da01f21ac..29b0fb2ea6 100644 --- a/routers/repo/wiki.go +++ b/routers/repo/wiki.go @@ -209,7 +209,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { return nil, nil } - metas := ctx.Repo.Repository.ComposeMetas() + metas := ctx.Repo.Repository.ComposeDocumentMetas() ctx.Data["content"] = markdown.RenderWiki(data, ctx.Repo.RepoLink, metas) ctx.Data["sidebarPresent"] = sidebarContent != nil ctx.Data["sidebarContent"] = markdown.RenderWiki(sidebarContent, ctx.Repo.RepoLink, metas) diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl index 283bd32508..cc98539eab 100644 --- a/templates/repo/editor/edit.tmpl +++ b/templates/repo/editor/edit.tmpl @@ -30,7 +30,7 @@ diff --git a/web_src/js/index.js b/web_src/js/index.js index 9aab32faab..60ce8042c0 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -42,7 +42,7 @@ function initCommentPreviewTab($form) { const $this = $(this); $.post($this.data('url'), { _csrf: csrf, - mode: 'gfm', + mode: 'comment', context: $this.data('context'), text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val() }, (data) => { @@ -66,6 +66,7 @@ function initEditPreviewTab($form) { $previewTab.on('click', function () { const $this = $(this); let context = `${$this.data('context')}/`; + const mode = $this.data('markdown-mode') || 'comment'; const treePathEl = $form.find('input#tree_path'); if (treePathEl.length > 0) { context += treePathEl.val(); @@ -73,7 +74,7 @@ function initEditPreviewTab($form) { context = context.substring(0, context.lastIndexOf('/')); $.post($this.data('url'), { _csrf: csrf, - mode: 'gfm', + mode, context, text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val() }, (data) => {