Compare commits

...

11 Commits

Author SHA1 Message Date
Giteabot
6be6c19daf
Fix PR view misalignment caused by long name file (#23321) (#23335)
Backport #23321

Close #23248

The UI after this PR:

https://user-images.githubusercontent.com/17645053/223009758-7f0c9f12-d346-4cb2-a605-729fddce732f.mov

Co-authored-by: Hester Gong <hestergong@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-03-07 11:11:55 +08:00
Giteabot
61f91bdc7e
Update documentation for the new YAML label file format (#23020) (#23341)
Backport #23020

update documentation to include an overview of the yaml label template
that is part of PR #22976

Signed-off-by: Jon Roadley-Battin <jon.roadleybattin@gmail.com>

Co-authored-by: JonRB <4564448+eeyrjmr@users.noreply.github.com>
Co-authored-by: Yarden Shoham <hrsi88@gmail.com>
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-authored-by: delvh <dev.lh@web.de>
2023-03-06 17:52:29 -06:00
Giteabot
8ab50be000
Remove unnecessary space on link (#23334) (#23340)
Backport #23334

The action run title has a blank. This PR removes it.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-03-06 16:35:51 -05:00
Giteabot
dfab6e2d1c
Fix incorrect redirect link of delete org project (#23327) (#23339)
Backport #23327

A part of https://github.com/go-gitea/gitea/pull/22865/commits

The old code will cause 500 error.

Co-authored-by: yp05327 <576951401@qq.com>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
2023-03-06 16:05:42 -05:00
Giteabot
2f7bbdf8c9
Fix incorrect project links and use symlink icon for org-wide projects (#23325) (#23336)
Backport #23325

Fix displaying same projects icons between user/repo projects.
And fix incorrect projects links.
A part of https://github.com/go-gitea/gitea/pull/22865.


![image](https://user-images.githubusercontent.com/18380374/223044279-7b620ff1-d88a-4146-97e6-531bbf269761.png)

![image](https://user-images.githubusercontent.com/18380374/223044390-42911e3f-1f6b-439f-8441-4f3ebf99ea13.png)

![image](https://user-images.githubusercontent.com/18380374/223044437-5cad5391-0f95-4c8b-b0a3-32e263e2854f.png)

Co-authored-by: yp05327 <576951401@qq.com>
2023-03-06 13:48:04 -06:00
Giteabot
af4767df5c
Scoped labels: don't require holding alt key to remove (#23303) (#23331)
Backport #23303

Alt doesn't work on all browsers, the simplest solution for v1.19 is to
just not require it and toggle the label by just clicking.

Part of #22974

Co-authored-by: Brecht Van Lommel <brecht@blender.org>
2023-03-06 12:59:53 -06:00
Giteabot
233a399706
Fix broken code editor diff preview (#23307) (#23320)
Backport #23307

Close #23265, the code editor diff preview has been broken for long
time.

* Fix the regression for `data-line-num`
    * `.code-diff` is necessary to show the line number
* Fix the regression for #12434
* The diff:
[12434](https://github.com/go-gitea/gitea/pull/12434/files?diff=unified&w=1)
    * It hides the Type(4) (aka HunkHeader)  for unexpected cases.


Diff with ignoring whitespaces:
https://github.com/go-gitea/gitea/pull/23307/files?diff=unified&w=1

Before: see the issue #23265

After:


![image](https://user-images.githubusercontent.com/2114189/222942810-286dc9af-0b39-4e9d-8585-8c299b881241.png)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-03-06 12:05:35 -06:00
Giteabot
dcf1717793
Add context when rendering labels or emojis (#23281) (#23319)
Backport #23281

This branch continues the work of #23092 and attempts to rid the
codebase of any `nil` contexts when using a `RenderContext`.

Anything that renders markdown or does post processing may call
`markup.sha1CurrentPatternProcessor()`, and this runs
`git.OpenRepository()`, which needs a context. It will panic if the
context is `nil`. This branch attempts to _always_ include a context
when creating a `RenderContext` to prevent future crashes.

Co-authored-by: Jonathan Tran <jon@allspice.io>
2023-03-06 10:32:40 -06:00
Giteabot
b1e68f39e7
Properly flush unique queues on startup (#23154) (#23201)
Backport #23154

There have been a number of reports of PRs being blocked whilst being
checked which have been difficult to debug. In investigating #23050 I
have realised that whilst the Warn there is somewhat of a miscall there
was a real bug in the way that the LevelUniqueQueue was being restored
on start-up of the PersistableChannelUniqueQueue.

Next there is a conflict in the setting of the internal leveldb queue
name - This wasn't being set so it was being overridden by other unique
queues.

This PR fixes these bugs and adds a testcase.

Thanks to @brechtvl  for noticing the second issue.

Fix #23050
and others

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: delvh <leon@kske.dev>
2023-03-06 22:35:07 +08:00
Giteabot
ee3d9330a8
Update hacking-on-gitea-zh_cn documentation (#23315) (#23323)
Backport #23315

This PR fix outdated hacking-on-gitea-zh_cn documentation.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jason Song <i@wolfogre.com>
2023-03-06 18:44:02 +08:00
Giteabot
d1d15306d1
Change interactiveBorder to fix popup preview (#23169) (#23314)
Backport #23169

Close #23073. 
Used the solution as reference to the reply:
https://github.com/go-gitea/gitea/issues/23073#issuecomment-1440124609
Here made the change inside the `contextpopup.js` because this is where
the popup component is created and tippy configuration is given.

Co-authored-by: Hester Gong <hestergong@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-03-06 16:45:43 +08:00
46 changed files with 874 additions and 170 deletions

View File

@ -282,9 +282,22 @@ To add custom .gitignore, add a file with existing [.gitignore rules](https://gi
### Labels
To add a custom label set, add a file that follows the [label format](https://github.com/go-gitea/gitea/blob/main/options/label/Default) to `$GITEA_CUSTOM/options/label`
Starting with Gitea 1.19, you can add a file that follows the [YAML label format](https://github.com/go-gitea/gitea/blob/main/options/label/Advanced.yaml) to `$GITEA_CUSTOM/options/label`:
```yaml
labels:
- name: "foo/bar" # name of the label that will appear in the dropdown
exclusive: true # whether to use the exclusive namespace for scoped labels. scoped delimiter is /
color: aabbcc # hex colour coding
description: Some label # long description of label intent
```
The [legacy file format](https://github.com/go-gitea/gitea/blob/main/options/label/Default) can still be used following the format below, however we strongly recommend using the newer YAML format instead.
`#hex-color label name ; label description`
For more information, see the [labels documentation]({{< relref "doc/usage/labels.en-us.md" >}}).
### Licenses
To add a custom license, add a file with the license text to `$GITEA_CUSTOM/options/license`

View File

@ -1,6 +1,6 @@
---
date: "2016-12-01T16:00:00+02:00"
title: "加入 Gitea 开源"
title: "玩转 Gitea"
slug: "hacking-on-gitea"
weight: 10
toc: false
@ -8,36 +8,342 @@ draft: false
menu:
sidebar:
parent: "developers"
name: "加入 Gitea 开源"
name: "玩转 Gitea"
weight: 10
identifier: "hacking-on-gitea"
---
# Hacking on Gitea
首先你需要一些运行环境,这和 [从源代码安装]({{< relref "doc/installation/from-source.zh-cn.md" >}}) 相同,如果你还没有设置好,可以先阅读那个章节。
**目录**
如果你想为 Gitea 贡献代码,你需要 Fork 这个项目并且以 `master` 为开发分支。Gitea 使用 Govendor
来管理依赖,因此所有依赖项都被工具自动 copy 在 vendor 子目录下。用下面的命令来下载源码:
{{< toc >}}
```
go get -d code.gitea.io/gitea
## 快速入门
要获得快速工作的开发环境,您可以使用 Gitpod。
[![在 Gitpod 中打开](/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/go-gitea/gitea)
## 安装 Golang
您需要 [安装 go]( https://golang.org/doc/install ) 并设置您的 go 环境。
接下来,[使用 npm 安装 Node.js](https://nodejs.org/en/download/) ,这是构建
JavaScript 和 CSS 文件的必要工具。最低支持的 Node.js 版本是 {{< min-node-version >}}
并且推荐使用最新的 LTS 版本。
**注意** :当执行需要外部工具的 make 任务时,比如
`make watch-backend`Gitea 会自动下载并构建这些必要的组件。为了能够使用这些,你必须
`"$GOPATH"/bin` 目录加入到可执行路径上。如果你不把go bin目录添加到可执行路径你必须手动
指定可执行程序路径。
**注意2** Go版本 {{< min-go-version >}} 或更高版本是必须的。Gitea 使用 `gofmt`
格式化源代码。然而,`gofmt` 的结果可能因 `go` 的版本而有差异。因此推荐安装我们持续集成使用
的 Go版本。截至上次更新Go 版本应该是 {{< go-version >}}。
## 安装 Make
Gitea 大量使用 `Make` 来自动化任务和改进开发。本指南涵盖了如何安装 Make。
### 在 Linux 上
使用包管理器安装。
在 Ubuntu/Debian 上:
```bash
sudo apt-get install make
```
然后你可以在 Github 上 fork [Gitea 项目](https://github.com/go-gitea/gitea),之后可以通过下面的命令进入源码目录:
在 Fedora/RHEL/CentOS 上
```
cd $GOPATH/src/code.gitea.io/gitea
```bash
sudo yum install make
```
要创建 pull requests 你还需要在源码中新增一个 remote 指向你 Fork 的地址,直接推送到 origin 的话会告诉你没有写权限:
### 在 Windows 上
Make 的这三个发行版都可以在 Windows 上运行:
- [单个二进制构建]( http://www.equation.com/servlet/equation.cmd?fa=make )。复制到某处并添加到 `PATH`
- [32 位版本](http://www.equation.com/ftpdir/make/32/make.exe)
- [64 位版本](http://www.equation.com/ftpdir/make/64/make.exe)
- [MinGW-w64](https://www.mingw-w64.org) / [MSYS2](https://www.msys2.org/)。
- MSYS2 是一个工具和库的集合,为您提供一个易于使用的环境来构建、安装和运行本机 Windows 软件,它包括 MinGW-w64。
- 在 MingGW-w64 中,二进制文件称为 `mingw32-make.exe` 而不是 `make.exe`。将 `bin` 文件夹添加到 `PATH`
- 在 MSYS2 中,您可以直接使用 `make`。请参阅 [MSYS2 移植](https://www.msys2.org/wiki/Porting/)。
- 要使用 CGO_ENABLED例如SQLite3编译 Gitea您可能需要使用 [tdm-gcc](https://jmeubank.github.io/tdm-gcc/) 而不是 MSYS2 gcc因为 MSYS2 gcc 标头缺少一些 Windows -只有 CRT 函数像 _beginthread 一样。
- [Chocolatey包管理器]( https://chocolatey.org/packages/make )。运行`choco install make`
**注意** :如果您尝试在 Windows 命令提示符下使用 make 进行构建您可能会遇到问题。建议使用上述提示Git bash 或 MinGW但是如果您只有命令提示符或可能是 PowerShell则可以使用 [set](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/set_1) 命令,例如 `set TAGS=bindata`
## 下载并克隆 Gitea 源代码
获取源代码的推荐方法是使用 `git clone`
```bash
git clone https://github.com/go-gitea/gitea
```
自从go modules出现后不再需要构建 go 项目从 `$GOPATH` 中获取,因此不再推荐使用 `go get` 方法。)
## 派生 Gitea
如上所述下载主要的 Gitea 源代码。然后,派生 [Gitea 仓库](https://github.com/go-gitea/gitea)
并为您的本地仓库切换 git 远程源,或添加另一个远程源:
```bash
# 将原来的 Gitea origin 重命名为 upstream
git remote rename origin upstream
git remote add origin git@github.com:<USERNAME>/gitea.git
git remote add origin "git@github.com:$GITHUB_USERNAME/gitea.git"
git fetch --all --prune
```
然后你就可以开始开发了。你可以看一下 `Makefile` 的内容。`make test` 可以运行测试程序, `make build` 将生成一个 `gitea` 可运行文件在根目录。如果你的提交比较复杂,尽量多写一些单元测试代码。
或者:
好了,到这里你已经设置好了所有的开发 Gitea 所需的环境。欢迎成为 Gitea 的 Contributor。
```bash
# 为我们的 fork 添加新的远程
git remote add "$FORK_NAME" "git@github.com:$GITHUB_USERNAME/gitea.git"
git fetch --all --prune
```
为了能够创建合并请求,应将分叉存储库添加为 Gitea 本地仓库的远程,否则无法推送更改。
## 构建 Gitea基本
看看我们的
<a href='{{ < relref "doc/installation/from-source.en-us.md" > }}'>说明</a>
关于如何 <a href='{{ < relref "doc/installation/from-source.en-us.md" > }}'>从源代码构建</a>
从源代码构建的最简单推荐方法是:
```bash
TAGS="bindata sqlite sqlite_unlock_notify" make build
```
`build` 目标将同时执行 `frontend``backend` 子目标。如果存在 `bindata` 标签,资源文件将被编译成二进制文件。建议在进行前端开发时省略 `bindata` 标签,以便实时反映更改。
有关所有可用的 `make` 目标,请参阅 `make help`。另请参阅 [`.drone.yml`](https://github.com/go-gitea/gitea/blob/main/.drone.yml) 以了解我们的持续集成是如何工作的。
## 持续构建
要在源文件更改时运行并持续构建:
```bash
# 对于前端和后端
make watch
# 或者只看前端文件html/js/css
make watch-frontend
# 或者:只看后端文件 (go)
make watch-backend
```
在 macOS 上,监视所有后端源文件可能会达到默认的打开文件限制,这可以通过当前 shell 的 `ulimit -n 12288` 或所有未来 shell 的 shell 启动文件来增加。
### 格式化、代码分析和拼写检查
我们的持续集成将拒绝未通过代码检查(包括格式检查、代码分析和拼写检查)的 PR。
你应该格式化你的代码:
```bash
make fmt
```
并检查源代码:
```bash
# lint 前端和后端代码
make lint
# 仅 lint 后端代码
make lint-backend
```
**注意** `gofmt` 的结果取决于 `go` 的版本。您应该运行与持续集成相同的 go 版本。
### 处理 JS 和 CSS
前端开发应遵循 [Guidelines for Frontend Development]({{ < 相关参考 "doc/developers/guidelines-frontend.en-us.md" > }})
要使用前端资源构建请使用上面提到的“watch-frontend”目标或只构建一次
```bash
make build && ./gitea
```
在提交之前,确保 linters 通过:
```bash
make lint-frontend
```
### 配置本地 ElasticSearch 实例
使用 docker 启动本地 ElasticSearch 实例:
```sh
mkdir -p $(pwd) /data/elasticsearch
sudo chown -R 1000:1000 $(pwd) /data/elasticsearch
docker run --rm --memory= "4g" -p 127.0.0.1:9200:9200 -p 127.0.0.1:9300:9300 -e "discovery.type=single-node" -v "$(pwd)/data /elasticsearch:/usr/share/elasticsearch/data" docker.elastic.co/elasticsearch/elasticsearch:7.16.3
```
配置`app.ini`
```ini
[indexer]
ISSUE_INDEXER_TYPE = elasticsearch
ISSUE_INDEXER_CONN_STR = http://elastic:changeme@localhost:9200
REPO_INDEXER_ENABLED = true
REPO_INDEXER_TYPE = elasticsearch
REPO_INDEXER_CONN_STR = http://elastic:changeme@localhost:9200
```
### 构建和添加 SVGs
SVG 图标是使用 `make svg` 目标构建的,该目标将 `build/generate-svg.js` 中定义的图标源编译到输出目录 `public/img/svg` 中。可以在 `web_src/svg` 目录中添加自定义图标。
### 构建 Logo
Gitea Logo的 PNG 和 SVG 版本是使用 `TAGS="gitea" make generate-images` 目标从单个 SVG 源文件 assets/logo.svg 构建的。要运行它Node.js 和 npm 必须可用。
通过更新 `assets/logo.svg` 并运行 `make generate-images`,同样的过程也可用于从 SVG 源文件生成自定义 Logo PNG。忽略 gitea 编译选项将仅更新用户指定的 LOGO 文件。
### 更新 API
创建新的 API 路由或修改现有的 API 路由时,您**必须**
更新和/或创建 [Swagger](https://swagger.io/docs/specification/2-0/what-is-swagger/)
这些使用 [go-swagger](https://goswagger.io/) 评论的文档。
[规范]( https://goswagger.io/use/spec.html#annotation-syntax )中描述了这些注释的结构。
如果您想了解更多有关 Swagger 结构的信息,可以查看
[Swagger 2.0 文档](https://swagger.io/docs/specification/2-0/basic-structure/)
或与添加新 API 端点的先前 PR 进行比较,例如 [PR #5483](https://github.com/go-gitea/gitea/pull/5843/files#diff-2e0a7b644cf31e1c8ef7d76b444fe3aaR20)
您应该注意不要破坏下游用户依赖的 API。在稳定的 API 上,一般来说添加是可以接受的,但删除
或对 API 进行根本性更改将会被拒绝。
创建或更改 API 端点后,请用以下命令重新生成 Swagger 文档:
```bash
make generate-swagger
```
您应该验证生成的 Swagger 文件并使用以下命令对其进行拼写检查:
```bash
make swagger-validate misspell-check
```
您应该提交更改后的 swagger JSON 文件。持续集成服务器将使用以下方法检查是否已完成:
```bash
make swagger-check
```
**注意** :请注意,您应该使用 Swagger 2.0 文档,而不是 OpenAPI 3 文档。
### 创建新的配置选项
创建新的配置选项时,将它们添加到 `modules/setting` 的对应文件。您应该将信息添加到 `custom/conf/app.ini`
并到 <a href = '{{ < relref "doc/advanced/config-cheat-sheet.en-us.md" > }}'>配置备忘单</a>
`docs/content/doc/advanced/config-cheat-sheet.en-us.md` 中找到
### 更改Logo
更改 Gitea Logo SVG 时,您将需要运行并提交结果的:
```bash
make generate-images
```
这将创建必要的 Gitea 图标和其他图标。
### 数据库迁移
如果您对数据库中的任何数据库持久结构进行重大更改
`models/` 目录,您将需要进行新的迁移。可以找到这些
`models/migrations/` 中。您可以确保您的迁移适用于主要
数据库类型使用:
```bash
make test-sqlite-migration # 将 SQLite 切换为适当的数据库
```
## 测试
Gitea 运行两种类型的测试:单元测试和集成测试。
### 单元测试
`go test` 系统中的`*_test.go` 涵盖了单元测试。
您可以设置环境变量 `GITEA_UNIT_TESTS_LOG_SQL=1` 以在详细模式下运行测试时显示所有 SQL 语句(即设置`GOTESTFLAGS=-v` 时)。
```bash
TAGS="bindata sqlite sqlite_unlock_notify" make test # Runs the unit tests
```
### 集成测试
单元测试不会也不能完全单独测试 Gitea。因此我们编写了集成测试但是这些依赖于数据库。
```bash
TAGS="bindata sqlite sqlite_unlock_notify" make build test-sqlite
```
将在 SQLite 环境中运行集成测试。集成测试需要安装 `git lfs`。其他数据库测试可用,但
可能需要适应当地环境。
看看 [`tests/integration/README.md`](https://github.com/go-gitea/gitea/blob/main/tests/integration/README.md) 有关更多信息以及如何运行单个测试。
### 测试 PR
我们的持续集成将测试代码是否通过了单元测试,并且所有支持的数据库都将在 Docker 环境中通过集成测试。
还将测试从几个最新版本的 Gitea 迁移。
请在PR中附带提交适当的单元测试和集成测试。
## 网站文档
该网站的文档位于 `docs/` 中。如果你改变了文档内容,你可以使用以下测试方法进行持续集成:
```bash
# 来自 Gitea 中的 docs 目录
make trans-copy clean build
```
运行此任务依赖于 [Hugo](https://gohugo.io/)。请注意:这可能会生成一些未跟踪的 Git 对象,
需要被清理干净。
## Visual Studio Code
`contrib/ide/vscode` 中为 Visual Studio Code 提供了 `launch.json``tasks.json`。查看
[`contrib/ide/README.md`](https://github.com/go-gitea/gitea/blob/main/contrib/ide/README.md) 了解更多信息。
## Goland
单击 `/main.go` 中函数 `func main()` 上的 `Run Application` 箭头
可以快速启动一个可调试的 Gitea 实例。
`Run/Debug Configuration` 中的 `Output Directory` 必须设置为
gitea 项目目录(包含 `main.go``go.mod`
否则,启动实例的工作目录是 GoLand 的临时目录
并防止 Gitea 在开发环境中加载动态资源(例如:模板)。
要在 GoLand 中使用 SQLite 运行单元测试,请设置 `-tags sqlite,sqlite_unlock_notify`
`运行/调试配置``Go 工具参数` 中。
## 提交 PR
对更改感到满意后,将它们推送并打开拉取请求。它建议您允许 Gitea Managers 和 Owners 修改您的 PR
分支,因为我们需要在合并之前将其更新为 main 和/或可能是能够直接帮助解决问题。
任何 PR 都需要 Gitea 维护者的两次批准,并且需要通过持续集成。看看我们的
[CONTRIBUTING.md](https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md)
文档。
如果您需要更多帮助,请访问 [Discord](https://discord.gg/gitea) #Develop 频道
并在那里聊天。
现在,您已准备好 Hacking Gitea。

View File

@ -11,6 +11,7 @@ import (
"strings"
"testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"
"github.com/stretchr/testify/assert"
@ -229,7 +230,10 @@ John Doe john@doe.com This,note,had,a,lot,of,commas,to,test,delimiters`,
}
for n, c := range cases {
delimiter := determineDelimiter(&markup.RenderContext{RelativePath: c.filename}, []byte(decodeSlashes(t, c.csv)))
delimiter := determineDelimiter(&markup.RenderContext{
Ctx: git.DefaultContext,
RelativePath: c.filename,
}, []byte(decodeSlashes(t, c.csv)))
assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter)
}
}

View File

@ -7,6 +7,7 @@ import (
"strings"
"testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"
"github.com/stretchr/testify/assert"
@ -23,7 +24,8 @@ func TestRenderConsole(t *testing.T) {
canRender := render.CanRender("test", strings.NewReader(k))
assert.True(t, canRender)
err := render.Render(&markup.RenderContext{}, strings.NewReader(k), &buf)
err := render.Render(&markup.RenderContext{Ctx: git.DefaultContext},
strings.NewReader(k), &buf)
assert.NoError(t, err)
assert.EqualValues(t, v, buf.String())
}

View File

@ -7,6 +7,7 @@ import (
"strings"
"testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"
"github.com/stretchr/testify/assert"
@ -23,7 +24,8 @@ func TestRenderCSV(t *testing.T) {
for k, v := range kases {
var buf strings.Builder
err := render.Render(&markup.RenderContext{}, strings.NewReader(k), &buf)
err := render.Render(&markup.RenderContext{Ctx: git.DefaultContext},
strings.NewReader(k), &buf)
assert.NoError(t, err)
assert.EqualValues(t, v, buf.String())
}

View File

@ -291,9 +291,10 @@ func RenderDescriptionHTML(
// RenderEmoji for when we want to just process emoji and shortcodes
// in various places it isn't already run through the normal markdown processor
func RenderEmoji(
ctx *RenderContext,
content string,
) (string, error) {
return renderProcessString(&RenderContext{}, emojiProcessors, content)
return renderProcessString(ctx, emojiProcessors, content)
}
var (

View File

@ -9,6 +9,7 @@ import (
"strings"
"testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@ -70,8 +71,13 @@ var localMetas = map[string]string{
func TestRender_IssueIndexPattern(t *testing.T) {
// numeric: render inputs without valid mentions
test := func(s string) {
testRenderIssueIndexPattern(t, s, s, &RenderContext{})
testRenderIssueIndexPattern(t, s, s, &RenderContext{Metas: numericMetas})
testRenderIssueIndexPattern(t, s, s, &RenderContext{
Ctx: git.DefaultContext,
})
testRenderIssueIndexPattern(t, s, s, &RenderContext{
Ctx: git.DefaultContext,
Metas: numericMetas,
})
}
// should not render anything when there are no mentions
@ -119,7 +125,10 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
links[i] = numericIssueLink(util.URLJoin(TestRepoURL, path), "ref-issue", index, marker)
}
expectedNil := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{Metas: localMetas})
testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{
Ctx: git.DefaultContext,
Metas: localMetas,
})
class := "ref-issue"
if isExternal {
@ -130,7 +139,10 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
links[i] = numericIssueLink(prefix, class, index, marker)
}
expectedNum := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{Metas: numericMetas})
testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{
Ctx: git.DefaultContext,
Metas: numericMetas,
})
}
// should render freestanding mentions
@ -164,7 +176,10 @@ func TestRender_IssueIndexPattern3(t *testing.T) {
// alphanumeric: render inputs without valid mentions
test := func(s string) {
testRenderIssueIndexPattern(t, s, s, &RenderContext{Metas: alphanumericMetas})
testRenderIssueIndexPattern(t, s, s, &RenderContext{
Ctx: git.DefaultContext,
Metas: alphanumericMetas,
})
}
test("")
test("this is a test")
@ -194,7 +209,10 @@ func TestRender_IssueIndexPattern4(t *testing.T) {
links[i] = externalIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name)
}
expected := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expected, &RenderContext{Metas: alphanumericMetas})
testRenderIssueIndexPattern(t, s, expected, &RenderContext{
Ctx: git.DefaultContext,
Metas: alphanumericMetas,
})
}
test("OTT-1234 test", "%s test", "OTT-1234")
test("test T-12 issue", "test %s issue", "T-12")
@ -214,7 +232,10 @@ func TestRender_IssueIndexPattern5(t *testing.T) {
}
expected := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expected, &RenderContext{Metas: metas})
testRenderIssueIndexPattern(t, s, expected, &RenderContext{
Ctx: git.DefaultContext,
Metas: metas,
})
}
test("abc ISSUE-123 def", "abc %s def",
@ -235,7 +256,10 @@ func TestRender_IssueIndexPattern5(t *testing.T) {
[]string{"ISSUE-123"},
)
testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{Metas: regexpMetas})
testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{
Ctx: git.DefaultContext,
Metas: regexpMetas,
})
}
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
@ -255,6 +279,7 @@ func TestRender_AutoLink(t *testing.T) {
test := func(input, expected string) {
var buffer strings.Builder
err := PostProcess(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: TestRepoURL,
Metas: localMetas,
}, strings.NewReader(input), &buffer)
@ -263,6 +288,7 @@ func TestRender_AutoLink(t *testing.T) {
buffer.Reset()
err = PostProcess(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: TestRepoURL,
Metas: localMetas,
IsWiki: true,
@ -292,6 +318,7 @@ func TestRender_FullIssueURLs(t *testing.T) {
test := func(input, expected string) {
var result strings.Builder
err := postProcess(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: TestRepoURL,
Metas: localMetas,
}, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)

View File

@ -91,6 +91,7 @@ func TestRender_CrossReferences(t *testing.T) {
test := func(input, expected string) {
buffer, err := RenderString(&RenderContext{
Ctx: git.DefaultContext,
RelativePath: "a.md",
URLPrefix: setting.AppSubURL,
Metas: localMetas,
@ -135,6 +136,7 @@ func TestRender_links(t *testing.T) {
test := func(input, expected string) {
buffer, err := RenderString(&RenderContext{
Ctx: git.DefaultContext,
RelativePath: "a.md",
URLPrefix: TestRepoURL,
}, input)
@ -234,6 +236,7 @@ func TestRender_email(t *testing.T) {
test := func(input, expected string) {
res, err := RenderString(&RenderContext{
Ctx: git.DefaultContext,
RelativePath: "a.md",
URLPrefix: TestRepoURL,
}, input)
@ -292,6 +295,7 @@ func TestRender_emoji(t *testing.T) {
test := func(input, expected string) {
expected = strings.ReplaceAll(expected, "&", "&amp;")
buffer, err := RenderString(&RenderContext{
Ctx: git.DefaultContext,
RelativePath: "a.md",
URLPrefix: TestRepoURL,
}, input)
@ -355,11 +359,13 @@ func TestRender_ShortLinks(t *testing.T) {
test := func(input, expected, expectedWiki string) {
buffer, err := markdown.RenderString(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: tree,
}, input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
buffer, err = markdown.RenderString(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: TestRepoURL,
Metas: localMetas,
IsWiki: true,
@ -461,12 +467,14 @@ func TestRender_RelativeImages(t *testing.T) {
test := func(input, expected, expectedWiki string) {
buffer, err := markdown.RenderString(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: tree,
Metas: localMetas,
}, input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
buffer, err = markdown.RenderString(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: TestRepoURL,
Metas: localMetas,
IsWiki: true,
@ -501,6 +509,7 @@ func Test_ParseClusterFuzz(t *testing.T) {
var res strings.Builder
err := PostProcess(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: "https://example.com",
Metas: localMetas,
}, strings.NewReader(data), &res)
@ -511,6 +520,7 @@ func Test_ParseClusterFuzz(t *testing.T) {
res.Reset()
err = PostProcess(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: "https://example.com",
Metas: localMetas,
}, strings.NewReader(data), &res)
@ -531,6 +541,7 @@ func TestIssue16020(t *testing.T) {
var res strings.Builder
err := PostProcess(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: "https://example.com",
Metas: localMetas,
}, strings.NewReader(data), &res)
@ -547,6 +558,7 @@ func BenchmarkEmojiPostprocess(b *testing.B) {
for i := 0; i < b.N; i++ {
var res strings.Builder
err := PostProcess(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: "https://example.com",
Metas: localMetas,
}, strings.NewReader(data), &res)
@ -557,6 +569,7 @@ func BenchmarkEmojiPostprocess(b *testing.B) {
func TestFuzz(t *testing.T) {
s := "t/l/issues/8#/../../a"
renderContext := RenderContext{
Ctx: git.DefaultContext,
URLPrefix: "https://example.com/go-gitea/gitea",
Metas: map[string]string{
"user": "go-gitea",
@ -574,6 +587,7 @@ func TestIssue18471(t *testing.T) {
var res strings.Builder
err := PostProcess(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: "https://example.com",
Metas: localMetas,
}, strings.NewReader(data), &res)

View File

@ -52,12 +52,14 @@ func TestRender_StandardLinks(t *testing.T) {
test := func(input, expected, expectedWiki string) {
buffer, err := RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
URLPrefix: setting.AppSubURL,
}, input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
buffer, err = RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
URLPrefix: setting.AppSubURL,
IsWiki: true,
}, input)
@ -81,6 +83,7 @@ func TestRender_Images(t *testing.T) {
test := func(input, expected string) {
buffer, err := RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
URLPrefix: setting.AppSubURL,
}, input)
assert.NoError(t, err)
@ -311,6 +314,7 @@ func TestTotal_RenderWiki(t *testing.T) {
for i := 0; i < len(testCases); i += 2 {
line, err := RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
URLPrefix: AppSubURL,
IsWiki: true,
}, testCases[i])
@ -339,6 +343,7 @@ func TestTotal_RenderString(t *testing.T) {
for i := 0; i < len(testCases); i += 2 {
line, err := RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
URLPrefix: AppSubURL,
}, testCases[i])
assert.NoError(t, err)
@ -348,17 +353,17 @@ func TestTotal_RenderString(t *testing.T) {
func TestRender_RenderParagraphs(t *testing.T) {
test := func(t *testing.T, str string, cnt int) {
res, err := RenderRawString(&markup.RenderContext{}, str)
res, err := RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, str)
assert.NoError(t, err)
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for unix should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
mac := strings.ReplaceAll(str, "\n", "\r")
res, err = RenderRawString(&markup.RenderContext{}, mac)
res, err = RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, mac)
assert.NoError(t, err)
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for mac should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
dos := strings.ReplaceAll(str, "\n", "\r\n")
res, err = RenderRawString(&markup.RenderContext{}, dos)
res, err = RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, dos)
assert.NoError(t, err)
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for windows should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
}
@ -386,7 +391,7 @@ func TestMarkdownRenderRaw(t *testing.T) {
for _, testcase := range testcases {
log.Info("Test markdown render error with fuzzy data: %x, the following errors can be recovered", testcase)
_, err := RenderRawString(&markup.RenderContext{}, string(testcase))
_, err := RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, string(testcase))
assert.NoError(t, err)
}
}
@ -398,7 +403,7 @@ func TestRenderSiblingImages_Issue12925(t *testing.T) {
expected := `<p><a href="/image1" target="_blank" rel="nofollow noopener"><img src="/image1" alt="image1"></a><br>
<a href="/image2" target="_blank" rel="nofollow noopener"><img src="/image2" alt="image2"></a></p>
`
res, err := RenderRawString(&markup.RenderContext{}, testcase)
res, err := RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase)
assert.NoError(t, err)
assert.Equal(t, expected, res)
}
@ -407,7 +412,7 @@ func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
testcase := `[Link with emoji :moon: in text](https://gitea.io)`
expected := `<p><a href="https://gitea.io" rel="nofollow">Link with emoji <span class="emoji" aria-label="waxing gibbous moon">🌔</span> in text</a></p>
`
res, err := RenderString(&markup.RenderContext{}, testcase)
res, err := RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase)
assert.NoError(t, err)
assert.Equal(t, expected, res)
}
@ -441,7 +446,7 @@ func TestColorPreview(t *testing.T) {
}
for _, test := range positiveTests {
res, err := RenderString(&markup.RenderContext{}, test.testcase)
res, err := RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
@ -461,7 +466,7 @@ func TestColorPreview(t *testing.T) {
}
for _, test := range negativeTests {
res, err := RenderString(&markup.RenderContext{}, test)
res, err := RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test)
assert.NoError(t, err, "Unexpected error in testcase: %q", test)
assert.NotContains(t, res, `<span class="color-preview" style="background-color: `, "Unexpected result in testcase %q", test)
}
@ -508,7 +513,7 @@ func TestMathBlock(t *testing.T) {
}
for _, test := range testcases {
res, err := RenderString(&markup.RenderContext{}, test.testcase)
res, err := RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)

View File

@ -7,6 +7,7 @@ import (
"strings"
"testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@ -26,6 +27,7 @@ func TestRender_StandardLinks(t *testing.T) {
test := func(input, expected string) {
buffer, err := RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
URLPrefix: setting.AppSubURL,
}, input)
assert.NoError(t, err)
@ -46,6 +48,7 @@ func TestRender_Images(t *testing.T) {
test := func(input, expected string) {
buffer, err := RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
URLPrefix: setting.AppSubURL,
}, input)
assert.NoError(t, err)
@ -65,6 +68,7 @@ func TestRender_Source(t *testing.T) {
test := func(input, expected string) {
buffer, err := RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
URLPrefix: setting.AppSubURL,
}, input)
assert.NoError(t, err)

View File

@ -124,7 +124,10 @@ func (q *ChannelQueue) Shutdown() {
log.Trace("ChannelQueue: %s Flushing", q.name)
// We can't use Cleanup here because that will close the channel
if err := q.FlushWithContext(q.terminateCtx); err != nil {
log.Warn("ChannelQueue: %s Terminated before completed flushing", q.name)
count := atomic.LoadInt64(&q.numInQueue)
if count > 0 {
log.Warn("ChannelQueue: %s Terminated before completed flushing", q.name)
}
return
}
log.Debug("ChannelQueue: %s Flushed", q.name)

View File

@ -94,7 +94,8 @@ func NewPersistableChannelQueue(handle HandlerFunc, cfg, exemplar interface{}) (
},
Workers: 0,
},
DataDir: config.DataDir,
DataDir: config.DataDir,
QueueName: config.Name + "-level",
}
levelQueue, err := NewLevelQueue(wrappedHandle, levelCfg, exemplar)
@ -172,16 +173,18 @@ func (q *PersistableChannelQueue) Run(atShutdown, atTerminate func(func())) {
atShutdown(q.Shutdown)
atTerminate(q.Terminate)
if lq, ok := q.internal.(*LevelQueue); ok && lq.byteFIFO.Len(lq.shutdownCtx) != 0 {
if lq, ok := q.internal.(*LevelQueue); ok && lq.byteFIFO.Len(lq.terminateCtx) != 0 {
// Just run the level queue - we shut it down once it's flushed
go q.internal.Run(func(_ func()) {}, func(_ func()) {})
go func() {
for !q.IsEmpty() {
_ = q.internal.Flush(0)
for !lq.IsEmpty() {
_ = lq.Flush(0)
select {
case <-time.After(100 * time.Millisecond):
case <-q.internal.(*LevelQueue).shutdownCtx.Done():
log.Warn("LevelQueue: %s shut down before completely flushed", q.internal.(*LevelQueue).Name())
case <-lq.shutdownCtx.Done():
if lq.byteFIFO.Len(lq.terminateCtx) > 0 {
log.Warn("LevelQueue: %s shut down before completely flushed", q.internal.(*LevelQueue).Name())
}
return
}
}
@ -316,10 +319,22 @@ func (q *PersistableChannelQueue) Shutdown() {
// Redirect all remaining data in the chan to the internal channel
log.Trace("PersistableChannelQueue: %s Redirecting remaining data", q.delayedStarter.name)
close(q.channelQueue.dataChan)
countOK, countLost := 0, 0
for data := range q.channelQueue.dataChan {
_ = q.internal.Push(data)
err := q.internal.Push(data)
if err != nil {
log.Error("PersistableChannelQueue: %s Unable redirect %v due to: %v", q.delayedStarter.name, data, err)
countLost++
} else {
countOK++
}
atomic.AddInt64(&q.channelQueue.numInQueue, -1)
}
if countLost > 0 {
log.Warn("PersistableChannelQueue: %s %d will be restored on restart, %d lost", q.delayedStarter.name, countOK, countLost)
} else if countOK > 0 {
log.Warn("PersistableChannelQueue: %s %d will be restored on restart", q.delayedStarter.name, countOK)
}
log.Trace("PersistableChannelQueue: %s Done Redirecting remaining data", q.delayedStarter.name)
log.Debug("PersistableChannelQueue: %s Shutdown", q.delayedStarter.name)

View File

@ -39,7 +39,7 @@ func TestPersistableChannelQueue(t *testing.T) {
Workers: 1,
BoostWorkers: 0,
MaxWorkers: 10,
Name: "first",
Name: "test-queue",
}, &testData{})
assert.NoError(t, err)
@ -135,7 +135,7 @@ func TestPersistableChannelQueue(t *testing.T) {
Workers: 1,
BoostWorkers: 0,
MaxWorkers: 10,
Name: "second",
Name: "test-queue",
}, &testData{})
assert.NoError(t, err)
@ -227,7 +227,7 @@ func TestPersistableChannelQueue_Pause(t *testing.T) {
Workers: 1,
BoostWorkers: 0,
MaxWorkers: 10,
Name: "first",
Name: "test-queue",
}, &testData{})
assert.NoError(t, err)
@ -433,7 +433,7 @@ func TestPersistableChannelQueue_Pause(t *testing.T) {
Workers: 1,
BoostWorkers: 0,
MaxWorkers: 10,
Name: "second",
Name: "test-queue",
}, &testData{})
assert.NoError(t, err)
pausable, ok = queue.(Pausable)

View File

@ -177,7 +177,9 @@ func (q *ChannelUniqueQueue) Shutdown() {
go func() {
log.Trace("ChannelUniqueQueue: %s Flushing", q.name)
if err := q.FlushWithContext(q.terminateCtx); err != nil {
log.Warn("ChannelUniqueQueue: %s Terminated before completed flushing", q.name)
if !q.IsEmpty() {
log.Warn("ChannelUniqueQueue: %s Terminated before completed flushing", q.name)
}
return
}
log.Debug("ChannelUniqueQueue: %s Flushed", q.name)

View File

@ -8,10 +8,13 @@ import (
"testing"
"time"
"code.gitea.io/gitea/modules/log"
"github.com/stretchr/testify/assert"
)
func TestChannelUniqueQueue(t *testing.T) {
_ = log.NewLogger(1000, "console", "console", `{"level":"warn","stacktracelevel":"NONE","stderr":true}`)
handleChan := make(chan *testData)
handle := func(data ...Data) []Data {
for _, datum := range data {
@ -52,6 +55,8 @@ func TestChannelUniqueQueue(t *testing.T) {
}
func TestChannelUniqueQueue_Batch(t *testing.T) {
_ = log.NewLogger(1000, "console", "console", `{"level":"warn","stacktracelevel":"NONE","stderr":true}`)
handleChan := make(chan *testData)
handle := func(data ...Data) []Data {
for _, datum := range data {
@ -98,6 +103,8 @@ func TestChannelUniqueQueue_Batch(t *testing.T) {
}
func TestChannelUniqueQueue_Pause(t *testing.T) {
_ = log.NewLogger(1000, "console", "console", `{"level":"warn","stacktracelevel":"NONE","stderr":true}`)
lock := sync.Mutex{}
var queue Queue
var err error

View File

@ -94,7 +94,8 @@ func NewPersistableChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interfac
},
Workers: 0,
},
DataDir: config.DataDir,
DataDir: config.DataDir,
QueueName: config.Name + "-level",
}
queue.channelQueue = channelUniqueQueue.(*ChannelUniqueQueue)
@ -209,17 +210,29 @@ func (q *PersistableChannelUniqueQueue) Run(atShutdown, atTerminate func(func())
atTerminate(q.Terminate)
_ = q.channelQueue.AddWorkers(q.channelQueue.workers, 0)
if luq, ok := q.internal.(*LevelUniqueQueue); ok && luq.ByteFIFOUniqueQueue.byteFIFO.Len(luq.shutdownCtx) != 0 {
if luq, ok := q.internal.(*LevelUniqueQueue); ok && !luq.IsEmpty() {
// Just run the level queue - we shut it down once it's flushed
go q.internal.Run(func(_ func()) {}, func(_ func()) {})
go luq.Run(func(_ func()) {}, func(_ func()) {})
go func() {
_ = q.internal.Flush(0)
log.Debug("LevelUniqueQueue: %s flushed so shutting down", q.internal.(*LevelUniqueQueue).Name())
q.internal.(*LevelUniqueQueue).Shutdown()
GetManager().Remove(q.internal.(*LevelUniqueQueue).qid)
_ = luq.Flush(0)
for !luq.IsEmpty() {
_ = luq.Flush(0)
select {
case <-time.After(100 * time.Millisecond):
case <-luq.shutdownCtx.Done():
if luq.byteFIFO.Len(luq.terminateCtx) > 0 {
log.Warn("LevelUniqueQueue: %s shut down before completely flushed", luq.Name())
}
return
}
}
log.Debug("LevelUniqueQueue: %s flushed so shutting down", luq.Name())
luq.Shutdown()
GetManager().Remove(luq.qid)
}()
} else {
log.Debug("PersistableChannelUniqueQueue: %s Skipping running the empty level queue", q.delayedStarter.name)
_ = q.internal.Flush(0)
q.internal.(*LevelUniqueQueue).Shutdown()
GetManager().Remove(q.internal.(*LevelUniqueQueue).qid)
}
@ -285,8 +298,20 @@ func (q *PersistableChannelUniqueQueue) Shutdown() {
// Redirect all remaining data in the chan to the internal channel
close(q.channelQueue.dataChan)
log.Trace("PersistableChannelUniqueQueue: %s Redirecting remaining data", q.delayedStarter.name)
countOK, countLost := 0, 0
for data := range q.channelQueue.dataChan {
_ = q.internal.Push(data)
err := q.internal.(*LevelUniqueQueue).Push(data)
if err != nil {
log.Error("PersistableChannelUniqueQueue: %s Unable redirect %v due to: %v", q.delayedStarter.name, data, err)
countLost++
} else {
countOK++
}
}
if countLost > 0 {
log.Warn("PersistableChannelUniqueQueue: %s %d will be restored on restart, %d lost", q.delayedStarter.name, countOK, countLost)
} else if countOK > 0 {
log.Warn("PersistableChannelUniqueQueue: %s %d will be restored on restart", q.delayedStarter.name, countOK)
}
log.Trace("PersistableChannelUniqueQueue: %s Done Redirecting remaining data", q.delayedStarter.name)

View File

@ -0,0 +1,259 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package queue
import (
"fmt"
"strconv"
"sync"
"testing"
"time"
"code.gitea.io/gitea/modules/log"
"github.com/stretchr/testify/assert"
)
func TestPersistableChannelUniqueQueue(t *testing.T) {
tmpDir := t.TempDir()
fmt.Printf("TempDir %s\n", tmpDir)
_ = log.NewLogger(1000, "console", "console", `{"level":"warn","stacktracelevel":"NONE","stderr":true}`)
// Common function to create the Queue
newQueue := func(name string, handle func(data ...Data) []Data) Queue {
q, err := NewPersistableChannelUniqueQueue(handle,
PersistableChannelUniqueQueueConfiguration{
Name: name,
DataDir: tmpDir,
QueueLength: 200,
MaxWorkers: 1,
BlockTimeout: 1 * time.Second,
BoostTimeout: 5 * time.Minute,
BoostWorkers: 1,
Workers: 0,
}, "task-0")
assert.NoError(t, err)
return q
}
// runs the provided queue and provides some timer function
type channels struct {
readyForShutdown chan struct{} // closed when shutdown functions have been assigned
readyForTerminate chan struct{} // closed when terminate functions have been assigned
signalShutdown chan struct{} // Should close to signal shutdown
doneShutdown chan struct{} // closed when shutdown function is done
queueTerminate []func() // list of atTerminate functions to call atTerminate - need to be accessed with lock
}
runQueue := func(q Queue, lock *sync.Mutex) *channels {
chans := &channels{
readyForShutdown: make(chan struct{}),
readyForTerminate: make(chan struct{}),
signalShutdown: make(chan struct{}),
doneShutdown: make(chan struct{}),
}
go q.Run(func(atShutdown func()) {
go func() {
lock.Lock()
select {
case <-chans.readyForShutdown:
default:
close(chans.readyForShutdown)
}
lock.Unlock()
<-chans.signalShutdown
atShutdown()
close(chans.doneShutdown)
}()
}, func(atTerminate func()) {
lock.Lock()
defer lock.Unlock()
select {
case <-chans.readyForTerminate:
default:
close(chans.readyForTerminate)
}
chans.queueTerminate = append(chans.queueTerminate, atTerminate)
})
return chans
}
// call to shutdown and terminate the queue associated with the channels
doTerminate := func(chans *channels, lock *sync.Mutex) {
<-chans.readyForTerminate
lock.Lock()
callbacks := []func(){}
callbacks = append(callbacks, chans.queueTerminate...)
lock.Unlock()
for _, callback := range callbacks {
callback()
}
}
mapLock := sync.Mutex{}
executedInitial := map[string][]string{}
hasInitial := map[string][]string{}
fillQueue := func(name string, done chan struct{}) {
t.Run("Initial Filling: "+name, func(t *testing.T) {
lock := sync.Mutex{}
startAt100Queued := make(chan struct{})
stopAt20Shutdown := make(chan struct{}) // stop and shutdown at the 20th item
handle := func(data ...Data) []Data {
<-startAt100Queued
for _, datum := range data {
s := datum.(string)
mapLock.Lock()
executedInitial[name] = append(executedInitial[name], s)
mapLock.Unlock()
if s == "task-20" {
close(stopAt20Shutdown)
}
}
return nil
}
q := newQueue(name, handle)
// add 100 tasks to the queue
for i := 0; i < 100; i++ {
_ = q.Push("task-" + strconv.Itoa(i))
}
close(startAt100Queued)
chans := runQueue(q, &lock)
<-chans.readyForShutdown
<-stopAt20Shutdown
close(chans.signalShutdown)
<-chans.doneShutdown
_ = q.Push("final")
// check which tasks are still in the queue
for i := 0; i < 100; i++ {
if has, _ := q.(UniqueQueue).Has("task-" + strconv.Itoa(i)); has {
mapLock.Lock()
hasInitial[name] = append(hasInitial[name], "task-"+strconv.Itoa(i))
mapLock.Unlock()
}
}
if has, _ := q.(UniqueQueue).Has("final"); has {
mapLock.Lock()
hasInitial[name] = append(hasInitial[name], "final")
mapLock.Unlock()
} else {
assert.Fail(t, "UnqueQueue %s should have \"final\"", name)
}
doTerminate(chans, &lock)
mapLock.Lock()
assert.Equal(t, 101, len(executedInitial[name])+len(hasInitial[name]))
mapLock.Unlock()
})
close(done)
}
doneA := make(chan struct{})
doneB := make(chan struct{})
go fillQueue("QueueA", doneA)
go fillQueue("QueueB", doneB)
<-doneA
<-doneB
executedEmpty := map[string][]string{}
hasEmpty := map[string][]string{}
emptyQueue := func(name string, done chan struct{}) {
t.Run("Empty Queue: "+name, func(t *testing.T) {
lock := sync.Mutex{}
stop := make(chan struct{})
// collect the tasks that have been executed
handle := func(data ...Data) []Data {
lock.Lock()
for _, datum := range data {
mapLock.Lock()
executedEmpty[name] = append(executedEmpty[name], datum.(string))
mapLock.Unlock()
if datum.(string) == "final" {
close(stop)
}
}
lock.Unlock()
return nil
}
q := newQueue(name, handle)
chans := runQueue(q, &lock)
<-chans.readyForShutdown
<-stop
close(chans.signalShutdown)
<-chans.doneShutdown
// check which tasks are still in the queue
for i := 0; i < 100; i++ {
if has, _ := q.(UniqueQueue).Has("task-" + strconv.Itoa(i)); has {
mapLock.Lock()
hasEmpty[name] = append(hasEmpty[name], "task-"+strconv.Itoa(i))
mapLock.Unlock()
}
}
doTerminate(chans, &lock)
mapLock.Lock()
assert.Equal(t, 101, len(executedInitial[name])+len(executedEmpty[name]))
assert.Equal(t, 0, len(hasEmpty[name]))
mapLock.Unlock()
})
close(done)
}
doneA = make(chan struct{})
doneB = make(chan struct{})
go emptyQueue("QueueA", doneA)
go emptyQueue("QueueB", doneB)
<-doneA
<-doneB
mapLock.Lock()
t.Logf("TestPersistableChannelUniqueQueue executedInitiallyA=%v, executedInitiallyB=%v, executedToEmptyA=%v, executedToEmptyB=%v",
len(executedInitial["QueueA"]), len(executedInitial["QueueB"]), len(executedEmpty["QueueA"]), len(executedEmpty["QueueB"]))
// reset and rerun
executedInitial = map[string][]string{}
hasInitial = map[string][]string{}
executedEmpty = map[string][]string{}
hasEmpty = map[string][]string{}
mapLock.Unlock()
doneA = make(chan struct{})
doneB = make(chan struct{})
go fillQueue("QueueA", doneA)
go fillQueue("QueueB", doneB)
<-doneA
<-doneB
doneA = make(chan struct{})
doneB = make(chan struct{})
go emptyQueue("QueueA", doneA)
go emptyQueue("QueueB", doneB)
<-doneA
<-doneB
mapLock.Lock()
t.Logf("TestPersistableChannelUniqueQueue executedInitiallyA=%v, executedInitiallyB=%v, executedToEmptyA=%v, executedToEmptyB=%v",
len(executedInitial["QueueA"]), len(executedInitial["QueueB"]), len(executedEmpty["QueueA"]), len(executedEmpty["QueueB"]))
mapLock.Unlock()
}

View File

@ -385,10 +385,10 @@ func NewFuncMap() []template.FuncMap {
// the table is NOT sorted with this header
return ""
},
"RenderLabel": func(label *issues_model.Label) template.HTML {
return template.HTML(RenderLabel(label))
"RenderLabel": func(ctx context.Context, label *issues_model.Label) template.HTML {
return template.HTML(RenderLabel(ctx, label))
},
"RenderLabels": func(labels []*issues_model.Label, repoLink string) template.HTML {
"RenderLabels": func(ctx context.Context, labels []*issues_model.Label, repoLink string) template.HTML {
htmlCode := `<span class="labels-list">`
for _, label := range labels {
// Protect against nil value in labels - shouldn't happen but would cause a panic if so
@ -396,7 +396,7 @@ func NewFuncMap() []template.FuncMap {
continue
}
htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d'>%s</a> ",
repoLink, label.ID, RenderLabel(label))
repoLink, label.ID, RenderLabel(ctx, label))
}
htmlCode += "</span>"
return template.HTML(htmlCode)
@ -808,7 +808,7 @@ func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[str
}
// RenderLabel renders a label
func RenderLabel(label *issues_model.Label) string {
func RenderLabel(ctx context.Context, label *issues_model.Label) string {
labelScope := label.ExclusiveScope()
textColor := "#111"
@ -821,12 +821,12 @@ func RenderLabel(label *issues_model.Label) string {
if labelScope == "" {
// Regular label
return fmt.Sprintf("<div class='ui label' style='color: %s !important; background-color: %s !important' title='%s'>%s</div>",
textColor, label.Color, description, RenderEmoji(label.Name))
textColor, label.Color, description, RenderEmoji(ctx, label.Name))
}
// Scoped label
scopeText := RenderEmoji(labelScope)
itemText := RenderEmoji(label.Name[len(labelScope)+1:])
scopeText := RenderEmoji(ctx, labelScope)
itemText := RenderEmoji(ctx, label.Name[len(labelScope)+1:])
itemColor := label.Color
scopeColor := label.Color
@ -869,8 +869,9 @@ func RenderLabel(label *issues_model.Label) string {
}
// RenderEmoji renders html text with emoji post processors
func RenderEmoji(text string) template.HTML {
renderedText, err := markup.RenderEmoji(template.HTMLEscapeString(text))
func RenderEmoji(ctx context.Context, text string) template.HTML {
renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ctx},
template.HTMLEscapeString(text))
if err != nil {
log.Error("RenderEmoji: %v", err)
return template.HTML("")

View File

@ -205,7 +205,7 @@ func DeleteProject(ctx *context.Context) {
}
ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": ctx.Repo.RepoLink + "/projects",
"redirect": ctx.ContextUser.HomeLink() + "/-/projects",
})
}

View File

@ -474,7 +474,7 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader, ski
sb := strings.Builder{}
// OK let's set a reasonable buffer size.
// This should be let's say at least the size of maxLineCharacters or 4096 whichever is larger.
// This should be at least the size of maxLineCharacters or 4096 whichever is larger.
readerSize := maxLineCharacters
if readerSize < 4096 {
readerSize = 4096

View File

@ -4,7 +4,7 @@
{{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}}
</a>
<a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects">
{{svg "octicon-project"}} {{.locale.Tr "user.projects"}}
{{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}}
</a>
{{if .IsPackageEnabled}}
<a class="item" href="{{$.Org.HomeLink}}/-/packages">

View File

@ -12,7 +12,7 @@
{{template "base/alert" .}}
<div class="ui compact tiny menu">
<a class="item{{if not .IsShowClosed}} active{{end}}" href="{{$.Link}}?state=open">
{{svg "octicon-project" 16 "gt-mr-3"}}
{{svg "octicon-project-symlink" 16 "gt-mr-3"}}
{{JsPrettyNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
</a>
<a class="item{{if .IsShowClosed}} active{{end}}" href="{{$.Link}}?state=closed">
@ -38,7 +38,7 @@
<div class="milestone list">
{{range .Projects}}
<li class="item">
{{svg "octicon-project"}} <a href="{{$.Link}}/{{.ID}}">{{.Title}}</a>
{{svg "octicon-project-symlink"}} <a href="{{.Link}}">{{.Title}}</a>
<div class="meta">
{{$closedDate:= TimeSinceUnix .ClosedDateUnix $.locale}}
{{if .IsClosed}}

View File

@ -234,7 +234,7 @@
{{if or .Labels .Assignees}}
<div class="extra content labels-list gt-p-0 gt-pt-2">
{{range .Labels}}
<a target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{RenderLabel .}}</a>
<a target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{RenderLabel $.Context .}}</a>
{{end}}
<div class="right floated">
{{range .Assignees}}

View File

@ -7,7 +7,7 @@
<div class="issue-item-main gt-f1 gt-fc gt-df">
<div class="issue-item-top-row">
<a class="index gt-ml-0 gt-mr-2" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
{{.Title}}
{{- .Title -}}
</a>
<span class="ui label">
{{if .RefLink}}

View File

@ -129,7 +129,7 @@
<span class="ui green label">{{$.locale.Tr "repo.activity.published_release_label"}}</span>
{{.TagName}}
{{if not .IsTag}}
<a class="title" href="{{$.RepoLink}}/src/{{.TagName | PathEscapeSegments}}">{{.Title | RenderEmoji}}</a>
<a class="title" href="{{$.RepoLink}}/src/{{.TagName | PathEscapeSegments}}">{{.Title | RenderEmoji $.Context}}</a>
{{end}}
{{TimeSinceUnix .CreatedUnix $.locale}}
</p>
@ -149,7 +149,7 @@
{{range .Activity.MergedPRs}}
<p class="desc">
<span class="ui purple label">{{$.locale.Tr "repo.activity.merged_prs_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji}}</a>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji $.Context}}</a>
{{TimeSinceUnix .MergedUnix $.locale}}
</p>
{{end}}
@ -168,7 +168,7 @@
{{range .Activity.OpenedPRs}}
<p class="desc">
<span class="ui green label">{{$.locale.Tr "repo.activity.opened_prs_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji}}</a>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji $.Context}}</a>
{{TimeSinceUnix .Issue.CreatedUnix $.locale}}
</p>
{{end}}
@ -187,7 +187,7 @@
{{range .Activity.ClosedIssues}}
<p class="desc">
<span class="ui red label">{{$.locale.Tr "repo.activity.closed_issue_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji}}</a>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context}}</a>
{{TimeSinceUnix .ClosedUnix $.locale}}
</p>
{{end}}
@ -206,7 +206,7 @@
{{range .Activity.OpenedIssues}}
<p class="desc">
<span class="ui green label">{{$.locale.Tr "repo.activity.new_issue_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji}}</a>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context}}</a>
{{TimeSinceUnix .CreatedUnix $.locale}}
</p>
{{end}}
@ -227,9 +227,9 @@
<span class="ui green label">{{$.locale.Tr "repo.activity.unresolved_conv_label"}}</span>
#{{.Index}}
{{if .IsPull}}
<a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Title | RenderEmoji}}</a>
<a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Title | RenderEmoji $.Context}}</a>
{{else}}
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji}}</a>
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context}}</a>
{{end}}
{{TimeSinceUnix .UpdatedUnix $.locale}}
</p>

View File

@ -61,7 +61,7 @@
<td class="message">
<span class="message-wrapper">
{{if $.PageIsWiki}}
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{.Summary | RenderEmoji}}</span>
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{.Summary | RenderEmoji $.Context}}</span>
{{else}}
{{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape .ID.String)}}
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject $.Context .Message $commitRepoLink $commitLink $.Repository.ComposeMetas}}</span>

View File

@ -79,7 +79,7 @@
{{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}}
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} gt-mt-3" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}>
<h4 class="diff-file-header sticky-2nd-row ui top attached normal header gt-df gt-ac gt-sb">
<div class="gt-df gt-ac">
<div class="diff-file-name gt-df gt-ac gt-mr-3">
<a role="button" class="fold-file muted gt-mr-2" {{if not $isExpandable}}style="visibility: hidden"{{end}}>
{{if $file.ShouldBeHidden}}
{{svg "octicon-chevron-right" 18}}
@ -96,7 +96,7 @@
{{template "repo/diff/stats" dict "file" . "root" $}}
{{end}}
</div>
<span class="file gt-mono"><a class="muted" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}} &rarr; {{end}}{{$file.Name}}</a>{{if .IsLFSFile}} ({{$.locale.Tr "repo.stored_lfs"}}){{end}}</span>
<span class="file gt-mono"><a class="muted file-link" title="{{if $file.IsRenamed}}{{$file.OldName}} &rarr; {{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}} &rarr; {{end}}{{$file.Name}}</a>{{if .IsLFSFile}} ({{$.locale.Tr "repo.stored_lfs"}}){{end}}</span>
{{if $file.IsGenerated}}
<span class="ui label gt-ml-3">{{$.locale.Tr "repo.diff.generated"}}</span>
{{end}}

View File

@ -1,9 +1,9 @@
{{$file := .file}}
{{range $j, $section := $file.Sections}}
{{range $k, $line := $section.Lines}}
{{if or $.root.AfterCommitID (ne .GetType 4)}}
<tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}" data-line-type="{{DiffLineTypeToStr .GetType}}">
{{if eq .GetType 4}}
<tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}" data-line-type="{{DiffLineTypeToStr .GetType}}">
{{if eq .GetType 4}}
{{if $.root.AfterCommitID}}
<td colspan="2" class="lines-num">
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5)}}
<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=down&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}">
@ -22,35 +22,38 @@
{{end}}
</td>
{{else}}
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$file.NameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$file.NameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
{{/* for code file preview page or comment diffs on pull comment pages, do not show the expansion arrows */}}
<td colspan="2" class="lines-num"></td>
{{end}}
{{$inlineDiff := $section.GetComputedInlineDiffFor $line $.root.locale -}}
<td class="lines-escape">{{if $inlineDiff.EscapeStatus.Escaped}}<a href="" class="toggle-escape-button" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff "locale" $.root.locale}}"></a>{{end}}</td>
<td class="lines-type-marker"><span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
{{if eq .GetType 4}}
<td class="chroma lines-code blob-hunk">{{/*
*/}}{{template "repo/diff/section_code" dict "diff" $inlineDiff "locale" $.root.locale}}{{/*
*/}}</td>
{{else}}
<td class="chroma lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}">{{/*
*/}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/*
*/}}<a class="ui primary button add-code-comment add-code-comment-{{if $line.RightIdx}}right{{else}}left{{end}}{{if (not $line.CanComment)}} invisible{{end}}" data-side="{{if $line.RightIdx}}right{{else}}left{{end}}" data-idx="{{if $line.RightIdx}}{{$line.RightIdx}}{{else}}{{$line.LeftIdx}}{{end}}">{{/*
*/}}{{svg "octicon-plus"}}{{/*
*/}}</a>{{/*
*/}}{{end}}{{/*
*/}}{{template "repo/diff/section_code" dict "diff" $inlineDiff "locale" $.root.locale}}{{/*
*/}}</td>
{{end}}
</tr>
{{if gt (len $line.Comments) 0}}
<tr class="add-comment" data-line-type="{{DiffLineTypeToStr .GetType}}">
<td colspan="3" class="lines-num"></td>
<td class="add-comment-left add-comment-right" colspan="5">
{{template "repo/diff/conversation" mergeinto $.root "comments" $line.Comments}}
</td>
</tr>
{{else}}
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$file.NameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$file.NameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
{{end}}
{{$inlineDiff := $section.GetComputedInlineDiffFor $line $.root.locale -}}
<td class="lines-escape">{{if $inlineDiff.EscapeStatus.Escaped}}<a href="" class="toggle-escape-button" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff "locale" $.root.locale}}"></a>{{end}}</td>
<td class="lines-type-marker"><span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
{{if eq .GetType 4}}
<td class="chroma lines-code blob-hunk">{{/*
*/}}{{template "repo/diff/section_code" dict "diff" $inlineDiff "locale" $.root.locale}}{{/*
*/}}</td>
{{else}}
<td class="chroma lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}">{{/*
*/}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/*
*/}}<a class="ui primary button add-code-comment add-code-comment-{{if $line.RightIdx}}right{{else}}left{{end}}{{if (not $line.CanComment)}} invisible{{end}}" data-side="{{if $line.RightIdx}}right{{else}}left{{end}}" data-idx="{{if $line.RightIdx}}{{$line.RightIdx}}{{else}}{{$line.LeftIdx}}{{end}}">{{/*
*/}}{{svg "octicon-plus"}}{{/*
*/}}</a>{{/*
*/}}{{end}}{{/*
*/}}{{template "repo/diff/section_code" dict "diff" $inlineDiff "locale" $.root.locale}}{{/*
*/}}</td>
{{end}}
</tr>
{{if gt (len $line.Comments) 0}}
<tr class="add-comment" data-line-type="{{DiffLineTypeToStr .GetType}}">
<td colspan="3" class="lines-num"></td>
<td class="add-comment-left add-comment-right" colspan="5">
{{template "repo/diff/conversation" mergeinto $.root "comments" $line.Comments}}
</td>
</tr>
{{end}}
{{end}}
{{end}}

View File

@ -1,6 +1,6 @@
<div class="diff-file-box">
<div class="ui attached table segment">
<div class="file-body file-code code-view code-diff-unified unicode-escaped">
<div class="file-body file-code code-diff code-diff-unified unicode-escaped">
<table>
<tbody>
{{template "repo/diff/section_unified" dict "file" .File "root" $}}

View File

@ -3,5 +3,5 @@
id="label_{{.label.ID}}"
href="{{.root.RepoLink}}/{{if or .root.IsPull .root.Issue.IsPull}}pulls{{else}}issues{{end}}?labels={{.label.ID}}"{{/* FIXME: use .root.Issue.Link or create .root.Link */}}
>
{{RenderLabel .label}}
{{RenderLabel $.Context .label}}
</a>

View File

@ -31,8 +31,8 @@
<li class="item">
<div class="ui grid middle aligned">
<div class="nine wide column">
{{RenderLabel .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}
{{RenderLabel $.Context .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
</div>
<div class="four wide column">
{{if $.PageIsOrgSettingsLabels}}
@ -70,8 +70,8 @@
<li class="item">
<div class="ui grid middle aligned">
<div class="nine wide column">
{{RenderLabel .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}
{{RenderLabel $.Context .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
</div>
<div class="four wide column">
<a class="ui left open-issues" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{svg "octicon-issue-opened"}} {{$.locale.Tr "repo.issues.label_open_issues" .NumOpenRepoIssues}}</a>

View File

@ -57,7 +57,7 @@
<div class="ui divider"></div>
{{end}}
{{$previousExclusiveScope = $exclusiveScope}}
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel .}}</a>
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel $.Context .}}</a>
{{end}}
</div>
</div>
@ -231,7 +231,7 @@
{{end}}
{{$previousExclusiveScope = $exclusiveScope}}
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
{{if contain $.SelLabelIDs .ID}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel .}}
{{if contain $.SelLabelIDs .ID}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel $.Context .}}
</div>
{{end}}
</div>

View File

@ -58,7 +58,7 @@
<span class="info">{{.locale.Tr "repo.issues.filter_label_exclude" | Safe}}</span>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_no_select"}}</a>
{{range .Labels}}
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}} {{RenderLabel .}}</a>
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}} {{RenderLabel $.Context .}}</a>
{{end}}
</div>
</div>
@ -161,7 +161,7 @@
<div class="menu">
{{range .Labels}}
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
{{if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}} {{RenderLabel .}}
{{if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}} {{RenderLabel $.Context .}}
</div>
{{end}}
</div>

View File

@ -60,8 +60,8 @@
<div class="ui divider"></div>
{{end}}
{{$previousExclusiveScope = $exclusiveScope}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel $.Context .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}</a>
{{end}}
<div class="ui divider"></div>
@ -72,8 +72,8 @@
<div class="ui divider"></div>
{{end}}
{{$previousExclusiveScope = $exclusiveScope}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel $.Context .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}</a>
{{end}}
{{else}}
<div class="header" style="text-transform: none;font-size:14px;">{{.locale.Tr "repo.issues.new.no_items"}}</div>
@ -176,8 +176,8 @@
{{.locale.Tr "repo.issues.new.open_projects"}}
</div>
{{range .OpenProjects}}
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{$.RepoLink}}/projects/{{.ID}}">
{{svg "octicon-project" 18 "gt-mr-3"}}
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link}}">
{{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "gt-mr-3"}}{{else}}{{svg "octicon-project" 18 "gt-mr-3"}}{{end}}
{{.Title}}
</a>
{{end}}
@ -188,8 +188,8 @@
{{.locale.Tr "repo.issues.new.closed_projects"}}
</div>
{{range .ClosedProjects}}
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{$.RepoLink}}/projects/{{.ID}}">
{{svg "octicon-project" 18 "gt-mr-3"}}
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link}}">
{{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "gt-mr-3"}}{{else}}{{svg "octicon-project" 18 "gt-mr-3"}}{{end}}
{{.Title}}
</a>
{{end}}
@ -201,8 +201,8 @@
<span class="no-select item {{if .Project}}gt-hidden{{end}}">{{.locale.Tr "repo.issues.new.no_projects"}}</span>
<div class="selected">
{{if .Project}}
<a class="item muted sidebar-item-link" href="{{.RepoLink}}/projects/{{.Project.ID}}">
{{svg "octicon-project" 18 "gt-mr-3"}}
<a class="item muted sidebar-item-link" href="{{.Project.Link}}">
{{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "gt-mr-3"}}{{else}}{{svg "octicon-project" 18 "gt-mr-3"}}{{end}}
{{.Project.Title}}
</a>
{{end}}

View File

@ -180,11 +180,11 @@
<span class="text grey muted-links">
{{template "shared/user/authorlink" .Poster}}
{{if and .AddedLabels (not .RemovedLabels)}}
{{$.locale.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels .AddedLabels $.RepoLink) $createdStr | Safe}}
{{$.locale.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels $.Context .AddedLabels $.RepoLink) $createdStr | Safe}}
{{else if and (not .AddedLabels) .RemovedLabels}}
{{$.locale.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels .RemovedLabels $.RepoLink) $createdStr | Safe}}
{{$.locale.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels $.Context .RemovedLabels $.RepoLink) $createdStr | Safe}}
{{else}}
{{$.locale.Tr "repo.issues.add_remove_labels" (RenderLabels .AddedLabels $.RepoLink) (RenderLabels .RemovedLabels $.RepoLink) $createdStr | Safe}}
{{$.locale.Tr "repo.issues.add_remove_labels" (RenderLabels $.Context .AddedLabels $.RepoLink) (RenderLabels $.Context .RemovedLabels $.RepoLink) $createdStr | Safe}}
{{end}}
</span>
</div>
@ -231,7 +231,7 @@
{{template "shared/user/avatarlink" Dict "Context" $.Context "user" .Poster}}
<span class="text grey muted-links">
{{template "shared/user/authorlink" .Poster}}
{{$.locale.Tr "repo.issues.change_title_at" (.OldTitle|RenderEmoji) (.NewTitle|RenderEmoji) $createdStr | Safe}}
{{$.locale.Tr "repo.issues.change_title_at" (.OldTitle|RenderEmoji $.Context) (.NewTitle|RenderEmoji $.Context) $createdStr | Safe}}
</span>
</div>
{{else if eq .Type 11}}

View File

@ -130,8 +130,8 @@
<div class="ui divider"></div>
{{end}}
{{$previousExclusiveScope = $exclusiveScope}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel $.Context .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}</a>
{{end}}
<div class="ui divider"></div>
{{$previousExclusiveScope := "_no_scope"}}
@ -141,8 +141,8 @@
<div class="ui divider"></div>
{{end}}
{{$previousExclusiveScope = $exclusiveScope}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel $.Context .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}</a>
{{end}}
{{else}}
<div class="header" style="text-transform: none;font-size:14px;">{{.locale.Tr "repo.issues.new.no_items"}}</div>
@ -487,8 +487,8 @@
{{range .BlockingDependencies}}
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} gt-df gt-ac gt-sb">
<div class="item-left gt-df gt-jc gt-fc gt-f1">
<a class="title tooltip" href="{{.Issue.Link}}" data-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji}}">
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji}}
<a class="title tooltip" href="{{.Issue.Link}}" data-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
</a>
<div class="text small">
{{.Repository.OwnerName}}/{{.Repository.Name}}
@ -514,8 +514,8 @@
{{range .BlockedByDependencies}}
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} gt-df gt-ac gt-sb">
<div class="item-left gt-df gt-jc gt-fc gt-f1">
<a class="title tooltip" href="{{.Issue.Link}}" data-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji}}">
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji}}
<a class="title tooltip" href="{{.Issue.Link}}" data-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
</a>
<div class="text small">
{{.Repository.OwnerName}}/{{.Repository.Name}}

View File

@ -40,7 +40,7 @@
<div class="milestone list">
{{range .Projects}}
<li class="item">
{{svg "octicon-project"}} <a href="{{$.RepoLink}}/projects/{{.ID}}">{{.Title}}</a>
{{svg "octicon-project"}} <a href="{{.Link}}">{{.Title}}</a>
<div class="meta">
{{$closedDate:= TimeSinceUnix .ClosedDateUnix $.locale}}
{{if .IsClosed}}

View File

@ -245,7 +245,7 @@
{{if or .Labels .Assignees}}
<div class="extra content labels-list gt-p-0 gt-pt-2">
{{range .Labels}}
<a target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{RenderLabel .}}</a>
<a target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{RenderLabel $.Context .}}</a>
{{end}}
<div class="right floated">
{{range .Assignees}}

View File

@ -19,7 +19,7 @@
<td class="message">
<span class="truncate">
<a href="{{$.RepoLink}}/commit/{{.SHA}}" title="{{.Summary | RenderEmojiPlain}}">
{{.Summary | RenderEmoji}}
{{.Summary | RenderEmoji $.Context}}
</a>
</span>
</td>

View File

@ -34,7 +34,7 @@
</div>
<div class="issue-item-main gt-f1 gt-fc gt-df">
<div class="issue-item-top-row">
<a class="title gt-tdn issue-title" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">{{RenderEmoji .Title | RenderCodeBlock}}</a>
<a class="title gt-tdn issue-title" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">{{RenderEmoji $.Context .Title | RenderCodeBlock}}</a>
{{if .IsPull}}
{{if (index $.CommitStatuses .PullRequest.ID)}}
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID) "root" $}}
@ -42,7 +42,7 @@
{{end}}
<span class="labels-list gt-ml-2">
{{range .Labels}}
<a href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{RenderLabel .}}</a>
<a href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{RenderLabel $.Context .}}</a>
{{end}}
</span>
</div>
@ -87,8 +87,8 @@
</a>
{{end}}
{{if .Project}}
<a class="project" {{if $.RepoLink}}href="{{$.RepoLink}}/projects/{{.Project.ID}}"{{else}}href="{{.Repo.Link}}/projects/{{.Project.ID}}"{{end}}>
{{svg "octicon-project" 14 "gt-mr-2"}}{{.Project.Title}}
<a class="project" href="{{.Project.Link}}">
{{if .Project.IsOrganizationProject}}{{svg "octicon-project-symlink" 14 "gt-mr-2"}}{{else}}{{svg "octicon-project" 14 "gt-mr-2"}}{{end}}{{.Project.Title}}
</a>
{{end}}
{{if .Ref}}

View File

@ -72,7 +72,7 @@
{{$index := index .GetIssueInfos 0}}
{{$.locale.Tr "action.comment_pull" ((printf "%s/pulls/%s" .GetRepoLink $index) |Escape) $index (.ShortRepoPath|Escape) | Str2html}}
{{else if eq .GetOpType 24}}
{{$linkText := .Content | RenderEmoji}}
{{$linkText := .Content | RenderEmoji $.Context}}
{{$.locale.Tr "action.publish_release" (.GetRepoLink|Escape) ((printf "%s/releases/tag/%s" .GetRepoLink .GetTag)|Escape) (.ShortRepoPath|Escape) $linkText | Str2html}}
{{else if eq .GetOpType 25}}
{{$index := index .GetIssueInfos 0}}
@ -99,20 +99,20 @@
</ul>
</div>
{{else if eq .GetOpType 6}}
<span class="text truncate issue title">{{index .GetIssueInfos 1 | RenderEmoji | RenderCodeBlock}}</span>
<span class="text truncate issue title">{{index .GetIssueInfos 1 | RenderEmoji $.Context | RenderCodeBlock}}</span>
{{else if eq .GetOpType 7}}
<span class="text truncate issue title">{{index .GetIssueInfos 1 | RenderEmoji | RenderCodeBlock}}</span>
<span class="text truncate issue title">{{index .GetIssueInfos 1 | RenderEmoji $.Context | RenderCodeBlock}}</span>
{{else if or (eq .GetOpType 10) (eq .GetOpType 21) (eq .GetOpType 22) (eq .GetOpType 23)}}
<a href="{{.GetCommentLink}}" class="text truncate issue title">{{.GetIssueTitle | RenderEmoji | RenderCodeBlock}}</a>
<a href="{{.GetCommentLink}}" class="text truncate issue title">{{.GetIssueTitle | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{$comment := index .GetIssueInfos 1}}
{{if gt (len $comment) 0}}<p class="text light grey">{{$comment | RenderEmoji}}</p>{{end}}
{{if gt (len $comment) 0}}<p class="text light grey">{{$comment | RenderEmoji $.Context}}</p>{{end}}
{{else if eq .GetOpType 11}}
<p class="text light grey">{{index .GetIssueInfos 1}}</p>
{{else if or (eq .GetOpType 12) (eq .GetOpType 13) (eq .GetOpType 14) (eq .GetOpType 15)}}
<span class="text truncate issue title">{{.GetIssueTitle | RenderEmoji | RenderCodeBlock}}</span>
<span class="text truncate issue title">{{.GetIssueTitle | RenderEmoji $.Context | RenderCodeBlock}}</span>
{{else if eq .GetOpType 25}}
<p class="text light grey">{{$.locale.Tr "action.review_dismissed_reason"}}</p>
<p class="text light grey">{{index .GetIssueInfos 2 | RenderEmoji}}</p>
<p class="text light grey">{{index .GetIssueInfos 2 | RenderEmoji $.Context}}</p>
{{end}}
<p class="text italic light grey">{{TimeSince .GetCreate $.locale}}</p>
</div>

View File

@ -23,7 +23,7 @@
{{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}}
</a>
<a href="{{.ContextUser.HomeLink}}/-/projects" class="{{if .PageIsViewProjects}}active {{end}}item">
{{svg "octicon-project"}} {{.locale.Tr "user.projects"}}
{{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}}
</a>
{{if (not .UnitPackagesGlobalDisabled)}}
<a href="{{.ContextUser.HomeLink}}/-/packages" class="{{if .IsPackagesPage}}active {{end}}item">

View File

@ -107,7 +107,7 @@
{{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}}
</a>
<a href="{{.Owner.HomeLink}}/-/projects" class="{{if eq .TabName "projects"}}active {{end}}item">
{{svg "octicon-project"}} {{.locale.Tr "user.projects"}}
{{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}}
</a>
{{if .IsPackageEnabled}}
<a class='{{if eq .TabName "packages"}}active {{end}}item' href="{{.Owner.HomeLink}}/-/packages">

View File

@ -31,6 +31,7 @@ export function initContextPopups() {
createTippy(this, {
content: el,
interactive: true,
interactiveBorder: 5,
onShow: () => {
el.firstChild.dispatchEvent(new CustomEvent('us-load-context-popup', {detail: {owner, repo, index}}));
}

View File

@ -145,7 +145,6 @@ export function initRepoCommentForm() {
const clickedItem = $(this);
const scope = $(this).attr('data-scope');
const canRemoveScope = e.altKey;
$(this).parent().find('.item').each(function () {
if (scope) {
@ -153,11 +152,7 @@ export function initRepoCommentForm() {
if ($(this).attr('data-scope') !== scope) {
return true;
}
if ($(this).is(clickedItem)) {
if (!canRemoveScope && $(this).hasClass('checked')) {
return true;
}
} else if (!$(this).hasClass('checked')) {
if (!$(this).is(clickedItem) && !$(this).hasClass('checked')) {
return true;
}
} else if (!$(this).is(clickedItem)) {

View File

@ -1663,8 +1663,14 @@
align-items: center;
.file {
flex: 1;
word-break: break-all;
min-width: 0;
.file-link {
max-width: fit-content;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
.button {
@ -3276,6 +3282,15 @@ td.blob-excerpt {
position: static;
}
}
.diff-file-header-actions {
flex-shrink: 0;
}
.diff-file-name {
flex: auto;
min-width: 100px;
}
}
.diff-file-body {