Allow different HardBreaks settings for documents and comments (#11515)
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 <me@silverwind.io> Changes to docs as per @guillep2k Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									3761bdb640
								
							
						
					
					
						commit
						814ca9ffea
					
				| @ -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. | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -175,6 +175,7 @@ type Repository struct { | ||||
| 	Status     RepositoryStatus `xorm:"NOT NULL DEFAULT 0"` | ||||
| 
 | ||||
| 	RenderingMetas         map[string]string `xorm:"-"` | ||||
| 	DocumentRenderingMetas map[string]string `xorm:"-"` | ||||
| 	Units                  []*RepoUnit       `xorm:"-"` | ||||
| 	PrimaryLanguage        *LanguageStat     `xorm:"-"` | ||||
| 
 | ||||
| @ -545,11 +546,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) | ||||
| @ -581,6 +583,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) | ||||
|  | ||||
| @ -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 | ||||
| 	}) | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -256,11 +256,13 @@ var ( | ||||
| 
 | ||||
| 	// Markdown settings
 | ||||
| 	Markdown = struct { | ||||
| 		EnableHardLineBreak bool | ||||
| 		EnableHardLineBreakInComments  bool | ||||
| 		EnableHardLineBreakInDocuments bool | ||||
| 		CustomURLSchemes               []string `ini:"CUSTOM_URL_SCHEMES"` | ||||
| 		FileExtensions                 []string | ||||
| 	}{ | ||||
| 		EnableHardLineBreak: true, | ||||
| 		EnableHardLineBreakInComments:  true, | ||||
| 		EnableHardLineBreakInDocuments: false, | ||||
| 		FileExtensions:                 strings.Split(".md,.markdown,.mdown,.mkd", ","), | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -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,8 +63,16 @@ func Markdown(ctx *context.APIContext, form api.MarkdownOption) { | ||||
| 			} | ||||
| 		} | ||||
| 		if ctx.Repo != nil && ctx.Repo.Repository != nil { | ||||
| 			// "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))) | ||||
| 			if err != nil { | ||||
|  | ||||
| @ -94,7 +94,7 @@ Here are some links to the most important topics. You can find the full list of | ||||
| <p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p> | ||||
| <h2 id="user-content-quick-links">Quick Links</h2> | ||||
| <p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p> | ||||
| <p><a href="` + AppSubURL + `wiki/Configuration" rel="nofollow">Configuration</a><br/> | ||||
| <p><a href="` + AppSubURL + `wiki/Configuration" rel="nofollow">Configuration</a> | ||||
| <a href="` + AppSubURL + `wiki/raw/images/icon-bug.png" rel="nofollow"><img src="` + AppSubURL + `wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p> | ||||
| `, | ||||
| 		// Guard wiki sidebar: special syntax
 | ||||
|  | ||||
| @ -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())) | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -30,7 +30,7 @@ | ||||
| 				<div class="ui top attached tabular menu" data-write="write" data-preview="preview" data-diff="diff"> | ||||
| 					<a class="active item" data-tab="write">{{svg "octicon-code" 16}} {{if .IsNewFile}}{{.i18n.Tr "repo.editor.new_file"}}{{else}}{{.i18n.Tr "repo.editor.edit_file"}}{{end}}</a> | ||||
| 					{{if not .IsNewFile}} | ||||
| 					<a class="item" data-tab="preview" data-url="{{.Repository.APIURL}}/markdown" data-context="{{.RepoLink}}/src/{{.BranchNameSubURL | EscapePound}}" data-preview-file-modes="{{.PreviewableFileModes}}">{{svg "octicon-eye" 16}} {{.i18n.Tr "preview"}}</a> | ||||
| 					<a class="item" data-tab="preview" data-url="{{.Repository.APIURL}}/markdown" data-context="{{.RepoLink}}/src/{{.BranchNameSubURL | EscapePound}}" data-preview-file-modes="{{.PreviewableFileModes}}" data-markdown-mode="gfm">{{svg "octicon-eye" 16}} {{.i18n.Tr "preview"}}</a> | ||||
| 					<a class="item" data-tab="diff" data-url="{{.RepoLink}}/_preview/{{.BranchName | EscapePound}}/{{.TreePath | EscapePound}}" data-context="{{.BranchLink}}">{{svg "octicon-diff" 16}} {{.i18n.Tr "repo.editor.preview_changes"}}</a> | ||||
| 					{{end}} | ||||
| 				</div> | ||||
|  | ||||
| @ -41,7 +41,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) => { | ||||
| @ -65,6 +65,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(); | ||||
| @ -72,7 +73,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) => { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user