Merge remote-tracking branch 'upstream/main'
This commit is contained in:
		
						commit
						47229ea208
					
				
					 41 changed files with 1206 additions and 288 deletions
				
			
		|  | @ -1262,6 +1262,9 @@ ROUTER = console | |||
| ;; List of file extensions that should be rendered/edited as Markdown | ||||
| ;; Separate the extensions with a comma. To render files without any extension as markdown, just put a comma | ||||
| ;FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd | ||||
| ;; | ||||
| ;; Enables math inline and block detection | ||||
| ;ENABLE_MATH = true | ||||
| 
 | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
|  |  | |||
|  | @ -236,6 +236,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a | |||
| - `CUSTOM_URL_SCHEMES`: Use a comma separated list (ftp,git,svn) to indicate additional | ||||
|   URL hyperlinks to be rendered in Markdown. URLs beginning in http and https are | ||||
|   always displayed | ||||
| - `ENABLE_MATH`: **true**: Enables detection of `\(...\)`, `\[...\]`, `$...$` and `$$...$$` blocks as math blocks. | ||||
| 
 | ||||
| ## Server (`server`) | ||||
| 
 | ||||
|  |  | |||
|  | @ -74,12 +74,13 @@ RENDER_COMMAND = "timeout 30s pandoc +RTS -M512M -RTS -f rst" | |||
| IS_INPUT_FILE = false | ||||
| ``` | ||||
| 
 | ||||
| If your external markup relies on additional classes and attributes on the generated HTML elements, you might need to enable custom sanitizer policies. Gitea uses the [`bluemonday`](https://godoc.org/github.com/microcosm-cc/bluemonday) package as our HTML sanitizier. The example below will support [KaTeX](https://katex.org/) output from [`pandoc`](https://pandoc.org/). | ||||
| If your external markup relies on additional classes and attributes on the generated HTML elements, you might need to enable custom sanitizer policies. Gitea uses the [`bluemonday`](https://godoc.org/github.com/microcosm-cc/bluemonday) package as our HTML sanitizer. The example below could be used to support server-side [KaTeX](https://katex.org/) rendering output from [`pandoc`](https://pandoc.org/). | ||||
| 
 | ||||
| ```ini | ||||
| [markup.sanitizer.TeX] | ||||
| ; Pandoc renders TeX segments as <span>s with the "math" class, optionally | ||||
| ; with "inline" or "display" classes depending on context. | ||||
| ; - note this is different from the built-in math support in our markdown parser which uses <code> | ||||
| ELEMENT = span | ||||
| ALLOW_ATTR = class | ||||
| REGEXP = ^\s*((math(\s+|$)|inline(\s+|$)|display(\s+|$)))+ | ||||
|  |  | |||
|  | @ -53,6 +53,8 @@ _Symbols used in table:_ | |||
| | WebAuthn (2FA)                      | ✓                                                  | ✘    | ✓         | ✓         | ✓         | ✓              | ?            | | ||||
| | Built-in CI/CD                      | ✘                                                  | ✘    | ✓         | ✓         | ✓         | ✘              | ✘            | | ||||
| | Subgroups: groups within groups     | [✘](https://github.com/go-gitea/gitea/issues/1872) | ✘    | ✘         | ✓         | ✓         | ✘              | ✓            | | ||||
| | Mermaid diagrams in Markdown        | ✓                                                  | ✘    | ✓         | ✓         | ✓         | ✘              | ✘            | | ||||
| | Math syntax in Markdown             | ✓                                                  | ✘    | ✓         | ✓         | ✓         | ✘              | ✘            | | ||||
| 
 | ||||
| ## Code management | ||||
| 
 | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ _表格中的符号含义:_ | |||
| #### 主要特性 | ||||
| 
 | ||||
| | 特性                  | Gitea                                              | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket      | RhodeCode CE | | ||||
| |-----------------------|-------|------|-----------|-----------|-----------|-----------|--------------| | ||||
| | --------------------- | -------------------------------------------------- | ---- | --------- | --------- | --------- | -------------- | ------------ | | ||||
| | 开源免费              | ✓                                                  | ✓    | ✘         | ✓         | ✘         | ✘              | ✓            | | ||||
| | 低资源开销 (RAM/CPU)  | ✓                                                  | ✓    | ✘         | ✘         | ✘         | ✘              | ✘            | | ||||
| | 支持多种数据库        | ✓                                                  | ✓    | ✘         | ⁄         | ⁄         | ✓              | ✓            | | ||||
|  | @ -42,79 +42,79 @@ _表格中的符号含义:_ | |||
| | 支持 Orgmode          | ✓                                                  | ✘    | ✓         | ✘         | ✘         | ✘              | ?            | | ||||
| | 支持 CSV              | ✓                                                  | ✘    | ✓         | ✘         | ✘         | ✓              | ?            | | ||||
| | 支持第三方渲染工具    | ✓                                                  | ✘    | ✘         | ✘         | ✘         | ✓              | ?            | | ||||
| | Git 驱动的静态 pages  | ✘     | ✘    | ✓         | ✓         | ✓         | ✘         | ✘            | | ||||
| | Git 驱动的集成化 wiki | ✓     | ✓    | ✓         | ✓         | ✓         | ✓         | ✘            | | ||||
| | Git 驱动的静态 pages  | [✘](https://github.com/go-gitea/gitea/issues/302)  | ✘    | ✓         | ✓         | ✓         | ✘              | ✘            | | ||||
| | Git 驱动的集成化 wiki | ✓                                                  | ✓    | ✓         | ✓         | ✓         | ✓ (cloud only) | ✘            | | ||||
| | 部署令牌              | ✓                                                  | ✓    | ✓         | ✓         | ✓         | ✓              | ✓            | | ||||
| | 仓库写权限令牌        | ✓     | ✘    | ✓         | ✓         | ✓         | ✘         | ✓            | | ||||
| | 内置容器 Registry     | ✓     | ✘    | ✘         | ✓         | ✓         | ✘         | ✘            | | ||||
| | 仓库写权限令牌        | ✓                                                  | ✘    | ✓         | ✓         | ✓         | ✓              | ✓            | | ||||
| | 内置容器 Registry     | ✓                                                  | ✘    | ✓         | ✓         | ✓         | ✘              | ✘            | | ||||
| | 外部 Git 镜像         | ✓                                                  | ✓    | ✘         | ✘         | ✓         | ✓              | ✓            | | ||||
| | WebAuthn (2FA)        | ✓                                                  | ✘    | ✓         | ✓         | ✓         | ✓              | ?            | | ||||
| | 内置 CI/CD            | ✘     | ✘    | ✘         | ✓         | ✓         | ✘         | ✘            | | ||||
| | 子组织:组织内的组织  | ✘     | ✘    | ✘         | ✓         | ✓         | ✘         | ✓            | | ||||
| | 内置 CI/CD            | ✘                                                  | ✘    | ✓         | ✓         | ✓         | ✘              | ✘            | | ||||
| | 子组织:组织内的组织  | [✘](https://github.com/go-gitea/gitea/issues/1872) | ✘    | ✘         | ✓         | ✓         | ✘              | ✓            | | ||||
| 
 | ||||
| #### 代码管理 | ||||
| 
 | ||||
| | 特性                                     | Gitea                                            | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | | ||||
| |------------------------------------------|-------|------|-----------|-----------|-----------|-----------|--------------| | ||||
| | ---------------------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ | | ||||
| | 仓库主题描述                             | ✓                                                | ✘    | ✓         | ✓         | ✓         | ✘         | ✘            | | ||||
| | 仓库内代码搜索                           | ✓                                                | ✘    | ✓         | ✓         | ✓         | ✓         | ✓            | | ||||
| | 全局代码搜索                             | ✓                                                | ✘    | ✓         | ✘         | ✓         | ✓         | ✓            | | ||||
| | Git LFS 2.0                              | ✓     | ✘    | ✓         | ✓         | ✓         | ⁄         | ✓            | | ||||
| | Git LFS 2.0                              | ✓                                                | ✘    | ✓         | ✓         | ✓         | ✓         | ✓            | | ||||
| | 组织里程碑                               | ✘                                                | ✘    | ✘         | ✓         | ✓         | ✘         | ✘            | | ||||
| | 细粒度用户角色 (例如 Code, Issues, Wiki) | ✓                                                | ✘    | ✘         | ✓         | ✓         | ✘         | ✘            | | ||||
| | 提交人的身份验证                         | ⁄                                                | ✘    | ?         | ✓         | ✓         | ✓         | ✘            | | ||||
| | GPG 签名的提交                           | ✓                                                | ✘    | ✓         | ✓         | ✓         | ✓         | ✓            | | ||||
| | SSH 签名的提交                           | ✓                                                | ✘    | ✘         | ✘         | ✘         | ?         | ?            | | ||||
| | 拒绝未用通过验证的提交                   | ✓     | ✘    | ✓         | ✓         | ✓         | ✘         | ✓            | | ||||
| | 拒绝未用通过验证的提交                   | [✓](https://github.com/go-gitea/gitea/pull/9708) | ✘    | ✓         | ✓         | ✓         | ✓         | ✓            | | ||||
| | 仓库活跃度页面                           | ✓                                                | ✘    | ✓         | ✓         | ✓         | ✓         | ✓            | | ||||
| | 分支管理                                 | ✓                                                | ✘    | ✓         | ✓         | ✓         | ✓         | ✓            | | ||||
| | 建立新分支                               | ✓                                                | ✘    | ✓         | ✓         | ✓         | ✘         | ✘            | | ||||
| | 在线代码编辑                             | ✓                                                | ✓    | ✓         | ✓         | ✓         | ✓         | ✓            | | ||||
| | 提交的统计图表                           | ✓                                                | ✘    | ✓         | ✓         | ✓         | ✓         | ✓            | | ||||
| | 模板仓库                                | ✓     | ✘    | ✓         | ✘         | ✓         | ✓         | ✘            | | ||||
| | 模板仓库                                 | [✓](https://github.com/go-gitea/gitea/pull/8768) | ✘    | ✓         | ✘         | ✓         | ✓         | ✘            | | ||||
| 
 | ||||
| #### Issue 管理 | ||||
| #### 工单管理 | ||||
| 
 | ||||
| | 特性                | Gitea                                              | Gogs                                          | GitHub EE | GitLab CE                                                               | GitLab EE | BitBucket      | RhodeCode CE | | ||||
| |----------------------|-------|------|-----------|-----------|-----------|-----------|--------------| | ||||
| | 跟踪 Issue           | ✓     | ✓    | ✓         | ✓         | ✓         | ✓         | ✘            | | ||||
| | Issue 模板           | ✓     | ✓    | ✓         | ✓         | ✓         | ✘         | ✘            | | ||||
| | ------------------- | -------------------------------------------------- | --------------------------------------------- | --------- | ----------------------------------------------------------------------- | --------- | -------------- | ------------ | | ||||
| | 工单跟踪            | ✓                                                  | ✓                                             | ✓         | ✓                                                                       | ✓         | ✓ (cloud only) | ✘            | | ||||
| | 工单模板            | ✓                                                  | ✓                                             | ✓         | ✓                                                                       | ✓         | ✘              | ✘            | | ||||
| | 标签                | ✓                                                  | ✓                                             | ✓         | ✓                                                                       | ✓         | ✘              | ✘            | | ||||
| | 跟踪时间             | ✓     | ✘    | ✓         | ✓         | ✓         | ✘         | ✘            | | ||||
| | Issue 可有多个负责人 | ✓     | ✘    | ✓         | ✘         | ✓         | ✘         | ✘            | | ||||
| | 关联的 issues        | ✘     | ✘    | ⁄         | ✘         | ✓         | ✘         | ✘            | | ||||
| | 私密 issues          | ✘     | ✘    | ✘         | ✓         | ✓         | ✘         | ✘            | | ||||
| | 时间跟踪            | ✓                                                  | ✘                                             | ✓         | ✓                                                                       | ✓         | ✘              | ✘            | | ||||
| | 支持多个负责人      | ✓                                                  | ✘                                             | ✓         | ✘                                                                       | ✓         | ✘              | ✘            | | ||||
| | 关联的工单          | ✘                                                  | ✘                                             | ⁄         | [✓](https://docs.gitlab.com/ce/user/project/issues/related_issues.html) | ✓         | ✘              | ✘            | | ||||
| | 私密工单            | [✘](https://github.com/go-gitea/gitea/issues/3217) | ✘                                             | ✘         | ✓                                                                       | ✓         | ✘              | ✘            | | ||||
| | 评论反馈            | ✓                                                  | ✘                                             | ✓         | ✓                                                                       | ✓         | ✘              | ✘            | | ||||
| | 锁定讨论            | ✓                                                  | ✘                                             | ✓         | ✓                                                                       | ✓         | ✘              | ✘            | | ||||
| | Issue 批量处理       | ✓     | ✘    | ✓         | ✓         | ✓         | ✘         | ✘            | | ||||
| | Issue 看板           | ✓     | ✘    | ✘         | ✓         | ✓         | ✘         | ✘            | | ||||
| | 从 issues 创建分支   | ✘     | ✘    | ✘         | ✓         | ✓         | ✘         | ✘            | | ||||
| | Issue 搜索           | ✓     | ✘    | ✓         | ✓         | ✓         | ✓         | ✘            | | ||||
| | 全局 Issue 搜索      | ✘     | ✘    | ✓         | ✓         | ✓         | ✓         | ✘            | | ||||
| | Issue 依赖           | ✓     | ✘    | ✘         | ✘         | ✘         | ✘         | ✘            | | ||||
| | 工单批处理          | ✓                                                  | ✘                                             | ✓         | ✓                                                                       | ✓         | ✘              | ✘            | | ||||
| | 工单看板            | [✓](https://github.com/go-gitea/gitea/pull/8346)   | ✘                                             | ✘         | ✓                                                                       | ✓         | ✘              | ✘            | | ||||
| | 从工单创建分支      | ✘                                                  | ✘                                             | ✘         | ✓                                                                       | ✓         | ✘              | ✘            | | ||||
| | 工单搜索            | ✓                                                  | ✘                                             | ✓         | ✓                                                                       | ✓         | ✓              | ✘            | | ||||
| | 工单全局搜索        | [✘](https://github.com/go-gitea/gitea/issues/2434) | ✘                                             | ✓         | ✓                                                                       | ✓         | ✓              | ✘            | | ||||
| | 工单依赖关系        | ✓                                                  | ✘                                             | ✘         | ✘                                                                       | ✘         | ✘              | ✘            | | ||||
| | 通过 Email 创建工单 | [✘](https://github.com/go-gitea/gitea/issues/6226) | [✘](https://github.com/gogs/gogs/issues/2602) | ✘         | ✘                                                                       | ✓         | ✓              | ✘            | | ||||
| | Service Desk | [✘](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | ✘ | ✓ | ✘ | ✘ | | ||||
| | 服务台              | [✘](https://github.com/go-gitea/gitea/issues/6219) | ✘                                             | ✘         | [✓](https://gitlab.com/groups/gitlab-org/-/epics/3103)                  | ✓         | ✘              | ✘            | | ||||
| 
 | ||||
| #### Pull/Merge requests | ||||
| 
 | ||||
| | 特性                                 | Gitea                                              | Gogs | GitHub EE | GitLab CE                                                                         | GitLab EE | BitBucket                                                                | RhodeCode CE | | ||||
| |--------------------------------------|-------|------|-----------|-----------|-----------|-----------|--------------| | ||||
| | ------------------------------------ | -------------------------------------------------- | ---- | --------- | --------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------ | ------------ | | ||||
| | Pull/Merge requests                  | ✓                                                  | ✓    | ✓         | ✓                                                                                 | ✓         | ✓                                                                        | ✓            | | ||||
| | Squash merging                       | ✓     | ✘    | ✓         | ✘         | ✓         | ✓         | ✓            | | ||||
| | Squash merging                       | ✓                                                  | ✘    | ✓         | [✓](https://docs.gitlab.com/ce/user/project/merge_requests/squash_and_merge.html) | ✓         | ✓                                                                        | ✓            | | ||||
| | Rebase merging                       | ✓                                                  | ✓    | ✓         | ✘                                                                                 | ⁄         | ✘                                                                        | ✓            | | ||||
| | 评论 Pull/Merge request 中的某行代码 | ✓                                                  | ✘    | ✓         | ✓                                                                                 | ✓         | ✓                                                                        | ✓            | | ||||
| | 指定 Pull/Merge request 的审核人     | ✓                                                  | ✘    | ⁄         | ✓                                                                                 | ✓         | ✓                                                                        | ✓            | | ||||
| | 解决 Merge 冲突                      | ✘     | ✘    | ✓         | ✓         | ✓         | ✓         | ✘            | | ||||
| | 解决 Merge 冲突                      | [✘](https://github.com/go-gitea/gitea/issues/5158) | ✘    | ✓         | ✓                                                                                 | ✓         | ✓                                                                        | ✘            | | ||||
| | 限制某些用户的 push 和 merge 权限    | ✓                                                  | ✘    | ✓         | ⁄                                                                                 | ✓         | ✓                                                                        | ✓            | | ||||
| | 回退某些 commits 或 merge request    | ✘     | ✘    | ✓         | ✓         | ✓         | ✓         | ✘            | | ||||
| | 回退某些 commits 或 merge request    | [✓](https://github.com/go-gitea/gitea/issues/5158) | ✘    | ✓         | ✓                                                                                 | ✓         | ✓                                                                        | ✘            | | ||||
| | Pull/Merge requests 模板             | ✓                                                  | ✓    | ✓         | ✓                                                                                 | ✓         | ✘                                                                        | ✘            | | ||||
| | 查看 Cherry-picking 的更改           | ✘     | ✘    | ✘         | ✓         | ✓         | ✘         | ✘            | | ||||
| | 下载 Patch                          | ✓     | ✘    | ✓         | ✓         | ✓         | /         | ✘            | | ||||
| | 查看 Cherry-picking 的更改           | [✓](https://github.com/go-gitea/gitea/issues/5158) | ✘    | ✘         | ✓                                                                                 | ✓         | ✘                                                                        | ✘            | | ||||
| | 下载 Patch                           | ✓                                                  | ✘    | ✓         | ✓                                                                                 | ✓         | [/](https://jira.atlassian.com/plugins/servlet/mobile#issue/BCLOUD-8323) | ✘            | | ||||
| 
 | ||||
| #### 第三方集成 | ||||
| 
 | ||||
| | 特性                       | Gitea                                              | Gogs                                          | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | | ||||
| |----------------------------|-------|------|-----------|-----------|-----------|-----------|--------------| | ||||
| | -------------------------- | -------------------------------------------------- | --------------------------------------------- | --------- | --------- | --------- | --------- | ------------ | | ||||
| | 支持 Webhook               | ✓                                                  | ✓                                             | ✓         | ✓         | ✓         | ✓         | ✓            | | ||||
| | 自定义 Git 钩子            | ✓                                                  | ✓                                             | ✓         | ✓         | ✓         | ✓         | ✓            | | ||||
| | 集成 AD / LDAP             | ✓                                                  | ✓                                             | ✓         | ✓         | ✓         | ✓         | ✓            | | ||||
|  |  | |||
|  | @ -131,7 +131,8 @@ You can try it out using [the online demo](https://try.gitea.io/). | |||
|   - Environment variables | ||||
|   - Command line options | ||||
| - Multi-language support ([21 languages](https://github.com/go-gitea/gitea/tree/main/options/locale)) | ||||
| - [Mermaid](https://mermaidjs.github.io/) Diagram support | ||||
| - [Mermaid](https://mermaidjs.github.io/) diagrams in Markdown | ||||
| - Math syntax in Markdown | ||||
| - Mail service | ||||
|   - Notifications | ||||
|   - Registration confirmation | ||||
|  |  | |||
							
								
								
									
										2
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -104,6 +104,7 @@ require ( | |||
| 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df | ||||
| 	gopkg.in/ini.v1 v1.67.0 | ||||
| 	gopkg.in/yaml.v2 v2.4.0 | ||||
| 	gopkg.in/yaml.v3 v3.0.1 | ||||
| 	mvdan.cc/xurls/v2 v2.4.0 | ||||
| 	strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 | ||||
| 	xorm.io/builder v0.3.11 | ||||
|  | @ -290,7 +291,6 @@ require ( | |||
| 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect | ||||
| 	gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect | ||||
| 	gopkg.in/warnings.v0 v0.1.2 // indirect | ||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||
| 	sigs.k8s.io/yaml v1.2.0 // indirect | ||||
| ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -63,34 +63,32 @@ func (repo *Repository) IsBranchExist(name string) bool { | |||
| // GetBranchNames returns branches from the repository, skipping skip initial branches and | ||||
| // returning at most limit branches, or all branches if limit is 0. | ||||
| func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { | ||||
| 	return callShowRef(repo.Ctx, repo.Path, BranchPrefix, "--heads", skip, limit) | ||||
| 	return callShowRef(repo.Ctx, repo.Path, BranchPrefix, []string{BranchPrefix, "--sort=-committerdate"}, skip, limit) | ||||
| } | ||||
| 
 | ||||
| // WalkReferences walks all the references from the repository | ||||
| func WalkReferences(ctx context.Context, repoPath string, walkfn func(sha1, refname string) error) (int, error) { | ||||
| 	return walkShowRef(ctx, repoPath, "", 0, 0, walkfn) | ||||
| 	return walkShowRef(ctx, repoPath, nil, 0, 0, walkfn) | ||||
| } | ||||
| 
 | ||||
| // WalkReferences walks all the references from the repository | ||||
| // refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty. | ||||
| func (repo *Repository) WalkReferences(refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { | ||||
| 	var arg string | ||||
| 	var args []string | ||||
| 	switch refType { | ||||
| 	case ObjectTag: | ||||
| 		arg = "--tags" | ||||
| 		args = []string{TagPrefix, "--sort=-taggerdate"} | ||||
| 	case ObjectBranch: | ||||
| 		arg = "--heads" | ||||
| 	default: | ||||
| 		arg = "" | ||||
| 		args = []string{BranchPrefix, "--sort=-committerdate"} | ||||
| 	} | ||||
| 
 | ||||
| 	return walkShowRef(repo.Ctx, repo.Path, arg, skip, limit, walkfn) | ||||
| 	return walkShowRef(repo.Ctx, repo.Path, args, skip, limit, walkfn) | ||||
| } | ||||
| 
 | ||||
| // callShowRef return refs, if limit = 0 it will not limit | ||||
| func callShowRef(ctx context.Context, repoPath, prefix, arg string, skip, limit int) (branchNames []string, countAll int, err error) { | ||||
| 	countAll, err = walkShowRef(ctx, repoPath, arg, skip, limit, func(_, branchName string) error { | ||||
| 		branchName = strings.TrimPrefix(branchName, prefix) | ||||
| func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs []string, skip, limit int) (branchNames []string, countAll int, err error) { | ||||
| 	countAll, err = walkShowRef(ctx, repoPath, extraArgs, skip, limit, func(_, branchName string) error { | ||||
| 		branchName = strings.TrimPrefix(branchName, trimPrefix) | ||||
| 		branchNames = append(branchNames, branchName) | ||||
| 
 | ||||
| 		return nil | ||||
|  | @ -98,7 +96,7 @@ func callShowRef(ctx context.Context, repoPath, prefix, arg string, skip, limit | |||
| 	return branchNames, countAll, err | ||||
| } | ||||
| 
 | ||||
| func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { | ||||
| func walkShowRef(ctx context.Context, repoPath string, extraArgs []string, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { | ||||
| 	stdoutReader, stdoutWriter := io.Pipe() | ||||
| 	defer func() { | ||||
| 		_ = stdoutReader.Close() | ||||
|  | @ -107,10 +105,8 @@ func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, wal | |||
| 
 | ||||
| 	go func() { | ||||
| 		stderrBuilder := &strings.Builder{} | ||||
| 		args := []string{"show-ref"} | ||||
| 		if arg != "" { | ||||
| 			args = append(args, arg) | ||||
| 		} | ||||
| 		args := []string{"for-each-ref", "--format=%(objectname) %(refname)"} | ||||
| 		args = append(args, extraArgs...) | ||||
| 		err := NewCommand(ctx, args...).Run(&RunOpts{ | ||||
| 			Dir:    repoPath, | ||||
| 			Stdout: stdoutWriter, | ||||
|  | @ -194,7 +190,7 @@ func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, wal | |||
| // GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash | ||||
| func (repo *Repository) GetRefsBySha(sha, prefix string) ([]string, error) { | ||||
| 	var revList []string | ||||
| 	_, err := walkShowRef(repo.Ctx, repo.Path, "", 0, 0, func(walkSha, refname string) error { | ||||
| 	_, err := walkShowRef(repo.Ctx, repo.Path, nil, 0, 0, func(walkSha, refname string) error { | ||||
| 		if walkSha == sha && strings.HasPrefix(refname, prefix) { | ||||
| 			revList = append(revList, refname) | ||||
| 		} | ||||
|  |  | |||
|  | @ -22,14 +22,14 @@ func TestRepository_GetBranches(t *testing.T) { | |||
| 	assert.NoError(t, err) | ||||
| 	assert.Len(t, branches, 2) | ||||
| 	assert.EqualValues(t, 3, countAll) | ||||
| 	assert.ElementsMatch(t, []string{"branch1", "branch2"}, branches) | ||||
| 	assert.ElementsMatch(t, []string{"master", "branch2"}, branches) | ||||
| 
 | ||||
| 	branches, countAll, err = bareRepo1.GetBranchNames(0, 0) | ||||
| 
 | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Len(t, branches, 3) | ||||
| 	assert.EqualValues(t, 3, countAll) | ||||
| 	assert.ElementsMatch(t, []string{"branch1", "branch2", "master"}, branches) | ||||
| 	assert.ElementsMatch(t, []string{"master", "branch2", "branch1"}, branches) | ||||
| 
 | ||||
| 	branches, countAll, err = bareRepo1.GetBranchNames(5, 1) | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ func (repo *Repository) IsTagExist(name string) bool { | |||
| // GetTags returns all tags of the repository. | ||||
| // returning at most limit tags, or all if limit is 0. | ||||
| func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) { | ||||
| 	tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, "--tags", skip, limit) | ||||
| 	tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, []string{TagPrefix, "--sort=-taggerdate"}, skip, limit) | ||||
| 	return tags, err | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										84
									
								
								modules/markup/markdown/convertyaml.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								modules/markup/markdown/convertyaml.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,84 @@ | |||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package markdown | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/yuin/goldmark/ast" | ||||
| 	east "github.com/yuin/goldmark/extension/ast" | ||||
| 	"gopkg.in/yaml.v3" | ||||
| ) | ||||
| 
 | ||||
| func nodeToTable(meta *yaml.Node) ast.Node { | ||||
| 	for { | ||||
| 		if meta == nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 		switch meta.Kind { | ||||
| 		case yaml.DocumentNode: | ||||
| 			meta = meta.Content[0] | ||||
| 			continue | ||||
| 		default: | ||||
| 		} | ||||
| 		break | ||||
| 	} | ||||
| 	switch meta.Kind { | ||||
| 	case yaml.MappingNode: | ||||
| 		return mappingNodeToTable(meta) | ||||
| 	case yaml.SequenceNode: | ||||
| 		return sequenceNodeToTable(meta) | ||||
| 	default: | ||||
| 		return ast.NewString([]byte(meta.Value)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func mappingNodeToTable(meta *yaml.Node) ast.Node { | ||||
| 	table := east.NewTable() | ||||
| 	alignments := []east.Alignment{} | ||||
| 	for i := 0; i < len(meta.Content); i += 2 { | ||||
| 		alignments = append(alignments, east.AlignNone) | ||||
| 	} | ||||
| 
 | ||||
| 	headerRow := east.NewTableRow(alignments) | ||||
| 	valueRow := east.NewTableRow(alignments) | ||||
| 	for i := 0; i < len(meta.Content); i += 2 { | ||||
| 		cell := east.NewTableCell() | ||||
| 
 | ||||
| 		cell.AppendChild(cell, nodeToTable(meta.Content[i])) | ||||
| 		headerRow.AppendChild(headerRow, cell) | ||||
| 
 | ||||
| 		if i+1 < len(meta.Content) { | ||||
| 			cell = east.NewTableCell() | ||||
| 			cell.AppendChild(cell, nodeToTable(meta.Content[i+1])) | ||||
| 			valueRow.AppendChild(valueRow, cell) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	table.AppendChild(table, east.NewTableHeader(headerRow)) | ||||
| 	table.AppendChild(table, valueRow) | ||||
| 	return table | ||||
| } | ||||
| 
 | ||||
| func sequenceNodeToTable(meta *yaml.Node) ast.Node { | ||||
| 	table := east.NewTable() | ||||
| 	alignments := []east.Alignment{east.AlignNone} | ||||
| 	for _, item := range meta.Content { | ||||
| 		row := east.NewTableRow(alignments) | ||||
| 		cell := east.NewTableCell() | ||||
| 		cell.AppendChild(cell, nodeToTable(item)) | ||||
| 		row.AppendChild(row, cell) | ||||
| 		table.AppendChild(table, row) | ||||
| 	} | ||||
| 	return table | ||||
| } | ||||
| 
 | ||||
| func nodeToDetails(meta *yaml.Node, icon string) ast.Node { | ||||
| 	details := NewDetails() | ||||
| 	summary := NewSummary() | ||||
| 	summary.AppendChild(summary, NewIcon(icon)) | ||||
| 	details.AppendChild(details, summary) | ||||
| 	details.AppendChild(details, nodeToTable(meta)) | ||||
| 
 | ||||
| 	return details | ||||
| } | ||||
|  | @ -15,7 +15,6 @@ import ( | |||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	giteautil "code.gitea.io/gitea/modules/util" | ||||
| 
 | ||||
| 	meta "github.com/yuin/goldmark-meta" | ||||
| 	"github.com/yuin/goldmark/ast" | ||||
| 	east "github.com/yuin/goldmark/extension/ast" | ||||
| 	"github.com/yuin/goldmark/parser" | ||||
|  | @ -32,20 +31,12 @@ type ASTTransformer struct{} | |||
| 
 | ||||
| // Transform transforms the given AST tree. | ||||
| func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { | ||||
| 	metaData := meta.GetItems(pc) | ||||
| 	firstChild := node.FirstChild() | ||||
| 	createTOC := false | ||||
| 	ctx := pc.Get(renderContextKey).(*markup.RenderContext) | ||||
| 	rc := &RenderConfig{ | ||||
| 		Meta: "table", | ||||
| 		Icon: "table", | ||||
| 		Lang: "", | ||||
| 	} | ||||
| 
 | ||||
| 	if metaData != nil { | ||||
| 		rc.ToRenderConfig(metaData) | ||||
| 
 | ||||
| 		metaNode := rc.toMetaNode(metaData) | ||||
| 	rc := pc.Get(renderConfigKey).(*RenderConfig) | ||||
| 	if rc.yamlNode != nil { | ||||
| 		metaNode := rc.toMetaNode() | ||||
| 		if metaNode != nil { | ||||
| 			node.InsertBefore(node, firstChild, metaNode) | ||||
| 		} | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ import ( | |||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/markup" | ||||
| 	"code.gitea.io/gitea/modules/markup/common" | ||||
| 	"code.gitea.io/gitea/modules/markup/markdown/math" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	giteautil "code.gitea.io/gitea/modules/util" | ||||
| 
 | ||||
|  | @ -38,6 +39,7 @@ var ( | |||
| 	isWikiKey        = parser.NewContextKey() | ||||
| 	renderMetasKey   = parser.NewContextKey() | ||||
| 	renderContextKey = parser.NewContextKey() | ||||
| 	renderConfigKey  = parser.NewContextKey() | ||||
| ) | ||||
| 
 | ||||
| type limitWriter struct { | ||||
|  | @ -98,7 +100,7 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) | |||
| 							languageStr := string(language) | ||||
| 
 | ||||
| 							preClasses := []string{"code-block"} | ||||
| 							if languageStr == "mermaid" { | ||||
| 							if languageStr == "mermaid" || languageStr == "math" { | ||||
| 								preClasses = append(preClasses, "is-loading") | ||||
| 							} | ||||
| 
 | ||||
|  | @ -120,6 +122,9 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) | |||
| 						} | ||||
| 					}), | ||||
| 				), | ||||
| 				math.NewExtension( | ||||
| 					math.Enabled(setting.Markdown.EnableMath), | ||||
| 				), | ||||
| 				meta.Meta, | ||||
| 			), | ||||
| 			goldmark.WithParserOptions( | ||||
|  | @ -167,7 +172,18 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) | |||
| 		log.Error("Unable to ReadAll: %v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := converter.Convert(giteautil.NormalizeEOL(buf), lw, parser.WithContext(pc)); err != nil { | ||||
| 	buf = giteautil.NormalizeEOL(buf) | ||||
| 
 | ||||
| 	rc := &RenderConfig{ | ||||
| 		Meta: "table", | ||||
| 		Icon: "table", | ||||
| 		Lang: "", | ||||
| 	} | ||||
| 	buf, _ = ExtractMetadataBytes(buf, rc) | ||||
| 
 | ||||
| 	pc.Set(renderConfigKey, rc) | ||||
| 
 | ||||
| 	if err := converter.Convert(buf, lw, parser.WithContext(pc)); err != nil { | ||||
| 		log.Error("Unable to render: %v", err) | ||||
| 		return err | ||||
| 	} | ||||
|  |  | |||
							
								
								
									
										42
									
								
								modules/markup/markdown/math/block_node.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								modules/markup/markdown/math/block_node.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package math | ||||
| 
 | ||||
| import "github.com/yuin/goldmark/ast" | ||||
| 
 | ||||
| // Block represents a display math block e.g. $$...$$ or \[...\] | ||||
| type Block struct { | ||||
| 	ast.BaseBlock | ||||
| 	Dollars bool | ||||
| 	Indent  int | ||||
| 	Closed  bool | ||||
| } | ||||
| 
 | ||||
| // KindBlock is the node kind for math blocks | ||||
| var KindBlock = ast.NewNodeKind("MathBlock") | ||||
| 
 | ||||
| // NewBlock creates a new math Block | ||||
| func NewBlock(dollars bool, indent int) *Block { | ||||
| 	return &Block{ | ||||
| 		Dollars: dollars, | ||||
| 		Indent:  indent, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Dump dumps the block to a string | ||||
| func (n *Block) Dump(source []byte, level int) { | ||||
| 	m := map[string]string{} | ||||
| 	ast.DumpHelper(n, source, level, m, nil) | ||||
| } | ||||
| 
 | ||||
| // Kind returns KindBlock for math Blocks | ||||
| func (n *Block) Kind() ast.NodeKind { | ||||
| 	return KindBlock | ||||
| } | ||||
| 
 | ||||
| // IsRaw returns true as this block should not be processed further | ||||
| func (n *Block) IsRaw() bool { | ||||
| 	return true | ||||
| } | ||||
							
								
								
									
										123
									
								
								modules/markup/markdown/math/block_parser.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								modules/markup/markdown/math/block_parser.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,123 @@ | |||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package math | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 
 | ||||
| 	"github.com/yuin/goldmark/ast" | ||||
| 	"github.com/yuin/goldmark/parser" | ||||
| 	"github.com/yuin/goldmark/text" | ||||
| 	"github.com/yuin/goldmark/util" | ||||
| ) | ||||
| 
 | ||||
| type blockParser struct { | ||||
| 	parseDollars bool | ||||
| } | ||||
| 
 | ||||
| // NewBlockParser creates a new math BlockParser | ||||
| func NewBlockParser(parseDollarBlocks bool) parser.BlockParser { | ||||
| 	return &blockParser{ | ||||
| 		parseDollars: parseDollarBlocks, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Open parses the current line and returns a result of parsing. | ||||
| func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) { | ||||
| 	line, segment := reader.PeekLine() | ||||
| 	pos := pc.BlockOffset() | ||||
| 	if pos == -1 || len(line[pos:]) < 2 { | ||||
| 		return nil, parser.NoChildren | ||||
| 	} | ||||
| 
 | ||||
| 	dollars := false | ||||
| 	if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' { | ||||
| 		dollars = true | ||||
| 	} else if line[pos] != '\\' || line[pos+1] != '[' { | ||||
| 		return nil, parser.NoChildren | ||||
| 	} | ||||
| 
 | ||||
| 	node := NewBlock(dollars, pos) | ||||
| 
 | ||||
| 	// Now we need to check if the ending block is on the segment... | ||||
| 	endBytes := []byte{'\\', ']'} | ||||
| 	if dollars { | ||||
| 		endBytes = []byte{'$', '$'} | ||||
| 	} | ||||
| 	idx := bytes.Index(line[pos+2:], endBytes) | ||||
| 	if idx >= 0 { | ||||
| 		segment.Stop = segment.Start + idx + 2 | ||||
| 		reader.Advance(segment.Len() - 1) | ||||
| 		segment.Start += 2 | ||||
| 		node.Lines().Append(segment) | ||||
| 		node.Closed = true | ||||
| 		return node, parser.Close | parser.NoChildren | ||||
| 	} | ||||
| 
 | ||||
| 	reader.Advance(segment.Len() - 1) | ||||
| 	segment.Start += 2 | ||||
| 	node.Lines().Append(segment) | ||||
| 	return node, parser.NoChildren | ||||
| } | ||||
| 
 | ||||
| // Continue parses the current line and returns a result of parsing. | ||||
| func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State { | ||||
| 	block := node.(*Block) | ||||
| 	if block.Closed { | ||||
| 		return parser.Close | ||||
| 	} | ||||
| 
 | ||||
| 	line, segment := reader.PeekLine() | ||||
| 	w, pos := util.IndentWidth(line, 0) | ||||
| 	if w < 4 { | ||||
| 		if block.Dollars { | ||||
| 			i := pos | ||||
| 			for ; i < len(line) && line[i] == '$'; i++ { | ||||
| 			} | ||||
| 			length := i - pos | ||||
| 			if length >= 2 && util.IsBlank(line[i:]) { | ||||
| 				reader.Advance(segment.Stop - segment.Start - segment.Padding) | ||||
| 				block.Closed = true | ||||
| 				return parser.Close | ||||
| 			} | ||||
| 		} else if len(line[pos:]) > 1 && line[pos] == '\\' && line[pos+1] == ']' && util.IsBlank(line[pos+2:]) { | ||||
| 			reader.Advance(segment.Stop - segment.Start - segment.Padding) | ||||
| 			block.Closed = true | ||||
| 			return parser.Close | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pos, padding := util.IndentPosition(line, 0, block.Indent) | ||||
| 	seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding) | ||||
| 	node.Lines().Append(seg) | ||||
| 	reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding) | ||||
| 	return parser.Continue | parser.NoChildren | ||||
| } | ||||
| 
 | ||||
| // Close will be called when the parser returns Close. | ||||
| func (b *blockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) { | ||||
| 	// noop | ||||
| } | ||||
| 
 | ||||
| // CanInterruptParagraph returns true if the parser can interrupt paragraphs, | ||||
| // otherwise false. | ||||
| func (b *blockParser) CanInterruptParagraph() bool { | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // CanAcceptIndentedLine returns true if the parser can open new node when | ||||
| // the given line is being indented more than 3 spaces. | ||||
| func (b *blockParser) CanAcceptIndentedLine() bool { | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // Trigger returns a list of characters that triggers Parse method of | ||||
| // this parser. | ||||
| // If Trigger returns a nil, Open will be called with any lines. | ||||
| // | ||||
| // We leave this as nil as our parse method is quick enough | ||||
| func (b *blockParser) Trigger() []byte { | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										43
									
								
								modules/markup/markdown/math/block_renderer.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								modules/markup/markdown/math/block_renderer.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package math | ||||
| 
 | ||||
| import ( | ||||
| 	gast "github.com/yuin/goldmark/ast" | ||||
| 	"github.com/yuin/goldmark/renderer" | ||||
| 	"github.com/yuin/goldmark/util" | ||||
| ) | ||||
| 
 | ||||
| // BlockRenderer represents a renderer for math Blocks | ||||
| type BlockRenderer struct{} | ||||
| 
 | ||||
| // NewBlockRenderer creates a new renderer for math Blocks | ||||
| func NewBlockRenderer() renderer.NodeRenderer { | ||||
| 	return &BlockRenderer{} | ||||
| } | ||||
| 
 | ||||
| // RegisterFuncs registers the renderer for math Blocks | ||||
| func (r *BlockRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { | ||||
| 	reg.Register(KindBlock, r.renderBlock) | ||||
| } | ||||
| 
 | ||||
| func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node) { | ||||
| 	l := n.Lines().Len() | ||||
| 	for i := 0; i < l; i++ { | ||||
| 		line := n.Lines().At(i) | ||||
| 		_, _ = w.Write(util.EscapeHTML(line.Value(source))) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) { | ||||
| 	n := node.(*Block) | ||||
| 	if entering { | ||||
| 		_, _ = w.WriteString(`<pre class="code-block is-loading"><code class="chroma language-math display">`) | ||||
| 		r.writeLines(w, source, n) | ||||
| 	} else { | ||||
| 		_, _ = w.WriteString(`</code></pre>` + "\n") | ||||
| 	} | ||||
| 	return gast.WalkContinue, nil | ||||
| } | ||||
							
								
								
									
										49
									
								
								modules/markup/markdown/math/inline_node.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								modules/markup/markdown/math/inline_node.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | |||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package math | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/yuin/goldmark/ast" | ||||
| 	"github.com/yuin/goldmark/util" | ||||
| ) | ||||
| 
 | ||||
| // Inline represents inline math e.g. $...$ or \(...\) | ||||
| type Inline struct { | ||||
| 	ast.BaseInline | ||||
| } | ||||
| 
 | ||||
| // Inline implements Inline.Inline. | ||||
| func (n *Inline) Inline() {} | ||||
| 
 | ||||
| // IsBlank returns if this inline node is empty | ||||
| func (n *Inline) IsBlank(source []byte) bool { | ||||
| 	for c := n.FirstChild(); c != nil; c = c.NextSibling() { | ||||
| 		text := c.(*ast.Text).Segment | ||||
| 		if !util.IsBlank(text.Value(source)) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // Dump renders this inline math as debug | ||||
| func (n *Inline) Dump(source []byte, level int) { | ||||
| 	ast.DumpHelper(n, source, level, nil, nil) | ||||
| } | ||||
| 
 | ||||
| // KindInline is the kind for math inline | ||||
| var KindInline = ast.NewNodeKind("MathInline") | ||||
| 
 | ||||
| // Kind returns KindInline | ||||
| func (n *Inline) Kind() ast.NodeKind { | ||||
| 	return KindInline | ||||
| } | ||||
| 
 | ||||
| // NewInline creates a new ast math inline node | ||||
| func NewInline() *Inline { | ||||
| 	return &Inline{ | ||||
| 		BaseInline: ast.BaseInline{}, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										99
									
								
								modules/markup/markdown/math/inline_parser.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								modules/markup/markdown/math/inline_parser.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,99 @@ | |||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package math | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 
 | ||||
| 	"github.com/yuin/goldmark/ast" | ||||
| 	"github.com/yuin/goldmark/parser" | ||||
| 	"github.com/yuin/goldmark/text" | ||||
| ) | ||||
| 
 | ||||
| type inlineParser struct { | ||||
| 	start []byte | ||||
| 	end   []byte | ||||
| } | ||||
| 
 | ||||
| var defaultInlineDollarParser = &inlineParser{ | ||||
| 	start: []byte{'$'}, | ||||
| 	end:   []byte{'$'}, | ||||
| } | ||||
| 
 | ||||
| // NewInlineDollarParser returns a new inline parser | ||||
| func NewInlineDollarParser() parser.InlineParser { | ||||
| 	return defaultInlineDollarParser | ||||
| } | ||||
| 
 | ||||
| var defaultInlineBracketParser = &inlineParser{ | ||||
| 	start: []byte{'\\', '('}, | ||||
| 	end:   []byte{'\\', ')'}, | ||||
| } | ||||
| 
 | ||||
| // NewInlineDollarParser returns a new inline parser | ||||
| func NewInlineBracketParser() parser.InlineParser { | ||||
| 	return defaultInlineBracketParser | ||||
| } | ||||
| 
 | ||||
| // Trigger triggers this parser on $ | ||||
| func (parser *inlineParser) Trigger() []byte { | ||||
| 	return parser.start[0:1] | ||||
| } | ||||
| 
 | ||||
| func isAlphanumeric(b byte) bool { | ||||
| 	// Github only cares about 0-9A-Za-z | ||||
| 	return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') | ||||
| } | ||||
| 
 | ||||
| // Parse parses the current line and returns a result of parsing. | ||||
| func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node { | ||||
| 	line, _ := block.PeekLine() | ||||
| 	opener := bytes.Index(line, parser.start) | ||||
| 	if opener < 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if opener != 0 && isAlphanumeric(line[opener-1]) { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	opener += len(parser.start) | ||||
| 	ender := bytes.Index(line[opener:], parser.end) | ||||
| 	if ender < 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if len(line) > opener+ender+len(parser.end) && isAlphanumeric(line[opener+ender+len(parser.end)]) { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	block.Advance(opener) | ||||
| 	_, pos := block.Position() | ||||
| 	node := NewInline() | ||||
| 	segment := pos.WithStop(pos.Start + ender) | ||||
| 	node.AppendChild(node, ast.NewRawTextSegment(segment)) | ||||
| 	block.Advance(ender + len(parser.end)) | ||||
| 
 | ||||
| 	trimBlock(node, block) | ||||
| 	return node | ||||
| } | ||||
| 
 | ||||
| func trimBlock(node *Inline, block text.Reader) { | ||||
| 	if node.IsBlank(block.Source()) { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// trim first space and last space | ||||
| 	first := node.FirstChild().(*ast.Text) | ||||
| 	if !(!first.Segment.IsEmpty() && block.Source()[first.Segment.Start] == ' ') { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	last := node.LastChild().(*ast.Text) | ||||
| 	if !(!last.Segment.IsEmpty() && block.Source()[last.Segment.Stop-1] == ' ') { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	first.Segment = first.Segment.WithStart(first.Segment.Start + 1) | ||||
| 	last.Segment = last.Segment.WithStop(last.Segment.Stop - 1) | ||||
| } | ||||
							
								
								
									
										47
									
								
								modules/markup/markdown/math/inline_renderer.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								modules/markup/markdown/math/inline_renderer.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package math | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 
 | ||||
| 	"github.com/yuin/goldmark/ast" | ||||
| 	"github.com/yuin/goldmark/renderer" | ||||
| 	"github.com/yuin/goldmark/util" | ||||
| ) | ||||
| 
 | ||||
| // InlineRenderer is an inline renderer | ||||
| type InlineRenderer struct{} | ||||
| 
 | ||||
| // NewInlineRenderer returns a new renderer for inline math | ||||
| func NewInlineRenderer() renderer.NodeRenderer { | ||||
| 	return &InlineRenderer{} | ||||
| } | ||||
| 
 | ||||
| func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { | ||||
| 	if entering { | ||||
| 		_, _ = w.WriteString(`<code class="language-math is-loading">`) | ||||
| 		for c := n.FirstChild(); c != nil; c = c.NextSibling() { | ||||
| 			segment := c.(*ast.Text).Segment | ||||
| 			value := util.EscapeHTML(segment.Value(source)) | ||||
| 			if bytes.HasSuffix(value, []byte("\n")) { | ||||
| 				_, _ = w.Write(value[:len(value)-1]) | ||||
| 				if c != n.LastChild() { | ||||
| 					_, _ = w.Write([]byte(" ")) | ||||
| 				} | ||||
| 			} else { | ||||
| 				_, _ = w.Write(value) | ||||
| 			} | ||||
| 		} | ||||
| 		return ast.WalkSkipChildren, nil | ||||
| 	} | ||||
| 	_, _ = w.WriteString(`</code>`) | ||||
| 	return ast.WalkContinue, nil | ||||
| } | ||||
| 
 | ||||
| // RegisterFuncs registers the renderer for inline math nodes | ||||
| func (r *InlineRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { | ||||
| 	reg.Register(KindInline, r.renderInline) | ||||
| } | ||||
							
								
								
									
										108
									
								
								modules/markup/markdown/math/math.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								modules/markup/markdown/math/math.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | |||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package math | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/yuin/goldmark" | ||||
| 	"github.com/yuin/goldmark/parser" | ||||
| 	"github.com/yuin/goldmark/renderer" | ||||
| 	"github.com/yuin/goldmark/util" | ||||
| ) | ||||
| 
 | ||||
| // Extension is a math extension | ||||
| type Extension struct { | ||||
| 	enabled           bool | ||||
| 	parseDollarInline bool | ||||
| 	parseDollarBlock  bool | ||||
| } | ||||
| 
 | ||||
| // Option is the interface Options should implement | ||||
| type Option interface { | ||||
| 	SetOption(e *Extension) | ||||
| } | ||||
| 
 | ||||
| type extensionFunc func(e *Extension) | ||||
| 
 | ||||
| func (fn extensionFunc) SetOption(e *Extension) { | ||||
| 	fn(e) | ||||
| } | ||||
| 
 | ||||
| // Enabled enables or disables this extension | ||||
| func Enabled(enable ...bool) Option { | ||||
| 	value := true | ||||
| 	if len(enable) > 0 { | ||||
| 		value = enable[0] | ||||
| 	} | ||||
| 	return extensionFunc(func(e *Extension) { | ||||
| 		e.enabled = value | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // WithInlineDollarParser enables or disables the parsing of $...$ | ||||
| func WithInlineDollarParser(enable ...bool) Option { | ||||
| 	value := true | ||||
| 	if len(enable) > 0 { | ||||
| 		value = enable[0] | ||||
| 	} | ||||
| 	return extensionFunc(func(e *Extension) { | ||||
| 		e.parseDollarInline = value | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // WithBlockDollarParser enables or disables the parsing of $$...$$ | ||||
| func WithBlockDollarParser(enable ...bool) Option { | ||||
| 	value := true | ||||
| 	if len(enable) > 0 { | ||||
| 		value = enable[0] | ||||
| 	} | ||||
| 	return extensionFunc(func(e *Extension) { | ||||
| 		e.parseDollarBlock = value | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // Math represents a math extension with default rendered delimiters | ||||
| var Math = &Extension{ | ||||
| 	enabled:           true, | ||||
| 	parseDollarBlock:  true, | ||||
| 	parseDollarInline: true, | ||||
| } | ||||
| 
 | ||||
| // NewExtension creates a new math extension with the provided options | ||||
| func NewExtension(opts ...Option) *Extension { | ||||
| 	r := &Extension{ | ||||
| 		enabled:           true, | ||||
| 		parseDollarBlock:  true, | ||||
| 		parseDollarInline: true, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, o := range opts { | ||||
| 		o.SetOption(r) | ||||
| 	} | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // Extend extends goldmark with our parsers and renderers | ||||
| func (e *Extension) Extend(m goldmark.Markdown) { | ||||
| 	if !e.enabled { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	m.Parser().AddOptions(parser.WithBlockParsers( | ||||
| 		util.Prioritized(NewBlockParser(e.parseDollarBlock), 701), | ||||
| 	)) | ||||
| 
 | ||||
| 	inlines := []util.PrioritizedValue{ | ||||
| 		util.Prioritized(NewInlineBracketParser(), 501), | ||||
| 	} | ||||
| 	if e.parseDollarInline { | ||||
| 		inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 501)) | ||||
| 	} | ||||
| 	m.Parser().AddOptions(parser.WithInlineParsers(inlines...)) | ||||
| 
 | ||||
| 	m.Renderer().AddOptions(renderer.WithNodeRenderers( | ||||
| 		util.Prioritized(NewBlockRenderer(), 501), | ||||
| 		util.Prioritized(NewInlineRenderer(), 502), | ||||
| 	)) | ||||
| } | ||||
|  | @ -5,47 +5,101 @@ | |||
| package markdown | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"strings" | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
| 
 | ||||
| 	"gopkg.in/yaml.v2" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"gopkg.in/yaml.v3" | ||||
| ) | ||||
| 
 | ||||
| func isYAMLSeparator(line string) bool { | ||||
| 	line = strings.TrimSpace(line) | ||||
| 	for i := 0; i < len(line); i++ { | ||||
| 		if line[i] != '-' { | ||||
| func isYAMLSeparator(line []byte) bool { | ||||
| 	idx := 0 | ||||
| 	for ; idx < len(line); idx++ { | ||||
| 		if line[idx] >= utf8.RuneSelf { | ||||
| 			r, sz := utf8.DecodeRune(line[idx:]) | ||||
| 			if !unicode.IsSpace(r) { | ||||
| 				return false | ||||
| 			} | ||||
| 			idx += sz | ||||
| 			continue | ||||
| 		} | ||||
| 		if line[idx] != ' ' { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	dashCount := 0 | ||||
| 	for ; idx < len(line); idx++ { | ||||
| 		if line[idx] != '-' { | ||||
| 			break | ||||
| 		} | ||||
| 		dashCount++ | ||||
| 	} | ||||
| 	if dashCount < 3 { | ||||
| 		return false | ||||
| 	} | ||||
| 	for ; idx < len(line); idx++ { | ||||
| 		if line[idx] >= utf8.RuneSelf { | ||||
| 			r, sz := utf8.DecodeRune(line[idx:]) | ||||
| 			if !unicode.IsSpace(r) { | ||||
| 				return false | ||||
| 			} | ||||
| 			idx += sz | ||||
| 			continue | ||||
| 		} | ||||
| 		if line[idx] != ' ' { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return len(line) > 2 | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // ExtractMetadata consumes a markdown file, parses YAML frontmatter, | ||||
| // and returns the frontmatter metadata separated from the markdown content | ||||
| func ExtractMetadata(contents string, out interface{}) (string, error) { | ||||
| 	var front, body []string | ||||
| 	lines := strings.Split(contents, "\n") | ||||
| 	for idx, line := range lines { | ||||
| 		if idx == 0 { | ||||
| 			// First line has to be a separator | ||||
| 	body, err := ExtractMetadataBytes([]byte(contents), out) | ||||
| 	return string(body), err | ||||
| } | ||||
| 
 | ||||
| // ExtractMetadata consumes a markdown file, parses YAML frontmatter, | ||||
| // and returns the frontmatter metadata separated from the markdown content | ||||
| func ExtractMetadataBytes(contents []byte, out interface{}) ([]byte, error) { | ||||
| 	var front, body []byte | ||||
| 
 | ||||
| 	start, end := 0, len(contents) | ||||
| 	idx := bytes.IndexByte(contents[start:], '\n') | ||||
| 	if idx >= 0 { | ||||
| 		end = start + idx | ||||
| 	} | ||||
| 	line := contents[start:end] | ||||
| 
 | ||||
| 	if !isYAMLSeparator(line) { | ||||
| 				return "", errors.New("frontmatter must start with a separator line") | ||||
| 		return contents, errors.New("frontmatter must start with a separator line") | ||||
| 	} | ||||
| 			continue | ||||
| 	frontMatterStart := end + 1 | ||||
| 	for start = frontMatterStart; start < len(contents); start = end + 1 { | ||||
| 		end = len(contents) | ||||
| 		idx := bytes.IndexByte(contents[start:], '\n') | ||||
| 		if idx >= 0 { | ||||
| 			end = start + idx | ||||
| 		} | ||||
| 		line := contents[start:end] | ||||
| 		if isYAMLSeparator(line) { | ||||
| 			front, body = lines[1:idx], lines[idx+1:] | ||||
| 			front = contents[frontMatterStart:start] | ||||
| 			body = contents[end+1:] | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if len(front) == 0 { | ||||
| 		return "", errors.New("could not determine metadata") | ||||
| 		return contents, errors.New("could not determine metadata") | ||||
| 	} | ||||
| 
 | ||||
| 	if err := yaml.Unmarshal([]byte(strings.Join(front, "\n")), out); err != nil { | ||||
| 		return "", err | ||||
| 	log.Info("%s", string(front)) | ||||
| 
 | ||||
| 	if err := yaml.Unmarshal(front, out); err != nil { | ||||
| 		return contents, err | ||||
| 	} | ||||
| 	return strings.Join(body, "\n"), nil | ||||
| 	return body, nil | ||||
| } | ||||
|  |  | |||
|  | @ -56,6 +56,38 @@ func TestExtractMetadata(t *testing.T) { | |||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func TestExtractMetadataBytes(t *testing.T) { | ||||
| 	t.Run("ValidFrontAndBody", func(t *testing.T) { | ||||
| 		var meta structs.IssueTemplate | ||||
| 		body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)), &meta) | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, bodyTest, body) | ||||
| 		assert.Equal(t, metaTest, meta) | ||||
| 		assert.True(t, validateMetadata(meta)) | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("NoFirstSeparator", func(t *testing.T) { | ||||
| 		var meta structs.IssueTemplate | ||||
| 		_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest)), &meta) | ||||
| 		assert.Error(t, err) | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("NoLastSeparator", func(t *testing.T) { | ||||
| 		var meta structs.IssueTemplate | ||||
| 		_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest)), &meta) | ||||
| 		assert.Error(t, err) | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("NoBody", func(t *testing.T) { | ||||
| 		var meta structs.IssueTemplate | ||||
| 		body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta) | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, "", body) | ||||
| 		assert.Equal(t, metaTest, meta) | ||||
| 		assert.True(t, validateMetadata(meta)) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
| 	sepTest   = "-----" | ||||
| 	frontTest = `name: Test | ||||
|  |  | |||
|  | @ -5,12 +5,11 @@ | |||
| package markdown | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"github.com/yuin/goldmark/ast" | ||||
| 	east "github.com/yuin/goldmark/extension/ast" | ||||
| 	"gopkg.in/yaml.v2" | ||||
| 	"gopkg.in/yaml.v3" | ||||
| ) | ||||
| 
 | ||||
| // RenderConfig represents rendering configuration for this file | ||||
|  | @ -19,48 +18,46 @@ type RenderConfig struct { | |||
| 	Icon     string | ||||
| 	TOC      bool | ||||
| 	Lang     string | ||||
| 	yamlNode *yaml.Node | ||||
| } | ||||
| 
 | ||||
| // ToRenderConfig converts a yaml.MapSlice to a RenderConfig | ||||
| func (rc *RenderConfig) ToRenderConfig(meta yaml.MapSlice) { | ||||
| 	if meta == nil { | ||||
| 		return | ||||
| // UnmarshalYAML implement yaml.v3 UnmarshalYAML | ||||
| func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error { | ||||
| 	if rc == nil { | ||||
| 		rc = &RenderConfig{ | ||||
| 			Meta: "table", | ||||
| 			Icon: "table", | ||||
| 			Lang: "", | ||||
| 		} | ||||
| 	found := false | ||||
| 	var giteaMetaControl yaml.MapItem | ||||
| 	for _, item := range meta { | ||||
| 		strKey, ok := item.Key.(string) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 		strKey = strings.TrimSpace(strings.ToLower(strKey)) | ||||
| 		switch strKey { | ||||
| 		case "gitea": | ||||
| 			giteaMetaControl = item | ||||
| 			found = true | ||||
| 		case "include_toc": | ||||
| 			val, ok := item.Value.(bool) | ||||
| 			if !ok { | ||||
| 				continue | ||||
| 			} | ||||
| 			rc.TOC = val | ||||
| 		case "lang": | ||||
| 			val, ok := item.Value.(string) | ||||
| 			if !ok { | ||||
| 				continue | ||||
| 			} | ||||
| 			val = strings.TrimSpace(val) | ||||
| 			if len(val) == 0 { | ||||
| 				continue | ||||
| 			} | ||||
| 			rc.Lang = val | ||||
| 	} | ||||
| 	rc.yamlNode = value | ||||
| 
 | ||||
| 	type basicRenderConfig struct { | ||||
| 		Gitea *yaml.Node `yaml:"gitea"` | ||||
| 		TOC   bool       `yaml:"include_toc"` | ||||
| 		Lang  string     `yaml:"lang"` | ||||
| 	} | ||||
| 
 | ||||
| 	if found { | ||||
| 		switch v := giteaMetaControl.Value.(type) { | ||||
| 		case string: | ||||
| 			switch v { | ||||
| 	var basic basicRenderConfig | ||||
| 
 | ||||
| 	err := value.Decode(&basic) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if basic.Lang != "" { | ||||
| 		rc.Lang = basic.Lang | ||||
| 	} | ||||
| 
 | ||||
| 	rc.TOC = basic.TOC | ||||
| 	if basic.Gitea == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	var control *string | ||||
| 	if err := basic.Gitea.Decode(&control); err == nil && control != nil { | ||||
| 		log.Info("control %v", control) | ||||
| 		switch strings.TrimSpace(strings.ToLower(*control)) { | ||||
| 		case "none": | ||||
| 			rc.Meta = "none" | ||||
| 		case "table": | ||||
|  | @ -68,20 +65,22 @@ func (rc *RenderConfig) ToRenderConfig(meta yaml.MapSlice) { | |||
| 		default: // "details" | ||||
| 			rc.Meta = "details" | ||||
| 		} | ||||
| 		case yaml.MapSlice: | ||||
| 			for _, item := range v { | ||||
| 				strKey, ok := item.Key.(string) | ||||
| 				if !ok { | ||||
| 					continue | ||||
| 		return nil | ||||
| 	} | ||||
| 				strKey = strings.TrimSpace(strings.ToLower(strKey)) | ||||
| 				switch strKey { | ||||
| 				case "meta": | ||||
| 					val, ok := item.Value.(string) | ||||
| 					if !ok { | ||||
| 						continue | ||||
| 
 | ||||
| 	type giteaControl struct { | ||||
| 		Meta string     `yaml:"meta"` | ||||
| 		Icon string     `yaml:"details_icon"` | ||||
| 		TOC  *yaml.Node `yaml:"include_toc"` | ||||
| 		Lang string     `yaml:"lang"` | ||||
| 	} | ||||
| 					switch strings.TrimSpace(strings.ToLower(val)) { | ||||
| 
 | ||||
| 	var controlStruct *giteaControl | ||||
| 	if err := basic.Gitea.Decode(controlStruct); err != nil || controlStruct == nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	switch strings.TrimSpace(strings.ToLower(controlStruct.Meta)) { | ||||
| 	case "none": | ||||
| 		rc.Meta = "none" | ||||
| 	case "table": | ||||
|  | @ -89,75 +88,31 @@ func (rc *RenderConfig) ToRenderConfig(meta yaml.MapSlice) { | |||
| 	default: // "details" | ||||
| 		rc.Meta = "details" | ||||
| 	} | ||||
| 				case "details_icon": | ||||
| 					val, ok := item.Value.(string) | ||||
| 					if !ok { | ||||
| 						continue | ||||
| 					} | ||||
| 					rc.Icon = strings.TrimSpace(strings.ToLower(val)) | ||||
| 				case "include_toc": | ||||
| 					val, ok := item.Value.(bool) | ||||
| 					if !ok { | ||||
| 						continue | ||||
| 					} | ||||
| 					rc.TOC = val | ||||
| 				case "lang": | ||||
| 					val, ok := item.Value.(string) | ||||
| 					if !ok { | ||||
| 						continue | ||||
| 					} | ||||
| 					val = strings.TrimSpace(val) | ||||
| 					if len(val) == 0 { | ||||
| 						continue | ||||
| 					} | ||||
| 					rc.Lang = val | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 	rc.Icon = strings.TrimSpace(strings.ToLower(controlStruct.Icon)) | ||||
| 
 | ||||
| 	if controlStruct.Lang != "" { | ||||
| 		rc.Lang = controlStruct.Lang | ||||
| 	} | ||||
| 
 | ||||
| 	var toc bool | ||||
| 	if err := controlStruct.TOC.Decode(&toc); err == nil { | ||||
| 		rc.TOC = toc | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (rc *RenderConfig) toMetaNode(meta yaml.MapSlice) ast.Node { | ||||
| func (rc *RenderConfig) toMetaNode() ast.Node { | ||||
| 	if rc.yamlNode == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	switch rc.Meta { | ||||
| 	case "table": | ||||
| 		return metaToTable(meta) | ||||
| 		return nodeToTable(rc.yamlNode) | ||||
| 	case "details": | ||||
| 		return metaToDetails(meta, rc.Icon) | ||||
| 		return nodeToDetails(rc.yamlNode, rc.Icon) | ||||
| 	default: | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func metaToTable(meta yaml.MapSlice) ast.Node { | ||||
| 	table := east.NewTable() | ||||
| 	alignments := []east.Alignment{} | ||||
| 	for range meta { | ||||
| 		alignments = append(alignments, east.AlignNone) | ||||
| 	} | ||||
| 	row := east.NewTableRow(alignments) | ||||
| 	for _, item := range meta { | ||||
| 		cell := east.NewTableCell() | ||||
| 		cell.AppendChild(cell, ast.NewString([]byte(fmt.Sprintf("%v", item.Key)))) | ||||
| 		row.AppendChild(row, cell) | ||||
| 	} | ||||
| 	table.AppendChild(table, east.NewTableHeader(row)) | ||||
| 
 | ||||
| 	row = east.NewTableRow(alignments) | ||||
| 	for _, item := range meta { | ||||
| 		cell := east.NewTableCell() | ||||
| 		cell.AppendChild(cell, ast.NewString([]byte(fmt.Sprintf("%v", item.Value)))) | ||||
| 		row.AppendChild(row, cell) | ||||
| 	} | ||||
| 	table.AppendChild(table, row) | ||||
| 	return table | ||||
| } | ||||
| 
 | ||||
| func metaToDetails(meta yaml.MapSlice, icon string) ast.Node { | ||||
| 	details := NewDetails() | ||||
| 	summary := NewSummary() | ||||
| 	summary.AppendChild(summary, NewIcon(icon)) | ||||
| 	details.AppendChild(details, summary) | ||||
| 	details.AppendChild(details, metaToTable(meta)) | ||||
| 
 | ||||
| 	return details | ||||
| } | ||||
|  |  | |||
							
								
								
									
										162
									
								
								modules/markup/markdown/renderconfig_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								modules/markup/markdown/renderconfig_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,162 @@ | |||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package markdown | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"gopkg.in/yaml.v3" | ||||
| ) | ||||
| 
 | ||||
| func TestRenderConfig_UnmarshalYAML(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name     string | ||||
| 		expected *RenderConfig | ||||
| 		args     string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			"empty", &RenderConfig{ | ||||
| 				Meta: "table", | ||||
| 				Icon: "table", | ||||
| 				Lang: "", | ||||
| 			}, "", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"lang", &RenderConfig{ | ||||
| 				Meta: "table", | ||||
| 				Icon: "table", | ||||
| 				Lang: "test", | ||||
| 			}, "lang: test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"metatable", &RenderConfig{ | ||||
| 				Meta: "table", | ||||
| 				Icon: "table", | ||||
| 				Lang: "", | ||||
| 			}, "gitea: table", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"metanone", &RenderConfig{ | ||||
| 				Meta: "none", | ||||
| 				Icon: "table", | ||||
| 				Lang: "", | ||||
| 			}, "gitea: none", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"metadetails", &RenderConfig{ | ||||
| 				Meta: "details", | ||||
| 				Icon: "table", | ||||
| 				Lang: "", | ||||
| 			}, "gitea: details", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"metawrong", &RenderConfig{ | ||||
| 				Meta: "details", | ||||
| 				Icon: "table", | ||||
| 				Lang: "", | ||||
| 			}, "gitea: wrong", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"toc", &RenderConfig{ | ||||
| 				TOC:  true, | ||||
| 				Meta: "table", | ||||
| 				Icon: "table", | ||||
| 				Lang: "", | ||||
| 			}, "include_toc: true", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"tocfalse", &RenderConfig{ | ||||
| 				TOC:  false, | ||||
| 				Meta: "table", | ||||
| 				Icon: "table", | ||||
| 				Lang: "", | ||||
| 			}, "include_toc: false", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"toclang", &RenderConfig{ | ||||
| 				Meta: "table", | ||||
| 				Icon: "table", | ||||
| 				TOC:  true, | ||||
| 				Lang: "testlang", | ||||
| 			}, ` | ||||
| 	include_toc: true | ||||
| 	lang: testlang | ||||
| `, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"complexlang", &RenderConfig{ | ||||
| 				Meta: "table", | ||||
| 				Icon: "table", | ||||
| 				Lang: "testlang", | ||||
| 			}, ` | ||||
| 	gitea: | ||||
| 		lang: testlang | ||||
| `, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"complexlang2", &RenderConfig{ | ||||
| 				Meta: "table", | ||||
| 				Icon: "table", | ||||
| 				Lang: "testlang", | ||||
| 			}, ` | ||||
| 	lang: notright | ||||
| 	gitea: | ||||
| 		lang: testlang | ||||
| `, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"complexlang", &RenderConfig{ | ||||
| 				Meta: "table", | ||||
| 				Icon: "table", | ||||
| 				Lang: "testlang", | ||||
| 			}, ` | ||||
| 	gitea: | ||||
| 		lang: testlang | ||||
| `, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"complex2", &RenderConfig{ | ||||
| 				Lang: "two", | ||||
| 				Meta: "table", | ||||
| 				TOC:  true, | ||||
| 				Icon: "smiley", | ||||
| 			}, ` | ||||
| 	lang: one | ||||
| 	include_toc: true | ||||
| 	gitea: | ||||
| 		details_icon: smiley | ||||
| 		meta: table | ||||
| 		include_toc: true | ||||
| 		lang: two | ||||
| `, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			got := &RenderConfig{ | ||||
| 				Meta: "table", | ||||
| 				Icon: "table", | ||||
| 				Lang: "", | ||||
| 			} | ||||
| 			if err := yaml.Unmarshal([]byte(tt.args), got); err != nil { | ||||
| 				t.Errorf("RenderConfig.UnmarshalYAML() error = %v", err) | ||||
| 				return | ||||
| 			} | ||||
| 
 | ||||
| 			if got.Meta != tt.expected.Meta { | ||||
| 				t.Errorf("Meta Expected %s Got %s", tt.expected.Meta, got.Meta) | ||||
| 			} | ||||
| 			if got.Icon != tt.expected.Icon { | ||||
| 				t.Errorf("Icon Expected %s Got %s", tt.expected.Icon, got.Icon) | ||||
| 			} | ||||
| 			if got.Lang != tt.expected.Lang { | ||||
| 				t.Errorf("Lang Expected %s Got %s", tt.expected.Lang, got.Lang) | ||||
| 			} | ||||
| 			if got.TOC != tt.expected.TOC { | ||||
| 				t.Errorf("TOC Expected %t Got %t", tt.expected.TOC, got.TOC) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -56,7 +56,7 @@ func createDefaultPolicy() *bluemonday.Policy { | |||
| 	policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-block( is-loading)?$`)).OnElements("pre") | ||||
| 
 | ||||
| 	// For Chroma markdown plugin | ||||
| 	policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(chroma )?language-[\w-]+$`)).OnElements("code") | ||||
| 	policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(chroma )?language-[\w-]+( display)?( is-loading)?$`)).OnElements("code") | ||||
| 
 | ||||
| 	// Checkboxes | ||||
| 	policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input") | ||||
|  | @ -83,7 +83,7 @@ func createDefaultPolicy() *bluemonday.Policy { | |||
| 	policy.AllowAttrs("class").Matching(regexp.MustCompile(`emoji`)).OnElements("img") | ||||
| 
 | ||||
| 	// Allow icons, emojis, chroma syntax and keyword markup on span | ||||
| 	policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(emoji))$|^([a-z][a-z0-9]{0,2})$|^` + keywordClass + `$`)).OnElements("span") | ||||
| 	policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(emoji)|(language-math display)|(language-math inline))$|^([a-z][a-z0-9]{0,2})$|^` + keywordClass + `$`)).OnElements("span") | ||||
| 
 | ||||
| 	// Allow 'style' attribute on text elements. | ||||
| 	policy.AllowAttrs("style").OnElements("span", "p") | ||||
|  |  | |||
|  | @ -344,10 +344,12 @@ var ( | |||
| 		EnableHardLineBreakInDocuments bool | ||||
| 		CustomURLSchemes               []string `ini:"CUSTOM_URL_SCHEMES"` | ||||
| 		FileExtensions                 []string | ||||
| 		EnableMath                     bool | ||||
| 	}{ | ||||
| 		EnableHardLineBreakInComments:  true, | ||||
| 		EnableHardLineBreakInDocuments: false, | ||||
| 		FileExtensions:                 strings.Split(".md,.markdown,.mdown,.mkd", ","), | ||||
| 		EnableMath:                     true, | ||||
| 	} | ||||
| 
 | ||||
| 	// Admin settings | ||||
|  |  | |||
|  | @ -3092,6 +3092,7 @@ container.details.platform = Platform | |||
| container.details.repository_site = Repository Site | ||||
| container.details.documentation_site = Documentation Site | ||||
| container.pull = Pull the image from the command line: | ||||
| container.digest = Digest: | ||||
| container.documentation = For more information on the Container registry, see <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/container/">the documentation</a>. | ||||
| container.multi_arch = OS / Arch | ||||
| container.layers = Image Layers | ||||
|  |  | |||
							
								
								
									
										39
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										39
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -20,6 +20,7 @@ | |||
|         "font-awesome": "4.7.0", | ||||
|         "jquery": "3.6.1", | ||||
|         "jquery.are-you-sure": "1.9.0", | ||||
|         "katex": "0.16.2", | ||||
|         "less": "4.1.3", | ||||
|         "less-loader": "11.0.0", | ||||
|         "license-checker-webpack-plugin": "0.2.1", | ||||
|  | @ -7750,6 +7751,29 @@ | |||
|       "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-5.1.1.tgz", | ||||
|       "integrity": "sha512-b+z6yF1d4EOyDgylzQo5IminlUmzSeqR1hs/bzjBNjuGras4FXq/6TrzjxfN0j+TmI0ltJzTNlqXUMCniciwKQ==" | ||||
|     }, | ||||
|     "node_modules/katex": { | ||||
|       "version": "0.16.2", | ||||
|       "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.2.tgz", | ||||
|       "integrity": "sha512-70DJdQAyh9EMsthw3AaQlDyFf54X7nWEUIa5W+rq8XOpEk//w5Th7/8SqFqpvi/KZ2t6MHUj4f9wLmztBmAYQA==", | ||||
|       "funding": [ | ||||
|         "https://opencollective.com/katex", | ||||
|         "https://github.com/sponsors/katex" | ||||
|       ], | ||||
|       "dependencies": { | ||||
|         "commander": "^8.0.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "katex": "cli.js" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/katex/node_modules/commander": { | ||||
|       "version": "8.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", | ||||
|       "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", | ||||
|       "engines": { | ||||
|         "node": ">= 12" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/khroma": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.0.0.tgz", | ||||
|  | @ -17717,6 +17741,21 @@ | |||
|       "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-5.1.1.tgz", | ||||
|       "integrity": "sha512-b+z6yF1d4EOyDgylzQo5IminlUmzSeqR1hs/bzjBNjuGras4FXq/6TrzjxfN0j+TmI0ltJzTNlqXUMCniciwKQ==" | ||||
|     }, | ||||
|     "katex": { | ||||
|       "version": "0.16.2", | ||||
|       "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.2.tgz", | ||||
|       "integrity": "sha512-70DJdQAyh9EMsthw3AaQlDyFf54X7nWEUIa5W+rq8XOpEk//w5Th7/8SqFqpvi/KZ2t6MHUj4f9wLmztBmAYQA==", | ||||
|       "requires": { | ||||
|         "commander": "^8.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "commander": { | ||||
|           "version": "8.3.0", | ||||
|           "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", | ||||
|           "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "khroma": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.0.0.tgz", | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ | |||
|     "font-awesome": "4.7.0", | ||||
|     "jquery": "3.6.1", | ||||
|     "jquery.are-you-sure": "1.9.0", | ||||
|     "katex": "0.16.2", | ||||
|     "less": "4.1.3", | ||||
|     "less-loader": "11.0.0", | ||||
|     "license-checker-webpack-plugin": "0.2.1", | ||||
|  |  | |||
|  | @ -427,5 +427,5 @@ func CreateBranch(ctx *context.Context) { | |||
| 	} | ||||
| 
 | ||||
| 	ctx.Flash.Success(ctx.Tr("repo.branch.create_success", form.NewBranchName)) | ||||
| 	ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(form.NewBranchName)) | ||||
| 	ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(form.NewBranchName) + "/" + util.PathEscapeSegments(form.CurrentPath)) | ||||
| } | ||||
|  |  | |||
|  | @ -375,7 +375,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st | |||
| 	ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) | ||||
| 
 | ||||
| 	if ctx.Repo.TreePath == ".editorconfig" { | ||||
| 		_, editorconfigErr := ctx.Repo.GetEditorconfig() | ||||
| 		_, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit) | ||||
| 		ctx.Data["FileError"] = editorconfigErr | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ import ( | |||
| // NewBranchForm form for creating a new branch | ||||
| type NewBranchForm struct { | ||||
| 	NewBranchName string `binding:"Required;MaxSize(100);GitRefName"` | ||||
| 	CurrentPath   string | ||||
| 	CreateTag     bool | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -151,7 +151,7 @@ | |||
| 
 | ||||
| 				<div class="field"> | ||||
| 					<button class="ui green button">{{.locale.Tr "admin.users.update_profile"}}</button> | ||||
| 					<div class="ui red button show-modal" data-modal="#delete-user-modal" data-url="{{$.Link}}/delete" data-id="{{.User.ID}}">{{.locale.Tr "admin.users.delete_account"}}</div> | ||||
| 					<div class="ui red button show-modal" data-modal="#delete-user-modal">{{.locale.Tr "admin.users.delete_account"}}</div> | ||||
| 				</div> | ||||
| 			</form> | ||||
| 		</div> | ||||
|  | @ -206,7 +206,6 @@ | |||
| 	</div> | ||||
| 	<form class="ui form" method="POST" action="{{.Link}}/delete"> | ||||
| 		{{$.CsrfTokenHtml}} | ||||
| 		<input type="hidden" name="id"> | ||||
| 		<div class="field"> | ||||
| 			<div class="ui checkbox"> | ||||
| 				<label for="purge">{{.locale.Tr "admin.users.purge"}}</label> | ||||
|  | @ -214,7 +213,16 @@ | |||
| 			</div> | ||||
| 			<p class="help">{{.locale.Tr "admin.users.purge_help"}}</p> | ||||
| 		</div> | ||||
| 		{{template "base/delete_modal_actions" .}} | ||||
| 		<div class="actions"> | ||||
| 			<div class="ui red basic inverted cancel button"> | ||||
| 				{{svg "octicon-x"}} | ||||
| 				{{.locale.Tr "modal.no"}} | ||||
| 			</div> | ||||
| 			<button class="ui green basic inverted ok button"> | ||||
| 				{{svg "octicon-check"}} | ||||
| 				{{.locale.Tr "modal.yes"}} | ||||
| 			</button> | ||||
| 		</div> | ||||
| 	</form> | ||||
| </div> | ||||
| {{template "base/footer" .}} | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ | |||
| 				{{template "org/team/navbar" .}} | ||||
| 				{{if .IsOrganizationOwner}} | ||||
| 					<div class="ui attached segment"> | ||||
| 						<form class="ui form" id="add-member-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/add" method="post"> | ||||
| 						<form class="ui form ignore-dirty" id="add-member-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/add" method="post"> | ||||
| 							{{.CsrfTokenHtml}} | ||||
| 							<input type="hidden" name="uid" value="{{.SignedUser.ID}}"> | ||||
| 							<div class="inline field ui left"> | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ | |||
| 				{{if $canAddRemove}} | ||||
| 					<div class="ui attached segment" id="repo-top-segment"> | ||||
| 						<div class="inline ui field left"> | ||||
| 							<form class="ui form" id="add-repo-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/add" method="post"> | ||||
| 							<form class="ui form ignore-dirty" id="add-repo-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/add" method="post"> | ||||
| 								{{.CsrfTokenHtml}} | ||||
| 								<div class="inline field ui left"> | ||||
| 									<div id="search-repo-box" data-uid="{{.Org.ID}}" class="ui search"> | ||||
|  |  | |||
|  | @ -14,6 +14,10 @@ | |||
| 					<div class="markup"><pre class="code-block"><code>docker pull {{.RegistryHost}}/{{.PackageDescriptor.Owner.LowerName}}/{{.PackageDescriptor.Package.LowerName}}{{$separator}}{{.PackageDescriptor.Version.LowerVersion}}</code></pre></div> | ||||
| 				{{end}} | ||||
| 			</div> | ||||
| 			<div class="field"> | ||||
| 				<label>{{svg "octicon-code"}} {{.locale.Tr "packages.container.digest"}}</label> | ||||
| 				<div class="markup"><pre class="code-block"><code>{{range .PackageDescriptor.Files}}{{if eq .File.LowerName "manifest.json"}}{{.Properties.GetByName "container.digest"}}{{end}}{{end}}</code></pre></div> | ||||
| 			</div> | ||||
| 			<div class="field"> | ||||
| 				<label>{{.locale.Tr "packages.container.documentation" | Safe}}</label> | ||||
| 			</div> | ||||
|  |  | |||
|  | @ -94,6 +94,9 @@ | |||
| 						{{.root.CsrfTokenHtml}} | ||||
| 						<input type="hidden" name="new_branch_name" v-model="searchTerm"> | ||||
| 						<input type="hidden" name="create_tag" v-model="createTag"> | ||||
| 						{{if $.root.TreePath}} | ||||
| 							<input type="hidden" name="current_path" value="{{.root.TreePath}}"> | ||||
| 						{{end}} | ||||
| 					</form> | ||||
| 				</div> | ||||
| 			</div> | ||||
|  |  | |||
|  | @ -1,10 +1,12 @@ | |||
| import {renderMermaid} from './mermaid.js'; | ||||
| import {renderMath} from './math.js'; | ||||
| import {renderCodeCopy} from './codecopy.js'; | ||||
| import {initMarkupTasklist} from './tasklist.js'; | ||||
| 
 | ||||
| // code that runs for all markup content
 | ||||
| export function initMarkupContent() { | ||||
|   renderMermaid(); | ||||
|   renderMath(); | ||||
|   renderCodeCopy(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										37
									
								
								web_src/js/markup/math.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								web_src/js/markup/math.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| function displayError(el, err) { | ||||
|   const target = targetElement(el); | ||||
|   target.remove('is-loading'); | ||||
|   const errorNode = document.createElement('div'); | ||||
|   errorNode.setAttribute('class', 'ui message error markup-block-error mono'); | ||||
|   errorNode.textContent = err.str || err.message || String(err); | ||||
|   target.before(errorNode); | ||||
| } | ||||
| 
 | ||||
| function targetElement(el) { | ||||
|   // The target element is either the current element if it has the `is-loading` class or the pre that contains it
 | ||||
|   return el.classList.contains('is-loading') ? el : el.closest('pre'); | ||||
| } | ||||
| 
 | ||||
| export async function renderMath() { | ||||
|   const els = document.querySelectorAll('.markup code.language-math'); | ||||
|   if (!els.length) return; | ||||
| 
 | ||||
|   const [{default: katex}] = await Promise.all([ | ||||
|     import(/* webpackChunkName: "katex" */'katex'), | ||||
|     import(/* webpackChunkName: "katex" */'katex/dist/katex.css'), | ||||
|   ]); | ||||
| 
 | ||||
|   for (const el of els) { | ||||
|     const source = el.textContent; | ||||
|     const options = {display: el.classList.contains('display')}; | ||||
| 
 | ||||
|     try { | ||||
|       const markup = katex.renderToString(source, options); | ||||
|       const tempEl = document.createElement(options.display ? 'p' : 'span'); | ||||
|       tempEl.innerHTML = markup; | ||||
|       targetElement(el).replaceWith(tempEl); | ||||
|     } catch (error) { | ||||
|       displayError(el, error); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -33,6 +33,13 @@ | |||
|   height: var(--height-loading); | ||||
| } | ||||
| 
 | ||||
| code.language-math.is-loading::after { | ||||
|   padding: 0; | ||||
|   border-width: 2px; | ||||
|   width: 1.25rem; | ||||
|   height: 1.25rem; | ||||
| } | ||||
| 
 | ||||
| @keyframes fadein { | ||||
|   0% { | ||||
|     opacity: 0; | ||||
|  |  | |||
|  | @ -37,6 +37,10 @@ const filterCssImport = (url, ...args) => { | |||
|     if (/(eot|ttf|otf|woff|svg)$/.test(importedFile)) return false; | ||||
|   } | ||||
| 
 | ||||
|   if (cssFile.includes('katex') && /(ttf|woff)$/.test(importedFile)) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   if (cssFile.includes('font-awesome') && /(eot|ttf|otf|woff|svg)$/.test(importedFile)) { | ||||
|     return false; | ||||
|   } | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Anthony Wang
						Anthony Wang