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:]
 | 
							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"))
 | 
								replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mentionedUsername), mention, "mention"))
 | 
				
			||||||
			node = node.NextSibling.NextSibling
 | 
								node = node.NextSibling.NextSibling
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,6 +47,12 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 | 
				
			|||||||
		tocMode = rc.TOC
 | 
							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])
 | 
						attentionMarkedBlockquotes := make(container.Set[*ast.Blockquote])
 | 
				
			||||||
	_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
 | 
						_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
 | 
				
			||||||
		if !entering {
 | 
							if !entering {
 | 
				
			||||||
@@ -69,6 +75,9 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 | 
				
			|||||||
				header.ID = util.BytesToReadOnlyString(id.([]byte))
 | 
									header.ID = util.BytesToReadOnlyString(id.([]byte))
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			tocList = append(tocList, header)
 | 
								tocList = append(tocList, header)
 | 
				
			||||||
 | 
								applyElementDir(v)
 | 
				
			||||||
 | 
							case *ast.Paragraph:
 | 
				
			||||||
 | 
								applyElementDir(v)
 | 
				
			||||||
		case *ast.Image:
 | 
							case *ast.Image:
 | 
				
			||||||
			// Images need two things:
 | 
								// Images need two things:
 | 
				
			||||||
			//
 | 
								//
 | 
				
			||||||
@@ -171,6 +180,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 | 
				
			|||||||
					v.AppendChild(v, newChild)
 | 
										v.AppendChild(v, newChild)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								applyElementDir(v)
 | 
				
			||||||
		case *ast.Text:
 | 
							case *ast.Text:
 | 
				
			||||||
			if v.SoftLineBreak() && !v.HardLineBreak() {
 | 
								if v.SoftLineBreak() && !v.HardLineBreak() {
 | 
				
			||||||
				renderMetas := pc.Get(renderMetasKey).(map[string]string)
 | 
									renderMetas := pc.Get(renderMetasKey).(map[string]string)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,14 +30,16 @@ const (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type ProcessorHelper struct {
 | 
					type ProcessorHelper struct {
 | 
				
			||||||
	IsUsernameMentionable func(ctx context.Context, username string) bool
 | 
						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
 | 
					// Init initialize regexps for markdown parsing
 | 
				
			||||||
func Init(ph *ProcessorHelper) {
 | 
					func Init(ph *ProcessorHelper) {
 | 
				
			||||||
	if ph != nil {
 | 
						if ph != nil {
 | 
				
			||||||
		processorHelper = *ph
 | 
							DefaultProcessorHelper = *ph
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	NewSanitizer()
 | 
						NewSanitizer()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func ProcessorHelper() *markup.ProcessorHelper {
 | 
					func ProcessorHelper() *markup.ProcessorHelper {
 | 
				
			||||||
	return &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 {
 | 
							IsUsernameMentionable: func(ctx context.Context, username string) bool {
 | 
				
			||||||
			mentionedUser, err := user.GetUserByName(ctx, username)
 | 
								mentionedUser, err := user.GetUserByName(ctx, username)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -250,7 +250,7 @@ func TestGetUserRss(t *testing.T) {
 | 
				
			|||||||
		title, _ := rssDoc.ChildrenFiltered("title").Html()
 | 
							title, _ := rssDoc.ChildrenFiltered("title").Html()
 | 
				
			||||||
		assert.EqualValues(t, "Feed of "the_1-user.with.all.allowedChars"", title)
 | 
							assert.EqualValues(t, "Feed of "the_1-user.with.all.allowedChars"", title)
 | 
				
			||||||
		description, _ := rssDoc.ChildrenFiltered("description").Html()
 | 
							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);
 | 
					  color: var(--color-text);
 | 
				
			||||||
  background: var(--color-box-body);
 | 
					  background: var(--color-box-body);
 | 
				
			||||||
  border-color: var(--color-secondary);
 | 
					  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,
 | 
					.ui.table th,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,9 +23,9 @@
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.markup .anchor {
 | 
					.markup .anchor {
 | 
				
			||||||
 | 
					  float: left;
 | 
				
			||||||
  padding-right: 4px;
 | 
					  padding-right: 4px;
 | 
				
			||||||
  margin-left: -20px;
 | 
					  margin-left: -20px;
 | 
				
			||||||
  line-height: 1;
 | 
					 | 
				
			||||||
  color: inherit;
 | 
					  color: inherit;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -37,6 +37,10 @@
 | 
				
			|||||||
  outline: none;
 | 
					  outline: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.markup h1 .anchor {
 | 
				
			||||||
 | 
					  margin-top: -2px; /* re-align to center */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.markup h1 .anchor .svg,
 | 
					.markup h1 .anchor .svg,
 | 
				
			||||||
.markup h2 .anchor .svg,
 | 
					.markup h2 .anchor .svg,
 | 
				
			||||||
.markup h3 .anchor .svg,
 | 
					.markup h3 .anchor .svg,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user