mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Add RTL rendering support to Markdown (#24816)
Support RTL content in Markdown:  Example document: https://try.gitea.io/silverwind/symlink-test/src/branch/master/bidi-text.md Same on GitHub: https://github.com/silverwind/symlink-test/blob/master/bidi-text.md `dir=auto` enables a browser heuristic that sets the text direction automatically. It is the only way to get automatic text direction. Ref: https://codeberg.org/Codeberg/Community/issues/1021 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		@@ -630,7 +630,7 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
 | 
			
		||||
		}
 | 
			
		||||
		mentionedUsername := mention[1:]
 | 
			
		||||
 | 
			
		||||
		if processorHelper.IsUsernameMentionable != nil && processorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
 | 
			
		||||
		if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
 | 
			
		||||
			replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mentionedUsername), mention, "mention"))
 | 
			
		||||
			node = node.NextSibling.NextSibling
 | 
			
		||||
		} else {
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,12 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 | 
			
		||||
		tocMode = rc.TOC
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	applyElementDir := func(n ast.Node) {
 | 
			
		||||
		if markup.DefaultProcessorHelper.ElementDir != "" {
 | 
			
		||||
			n.SetAttributeString("dir", []byte(markup.DefaultProcessorHelper.ElementDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attentionMarkedBlockquotes := make(container.Set[*ast.Blockquote])
 | 
			
		||||
	_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
 | 
			
		||||
		if !entering {
 | 
			
		||||
@@ -69,6 +75,9 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 | 
			
		||||
				header.ID = util.BytesToReadOnlyString(id.([]byte))
 | 
			
		||||
			}
 | 
			
		||||
			tocList = append(tocList, header)
 | 
			
		||||
			applyElementDir(v)
 | 
			
		||||
		case *ast.Paragraph:
 | 
			
		||||
			applyElementDir(v)
 | 
			
		||||
		case *ast.Image:
 | 
			
		||||
			// Images need two things:
 | 
			
		||||
			//
 | 
			
		||||
@@ -171,6 +180,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 | 
			
		||||
					v.AppendChild(v, newChild)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			applyElementDir(v)
 | 
			
		||||
		case *ast.Text:
 | 
			
		||||
			if v.SoftLineBreak() && !v.HardLineBreak() {
 | 
			
		||||
				renderMetas := pc.Get(renderMetasKey).(map[string]string)
 | 
			
		||||
 
 | 
			
		||||
@@ -30,14 +30,16 @@ const (
 | 
			
		||||
 | 
			
		||||
type ProcessorHelper struct {
 | 
			
		||||
	IsUsernameMentionable func(ctx context.Context, username string) bool
 | 
			
		||||
 | 
			
		||||
	ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var processorHelper ProcessorHelper
 | 
			
		||||
var DefaultProcessorHelper ProcessorHelper
 | 
			
		||||
 | 
			
		||||
// Init initialize regexps for markdown parsing
 | 
			
		||||
func Init(ph *ProcessorHelper) {
 | 
			
		||||
	if ph != nil {
 | 
			
		||||
		processorHelper = *ph
 | 
			
		||||
		DefaultProcessorHelper = *ph
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	NewSanitizer()
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import (
 | 
			
		||||
 | 
			
		||||
func ProcessorHelper() *markup.ProcessorHelper {
 | 
			
		||||
	return &markup.ProcessorHelper{
 | 
			
		||||
		ElementDir: "auto", // set dir="auto" for necessary (eg: <p>, <h?>, etc) tags
 | 
			
		||||
		IsUsernameMentionable: func(ctx context.Context, username string) bool {
 | 
			
		||||
			mentionedUser, err := user.GetUserByName(ctx, username)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -250,7 +250,7 @@ func TestGetUserRss(t *testing.T) {
 | 
			
		||||
		title, _ := rssDoc.ChildrenFiltered("title").Html()
 | 
			
		||||
		assert.EqualValues(t, "Feed of "the_1-user.with.all.allowedChars"", title)
 | 
			
		||||
		description, _ := rssDoc.ChildrenFiltered("description").Html()
 | 
			
		||||
		assert.EqualValues(t, "<p>some <a href="https://commonmark.org/" rel="nofollow">commonmark</a>!</p>\n", description)
 | 
			
		||||
		assert.EqualValues(t, "<p dir="auto">some <a href="https://commonmark.org/" rel="nofollow">commonmark</a>!</p>\n", description)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1091,6 +1091,7 @@ a.label,
 | 
			
		||||
  color: var(--color-text);
 | 
			
		||||
  background: var(--color-box-body);
 | 
			
		||||
  border-color: var(--color-secondary);
 | 
			
		||||
  text-align: start; /* Override fomantic's `text-align: left` to make RTL work via HTML `dir="auto"` */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ui.table th,
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,9 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.markup .anchor {
 | 
			
		||||
  float: left;
 | 
			
		||||
  padding-right: 4px;
 | 
			
		||||
  margin-left: -20px;
 | 
			
		||||
  line-height: 1;
 | 
			
		||||
  color: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -37,6 +37,10 @@
 | 
			
		||||
  outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.markup h1 .anchor {
 | 
			
		||||
  margin-top: -2px; /* re-align to center */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.markup h1 .anchor .svg,
 | 
			
		||||
.markup h2 .anchor .svg,
 | 
			
		||||
.markup h3 .anchor .svg,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user