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>tags/v1.13.0-rc1
@@ -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 |
@@ -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"` | |||
@@ -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,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 |
@@ -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))) |
@@ -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) => { |