diff --git a/Dockerfile b/Dockerfile index fad8ae1790d5..3623085e4744 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ #Build stage -FROM golang:1.20-alpine3.17 AS build-env +FROM docker.io/library/golang:1.20-alpine3.17 AS build-env ARG GOPROXY ENV GOPROXY ${GOPROXY:-direct} @@ -23,7 +23,7 @@ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ # Begin env-to-ini build RUN go build contrib/environment-to-ini/environment-to-ini.go -FROM alpine:3.17 +FROM docker.io/library/alpine:3.17 LABEL maintainer="maintainers@gitea.io" EXPOSE 22 3000 diff --git a/Dockerfile.rootless b/Dockerfile.rootless index 98051f7ddba7..67bd9c4880fb 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -1,5 +1,5 @@ #Build stage -FROM golang:1.20-alpine3.17 AS build-env +FROM docker.io/library/golang:1.20-alpine3.17 AS build-env ARG GOPROXY ENV GOPROXY ${GOPROXY:-direct} @@ -23,7 +23,7 @@ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ # Begin env-to-ini build RUN go build contrib/environment-to-ini/environment-to-ini.go -FROM alpine:3.17 +FROM docker.io/library/alpine:3.17 LABEL maintainer="maintainers@gitea.io" EXPOSE 2222 3000 diff --git a/Makefile b/Makefile index 6dedea12cc0f..56ac44eb1174 100644 --- a/Makefile +++ b/Makefile @@ -747,7 +747,7 @@ generate-go: $(TAGS_PREREQ) .PHONY: security-check security-check: - go run $(GOVULNCHECK_PACKAGE) -v ./... + go run $(GOVULNCHECK_PACKAGE) ./... $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@ diff --git a/docs/content/doc/administration/backup-and-restore.en-us.md b/docs/content/doc/administration/backup-and-restore.en-us.md index 16e8654b74c5..b1e5a6aee013 100644 --- a/docs/content/doc/administration/backup-and-restore.en-us.md +++ b/docs/content/doc/administration/backup-and-restore.en-us.md @@ -1,6 +1,6 @@ --- date: "2017-01-01T16:00:00+02:00" -title: "Usage: Backup and Restore" +title: "Backup and Restore" slug: "backup-and-restore" weight: 11 toc: false diff --git a/docs/content/doc/administration/command-line.en-us.md b/docs/content/doc/administration/command-line.en-us.md index 20b788510d26..d3362e573138 100644 --- a/docs/content/doc/administration/command-line.en-us.md +++ b/docs/content/doc/administration/command-line.en-us.md @@ -1,6 +1,6 @@ --- date: "2017-01-01T16:00:00+02:00" -title: "Usage: Command Line" +title: "Gitea Command Line" slug: "command-line" weight: 1 toc: false diff --git a/docs/content/doc/administration/email-setup.en-us.md b/docs/content/doc/administration/email-setup.en-us.md index 05a69d56ade2..b8a3324f1644 100644 --- a/docs/content/doc/administration/email-setup.en-us.md +++ b/docs/content/doc/administration/email-setup.en-us.md @@ -1,6 +1,6 @@ --- date: "2019-10-15T10:10:00+05:00" -title: "Usage: Email setup" +title: "Email setup" slug: "email-setup" weight: 12 toc: false diff --git a/docs/content/doc/administration/fail2ban-setup.en-us.md b/docs/content/doc/administration/fail2ban-setup.en-us.md index 746222420a92..420c6ae54924 100644 --- a/docs/content/doc/administration/fail2ban-setup.en-us.md +++ b/docs/content/doc/administration/fail2ban-setup.en-us.md @@ -1,6 +1,6 @@ --- date: "2018-05-11T11:00:00+02:00" -title: "Usage: Setup fail2ban" +title: "Fail2ban Setup " slug: "fail2ban-setup" weight: 16 toc: false diff --git a/docs/content/doc/administration/fail2ban-setup.zh-cn.md b/docs/content/doc/administration/fail2ban-setup.zh-cn.md index 37a0838b24ae..920d3f4af2f7 100644 --- a/docs/content/doc/administration/fail2ban-setup.zh-cn.md +++ b/docs/content/doc/administration/fail2ban-setup.zh-cn.md @@ -1,6 +1,6 @@ --- date: "2022-08-01T00:00:00+00:00" -title: "使用: 设置 Fail2ban" +title: "设置 Fail2ban" slug: "fail2ban-setup" weight: 16 toc: false diff --git a/docs/content/doc/administration/git-lfs-support.en-us.md b/docs/content/doc/administration/git-lfs-support.en-us.md index 86cc3a502892..95f4b95887f5 100644 --- a/docs/content/doc/administration/git-lfs-support.en-us.md +++ b/docs/content/doc/administration/git-lfs-support.en-us.md @@ -1,6 +1,6 @@ --- date: "2019-10-06T08:00:00+05:00" -title: "Usage: Git LFS setup" +title: "Git LFS setup" slug: "git-lfs-setup" weight: 12 toc: false diff --git a/docs/content/doc/administration/https-support.en-us.md b/docs/content/doc/administration/https-support.en-us.md index fd7badc64aa0..f5cd28682312 100644 --- a/docs/content/doc/administration/https-support.en-us.md +++ b/docs/content/doc/administration/https-support.en-us.md @@ -1,6 +1,6 @@ --- date: "2018-06-02T11:00:00+02:00" -title: "Usage: HTTPS setup" +title: "HTTPS setup" slug: "https-setup" weight: 12 toc: false diff --git a/docs/content/doc/administration/logging-documentation.en-us.md b/docs/content/doc/administration/logging-documentation.en-us.md index 08e7c9b15c7a..13de8ab882bf 100644 --- a/docs/content/doc/administration/logging-documentation.en-us.md +++ b/docs/content/doc/administration/logging-documentation.en-us.md @@ -1,6 +1,6 @@ --- date: "2019-04-02T17:06:00+01:00" -title: "Advanced: Logging Configuration" +title: "Logging Configuration" slug: "logging-configuration" weight: 40 toc: false diff --git a/docs/content/doc/administration/reverse-proxies.en-us.md b/docs/content/doc/administration/reverse-proxies.en-us.md index d692473fe357..5132ee83f147 100644 --- a/docs/content/doc/administration/reverse-proxies.en-us.md +++ b/docs/content/doc/administration/reverse-proxies.en-us.md @@ -1,6 +1,6 @@ --- date: "2018-05-22T11:00:00+00:00" -title: "Usage: Reverse Proxies" +title: "Reverse Proxies" slug: "reverse-proxies" weight: 16 toc: false diff --git a/docs/content/doc/administration/search-engines-indexation.en-us.md b/docs/content/doc/administration/search-engines-indexation.en-us.md index 4452fd77f6d1..8c7f79d4b87f 100644 --- a/docs/content/doc/administration/search-engines-indexation.en-us.md +++ b/docs/content/doc/administration/search-engines-indexation.en-us.md @@ -1,6 +1,6 @@ --- date: "2019-12-31T13:55:00+05:00" -title: "Advanced: Search Engines Indexation" +title: "Search Engines Indexation" slug: "search-engines-indexation" weight: 60 toc: false diff --git a/docs/content/doc/installation/comparison.en-us.md b/docs/content/doc/installation/comparison.en-us.md index 819af95be957..d372d1f9ec6d 100644 --- a/docs/content/doc/installation/comparison.en-us.md +++ b/docs/content/doc/installation/comparison.en-us.md @@ -1,6 +1,6 @@ --- date: "2018-05-07T13:00:00+02:00" -title: "Gitea compared to other Git hosting options" +title: "Compared to other Git hosting" slug: "comparison" weight: 5 toc: false diff --git a/docs/content/doc/installation/database-preparation.en-us.md b/docs/content/doc/installation/database-preparation.en-us.md index 1b86df978ab2..edad89b07cc7 100644 --- a/docs/content/doc/installation/database-preparation.en-us.md +++ b/docs/content/doc/installation/database-preparation.en-us.md @@ -15,7 +15,7 @@ menu: # Database Preparation -You need a database to use Gitea. Gitea supports PostgreSQL (>=10), MySQL (>=5.7), SQLite, and MSSQL (>=2008R2 SP3). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. +You need a database to use Gitea. Gitea supports PostgreSQL (>=10), MySQL (>=5.7), SQLite, and MSSQL (>=2008R2 SP3). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter. Database instance can be on same machine as Gitea (local database setup), or on different machine (remote database). diff --git a/docs/content/doc/installation/from-package.en-us.md b/docs/content/doc/installation/from-package.en-us.md index c9df0d21be5f..2615b7d27d11 100644 --- a/docs/content/doc/installation/from-package.en-us.md +++ b/docs/content/doc/installation/from-package.en-us.md @@ -13,12 +13,25 @@ menu: identifier: "install-from-package" --- -# Installation from package - **Table of Contents** {{< toc >}} +# Official packages + +## macOS + +Currently, the only supported method of installation on MacOS is [Homebrew](http://brew.sh/). +Following the [deployment from binary]({{< relref "from-binary.en-us.md" >}}) guide may work, +but is not supported. To install Gitea via `brew`: + +``` +brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea +brew install gitea +``` + +# Unofficial packages + ## Alpine Linux Alpine Linux has [Gitea](https://pkgs.alpinelinux.org/packages?name=gitea&branch=edge) in its community repository which follows the latest stable version. @@ -74,17 +87,6 @@ choco install gitea Or follow the [deployment from binary]({{< relref "from-binary.en-us.md" >}}) guide. -## macOS - -Currently, the only supported method of installation on MacOS is [Homebrew](http://brew.sh/). -Following the [deployment from binary]({{< relref "from-binary.en-us.md" >}}) guide may work, -but is not supported. To install Gitea via `brew`: - -``` -brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea -brew install gitea -``` - ## FreeBSD A FreeBSD port `www/gitea` is available. To install the pre-built binary package: @@ -108,7 +110,7 @@ is in `/usr/local/etc/rc.d/gitea`. To enable Gitea to run as a service, run `sysrc gitea_enable=YES` and start it with `service gitea start`. -## Third-party +## Others Various other third-party packages of Gitea exist. To see a curated list, head over to [awesome-gitea](https://gitea.com/gitea/awesome-gitea/src/branch/master/README.md#user-content-packages). diff --git a/docs/content/doc/installation/from-package.zh-cn.md b/docs/content/doc/installation/from-package.zh-cn.md index 93d3c125d583..e3ed3aa37145 100644 --- a/docs/content/doc/installation/from-package.zh-cn.md +++ b/docs/content/doc/installation/from-package.zh-cn.md @@ -13,12 +13,23 @@ menu: identifier: "install-from-package" --- -# 使用包管理器安装 - **目录** {{< toc >}} +# 官方包管理器 + +## macOS + +macOS 平台下当前我们仅支持通过 `brew` 来安装。如果你没有安装 [Homebrew](http://brew.sh/),你也可以查看 [从二进制安装]({{< relref "from-binary.zh-cn.md" >}})。在你安装了 `brew` 之后, 你可以执行以下命令: + +``` +brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea +brew install gitea +``` + +# 非官方包管理器 + ## Alpine Linux Gitea 已经包含在 Alpine Linux 的[社区存储库](https://pkgs.alpinelinux.org/packages?name=gitea&branch=edge)中,版本与 Gitea 官方保持同步。 @@ -66,15 +77,6 @@ choco install gitea 你也可以 [从二进制安装]({{< relref "from-binary.zh-cn.md" >}}) 。 -## macOS - -macOS 平台下当前我们仅支持通过 `brew` 来安装。如果你没有安装 [Homebrew](http://brew.sh/),你也可以查看 [从二进制安装]({{< relref "from-binary.zh-cn.md" >}})。在你安装了 `brew` 之后, 你可以执行以下命令: - -``` -brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea -brew install gitea -``` - ## FreeBSD 可以使用 Gitea 的 FreeBSD port `www/gitea`。 请安装预构建的二进制包: diff --git a/docs/content/doc/usage/agit-support.en-us.md b/docs/content/doc/usage/agit-support.en-us.md index c71be6837064..b005e1bd7367 100644 --- a/docs/content/doc/usage/agit-support.en-us.md +++ b/docs/content/doc/usage/agit-support.en-us.md @@ -1,6 +1,6 @@ --- date: " 2022-09-01T20:50:42+0000" -title: "Usage: Agit Setup" +title: "Agit Setup" slug: "agit-setup" weight: 12 toc: false diff --git a/docs/content/doc/usage/issue-pull-request-templates.en-us.md b/docs/content/doc/usage/issue-pull-request-templates.en-us.md index f36037956a5c..107484689b9f 100644 --- a/docs/content/doc/usage/issue-pull-request-templates.en-us.md +++ b/docs/content/doc/usage/issue-pull-request-templates.en-us.md @@ -1,6 +1,6 @@ --- date: "2018-05-10T16:00:00+02:00" -title: "Usage: Issue and Pull Request templates" +title: "Issue and Pull Request templates" slug: "issue-pull-request-templates" weight: 15 toc: false diff --git a/docs/content/doc/usage/labels.en-us.md b/docs/content/doc/usage/labels.en-us.md index 8e5ff1cf8d9f..7c2494971fcc 100644 --- a/docs/content/doc/usage/labels.en-us.md +++ b/docs/content/doc/usage/labels.en-us.md @@ -1,6 +1,6 @@ --- date: "2023-03-04T19:00:00+00:00" -title: "Usage: Labels" +title: "Labels" slug: "labels" weight: 13 toc: false diff --git a/docs/content/doc/usage/linked-references.en-us.md b/docs/content/doc/usage/linked-references.en-us.md index 721c2cf13e2a..335d2654e37a 100644 --- a/docs/content/doc/usage/linked-references.en-us.md +++ b/docs/content/doc/usage/linked-references.en-us.md @@ -1,6 +1,6 @@ --- date: "2019-11-21T17:00:00-03:00" -title: "Usage: Automatically Linked References" +title: "Automatically Linked References" slug: "automatically-linked-references" weight: 15 toc: false diff --git a/docs/content/doc/usage/merge-message-templates.en-us.md b/docs/content/doc/usage/merge-message-templates.en-us.md index c30ac1bfeb40..70710b989e24 100644 --- a/docs/content/doc/usage/merge-message-templates.en-us.md +++ b/docs/content/doc/usage/merge-message-templates.en-us.md @@ -1,6 +1,6 @@ --- date: "2022-08-31T17:35:40+08:00" -title: "Usage: Merge Message templates" +title: "Merge Message templates" slug: "merge-message-templates" weight: 15 toc: false diff --git a/docs/content/doc/usage/pull-request.en-us.md b/docs/content/doc/usage/pull-request.en-us.md index 4150f4427945..c7fea88a5a3e 100644 --- a/docs/content/doc/usage/pull-request.en-us.md +++ b/docs/content/doc/usage/pull-request.en-us.md @@ -1,6 +1,6 @@ --- date: "2018-06-01T19:00:00+02:00" -title: "Usage: Pull Request" +title: "Pull Request" slug: "pull-request" weight: 13 toc: false diff --git a/docs/content/doc/usage/pull-request.zh-tw.md b/docs/content/doc/usage/pull-request.zh-tw.md index ee09e1893be3..b666fece136f 100644 --- a/docs/content/doc/usage/pull-request.zh-tw.md +++ b/docs/content/doc/usage/pull-request.zh-tw.md @@ -1,6 +1,6 @@ --- date: "2018-06-01T19:00:00+02:00" -title: "使用: 合併請求" +title: "合併請求" slug: "pull-request" weight: 13 toc: false diff --git a/docs/content/doc/usage/push-options.en-us.md b/docs/content/doc/usage/push-options.en-us.md deleted file mode 100644 index b58a91c2cc54..000000000000 --- a/docs/content/doc/usage/push-options.en-us.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -date: "2020-07-06T16:00:00+02:00" -title: "Usage: Push Options" -slug: "push-options" -weight: 15 -toc: false -draft: false -menu: - sidebar: - parent: "usage" - name: "Push Options" - weight: 15 - identifier: "push-options" ---- - -# Push Options - -In Gitea `1.13`, support for some [push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt) -were added. - -## Supported Options - -- `repo.private` (true|false) - Change the repository's visibility. - - This is particularly useful when combined with push-to-create. - -- `repo.template` (true|false) - Change whether the repository is a template. - -Example of changing a repository's visibility to public: - -```shell -git push -o repo.private=false -u origin main -``` diff --git a/docs/content/doc/usage/push-options.zh-tw.md b/docs/content/doc/usage/push-options.zh-tw.md deleted file mode 100644 index 558492af7707..000000000000 --- a/docs/content/doc/usage/push-options.zh-tw.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -date: "2020-07-06T16:00:00+02:00" -title: "使用: Push Options" -slug: "push-options" -weight: 15 -toc: false -draft: false -menu: - sidebar: - parent: "usage" - name: "Push Options" - weight: 15 - identifier: "push-options" ---- - -# Push Options - -Gitea 從 `1.13` 版開始支援某些 [push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt) -。 - -## 支援的 Options - -- `repo.private` (true|false) - 修改儲存庫的可見性。 - - 與 push-to-create 一起使用時特別有用。 - -- `repo.template` (true|false) - 修改儲存庫是否為範本儲存庫。 - -以下範例修改儲存庫的可見性為公開: - -```shell -git push -o repo.private=false -u origin main -``` diff --git a/docs/content/doc/usage/push-to-create.en-us.md b/docs/content/doc/usage/push.en-us.md similarity index 56% rename from docs/content/doc/usage/push-to-create.en-us.md rename to docs/content/doc/usage/push.en-us.md index 27b26b16a944..cf858e4e511b 100644 --- a/docs/content/doc/usage/push-to-create.en-us.md +++ b/docs/content/doc/usage/push.en-us.md @@ -1,18 +1,51 @@ --- date: "2020-07-06T16:00:00+02:00" -title: "Usage: Push To Create" -slug: "push-to-create" +title: "Usage: Push" +slug: "push" weight: 15 toc: false draft: false menu: sidebar: parent: "usage" - name: "Push To Create" + name: "Push" weight: 15 - identifier: "push-to-create" + identifier: "push" --- +**Table of Contents** + +{{< toc >}} + +There are some additional features when pushing commits to Gitea server. + +# Open PR through Push + +When you push commits to a non-default branch for the first time, +you will receive a link you can click on to visit the compare page of your branch compared to your main branch. +From there, it's easy to create a pull request, even if you want to target another branch. + +![Gitea Push Hint](/gitea-push-hint.png) + +# Push Options + +In Gitea `1.13`, support for some [push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt) +were added. + +## Supported Options + +- `repo.private` (true|false) - Change the repository's visibility. + + This is particularly useful when combined with push-to-create. + +- `repo.template` (true|false) - Change whether the repository is a template. + +Example of changing a repository's visibility to public: + +```shell +git push -o repo.private=false -u origin main +``` + # Push To Create Push to create is a feature that allows you to push to a repository that does not exist yet in Gitea. This is useful for automation and for allowing users to create repositories without having to go through the web interface. This feature is disabled by default. @@ -35,6 +68,4 @@ git push -u origin main This assumes you are using an SSH remote, but you can also use HTTPS remotes as well. -## Push options (bonus) - -Push-to-create will default to the visibility defined by `DEFAULT_PUSH_CREATE_PRIVATE` in `app.ini`. To explicitly set the visibility, you can use a [push option]({{< relref "doc/usage/push-options.en-us.md" >}}). +Push-to-create will default to the visibility defined by `DEFAULT_PUSH_CREATE_PRIVATE` in `app.ini`. diff --git a/docs/content/doc/usage/push.zh-tw.md b/docs/content/doc/usage/push.zh-tw.md new file mode 100644 index 000000000000..f97d4285cc06 --- /dev/null +++ b/docs/content/doc/usage/push.zh-tw.md @@ -0,0 +1,69 @@ +--- +date: "2020-07-06T16:00:00+02:00" +title: "使用: Push" +slug: "push" +weight: 15 +toc: false +draft: false +menu: + sidebar: + parent: "usage" + name: "Push" + weight: 15 + identifier: "push" +--- + +**Table of Contents** + +{{< toc >}} + +There are some additional features when pushing commits to Gitea server. + +# Push Merge Hint + +When you pushing commits to a non-default branch, you will get an information from +Gitea which is a link, you can click the link and go to a compare page. It's a quick +way to create a pull request or a code review yourself in the Gitea UI. + +![Gitea Push Hint](/gitea-push-hint.png) + +# Push Options + +Gitea 從 `1.13` 版開始支援某些 [push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt) +。 + +## 支援的 Options + +- `repo.private` (true|false) - 修改儲存庫的可見性。 + + 與 push-to-create 一起使用時特別有用。 + +- `repo.template` (true|false) - 修改儲存庫是否為範本儲存庫。 + +以下範例修改儲存庫的可見性為公開: + +```shell +git push -o repo.private=false -u origin main +``` + +# Push To Create + +Push to create is a feature that allows you to push to a repository that does not exist yet in Gitea. This is useful for automation and for allowing users to create repositories without having to go through the web interface. This feature is disabled by default. + +## Enabling Push To Create + +In the `app.ini` file, set `ENABLE_PUSH_CREATE_USER` to `true` and `ENABLE_PUSH_CREATE_ORG` to `true` if you want to allow users to create repositories in their own user account and in organizations they are a member of respectively. Restart Gitea for the changes to take effect. You can read more about these two options in the [Configuration Cheat Sheet]({{< relref "doc/administration/config-cheat-sheet.zh-tw.md#repository-repository" >}}). + +## Using Push To Create + +Assuming you have a git repository in the current directory, you can push to a repository that does not exist yet in Gitea by running the following command: + +```shell +# Add the remote you want to push to +git remote add origin git@{domain}:{username}/{repo name that does not exist yet}.git + +# push to the remote +git push -u origin main +``` + +This assumes you are using an SSH remote, but you can also use HTTPS remotes as well. diff --git a/docs/static/gitea-push-hint.png b/docs/static/gitea-push-hint.png new file mode 100644 index 000000000000..6f3ab3c60682 Binary files /dev/null and b/docs/static/gitea-push-hint.png differ diff --git a/models/actions/run.go b/models/actions/run.go index 22041b65a9b1..b58683dd36b3 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -36,7 +36,7 @@ type ActionRun struct { TriggerUser *user_model.User `xorm:"-"` Ref string CommitSHA string - IsForkPullRequest bool + IsForkPullRequest bool // If this is triggered by a PR from a forked repository or an untrusted user, we need to check if it is approved and limit permissions when running the workflow. NeedApproval bool // may need approval if it's a fork pull request ApprovedBy int64 `xorm:"index"` // who approved Event webhook_module.HookEventType diff --git a/models/fixtures/org_user.yml b/models/fixtures/org_user.yml index d6bbdaa9da26..d08f69579913 100644 --- a/models/fixtures/org_user.yml +++ b/models/fixtures/org_user.yml @@ -75,3 +75,9 @@ uid: 31 org_id: 19 is_public: true + +- + id: 14 + uid: 5 + org_id: 23 + is_public: false diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml index be988b8fce12..aa3b36e64439 100644 --- a/models/fixtures/team.yml +++ b/models/fixtures/team.yml @@ -172,4 +172,15 @@ num_repos: 0 num_members: 0 includes_all_repositories: false - can_create_org_repo: true \ No newline at end of file + can_create_org_repo: true + +- + id: 17 + org_id: 23 + lower_name: team14writeauth + name: team14WriteAuth + authorize: 2 # write + num_repos: 0 + num_members: 1 + includes_all_repositories: false + can_create_org_repo: true diff --git a/models/fixtures/team_unit.yml b/models/fixtures/team_unit.yml index 2e23a6312919..5257d2c3856d 100644 --- a/models/fixtures/team_unit.yml +++ b/models/fixtures/team_unit.yml @@ -268,3 +268,9 @@ team_id: 9 type: 1 # code access_mode: 1 + +- + id: 46 + team_id: 17 + type: 9 # package + access_mode: 0 diff --git a/models/fixtures/team_user.yml b/models/fixtures/team_user.yml index de4f29d977cb..b95f76c72376 100644 --- a/models/fixtures/team_user.yml +++ b/models/fixtures/team_user.yml @@ -99,3 +99,9 @@ org_id: 3 team_id: 14 uid: 2 + +- + id: 18 + org_id: 23 + team_id: 17 + uid: 5 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index ce54defacdc6..3e302dfb9af0 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -844,8 +844,8 @@ num_following: 0 num_stars: 0 num_repos: 2 - num_teams: 1 - num_members: 0 + num_teams: 2 + num_members: 1 visibility: 2 repo_admin_change_team_access: false theme: "" diff --git a/models/organization/org_test.go b/models/organization/org_test.go index cfa304d7b29f..6e583879976a 100644 --- a/models/organization/org_test.go +++ b/models/organization/org_test.go @@ -212,25 +212,31 @@ func TestGetOrgUsersByUserID(t *testing.T) { orgUsers, err := organization.GetOrgUsersByUserID(5, &organization.SearchOrganizationsOptions{All: true}) assert.NoError(t, err) - if assert.Len(t, orgUsers, 2) { + if assert.Len(t, orgUsers, 3) { assert.Equal(t, organization.OrgUser{ ID: orgUsers[0].ID, - OrgID: 6, + OrgID: 23, UID: 5, - IsPublic: true, + IsPublic: false, }, *orgUsers[0]) assert.Equal(t, organization.OrgUser{ ID: orgUsers[1].ID, + OrgID: 6, + UID: 5, + IsPublic: true, + }, *orgUsers[1]) + assert.Equal(t, organization.OrgUser{ + ID: orgUsers[2].ID, OrgID: 7, UID: 5, IsPublic: false, - }, *orgUsers[1]) + }, *orgUsers[2]) } publicOrgUsers, err := organization.GetOrgUsersByUserID(5, &organization.SearchOrganizationsOptions{All: false}) assert.NoError(t, err) assert.Len(t, publicOrgUsers, 1) - assert.Equal(t, *orgUsers[0], *publicOrgUsers[0]) + assert.Equal(t, *orgUsers[1], *publicOrgUsers[0]) orgUsers, err = organization.GetOrgUsersByUserID(1, &organization.SearchOrganizationsOptions{All: true}) assert.NoError(t, err) diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go index cdb266e014a9..dd2ef6220116 100644 --- a/models/repo/user_repo.go +++ b/models/repo/user_repo.go @@ -155,14 +155,25 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64) return users, db.GetEngine(ctx).Where(cond).OrderBy(user_model.GetOrderByName()).Find(&users) } -// GetIssuePosters returns all users that have authored an issue/pull request for the given repository -func GetIssuePosters(ctx context.Context, repo *Repository, isPull bool) ([]*user_model.User, error) { - users := make([]*user_model.User, 0, 8) +// GetIssuePostersWithSearch returns users with limit of 30 whose username started with prefix that have authored an issue/pull request for the given repository +// If isShowFullName is set to true, also include full name prefix search +func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) { + users := make([]*user_model.User, 0, 30) + var prefixCond builder.Cond = builder.Like{"name", search + "%"} + if isShowFullName { + prefixCond = prefixCond.Or(builder.Like{"full_name", "%" + search + "%"}) + } + cond := builder.In("`user`.id", builder.Select("poster_id").From("issue").Where( builder.Eq{"repo_id": repo.ID}. And(builder.Eq{"is_pull": isPull}), - ).GroupBy("poster_id"), - ) - return users, db.GetEngine(ctx).Where(cond).OrderBy(user_model.GetOrderByName()).Find(&users) + ).GroupBy("poster_id")).And(prefixCond) + + return users, db.GetEngine(ctx). + Where(cond). + Cols("id", "name", "full_name", "avatar", "avatar_email", "use_custom_avatar"). + OrderBy("name"). + Limit(30). + Find(&users) } diff --git a/modules/context/package.go b/modules/context/package.go index 2a55db3a77fa..2a0159eb5cdd 100644 --- a/modules/context/package.go +++ b/modules/context/package.go @@ -92,33 +92,25 @@ func determineAccessMode(ctx *Context) (perm.AccessMode, error) { return perm.AccessModeNone, nil } + // TODO: ActionUser permission check accessMode := perm.AccessModeNone if ctx.Package.Owner.IsOrganization() { org := organization.OrgFromUser(ctx.Package.Owner) - // 1. Get user max authorize level for the org (may be none, if user is not member of the org) - if ctx.Doer != nil { - var err error - accessMode, err = org.GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID) + if ctx.Doer != nil && !ctx.Doer.IsGhost() { + // 1. If user is logged in, check all team packages permissions + teams, err := organization.GetUserOrgTeams(ctx, org.ID, ctx.Doer.ID) if err != nil { return accessMode, err } - // If access mode is less than write check every team for more permissions - if accessMode < perm.AccessModeWrite { - teams, err := organization.GetUserOrgTeams(ctx, org.ID, ctx.Doer.ID) - if err != nil { - return accessMode, err - } - for _, t := range teams { - perm := t.UnitAccessMode(ctx, unit.TypePackages) - if accessMode < perm { - accessMode = perm - } + for _, t := range teams { + perm := t.UnitAccessMode(ctx, unit.TypePackages) + if accessMode < perm { + accessMode = perm } } - } - // 2. If authorize level is none, check if org is visible to user - if accessMode == perm.AccessModeNone && organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) { + } else if organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) { + // 2. If user is non-login, check if org is visible to non-login user accessMode = perm.AccessModeRead } } else { diff --git a/modules/context/repo.go b/modules/context/repo.go index 820e756fbdb1..9d45a6019a68 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -240,35 +240,34 @@ func (r *Repository) FileExists(path, branch string) (bool, error) { // GetEditorconfig returns the .editorconfig definition if found in the // HEAD of the default repo branch. -func (r *Repository) GetEditorconfig(optCommit ...*git.Commit) (*editorconfig.Editorconfig, error) { +func (r *Repository) GetEditorconfig(optCommit ...*git.Commit) (cfg *editorconfig.Editorconfig, warning, err error) { if r.GitRepo == nil { - return nil, nil + return nil, nil, nil } - var ( - err error - commit *git.Commit - ) + + var commit *git.Commit + if len(optCommit) != 0 { commit = optCommit[0] } else { commit, err = r.GitRepo.GetBranchCommit(r.Repository.DefaultBranch) if err != nil { - return nil, err + return nil, nil, err } } treeEntry, err := commit.GetTreeEntryByPath(".editorconfig") if err != nil { - return nil, err + return nil, nil, err } if treeEntry.Blob().Size() >= setting.UI.MaxDisplayFileSize { - return nil, git.ErrNotExist{ID: "", RelPath: ".editorconfig"} + return nil, nil, git.ErrNotExist{ID: "", RelPath: ".editorconfig"} } reader, err := treeEntry.Blob().DataAsync() if err != nil { - return nil, err + return nil, nil, err } defer reader.Close() - return editorconfig.Parse(reader) + return editorconfig.ParseGraceful(reader) } // RetrieveBaseRepo retrieves base repository diff --git a/modules/git/commit.go b/modules/git/commit.go index 6e8fcb3e0802..610d27c68ac0 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -291,7 +291,7 @@ func (c *Commit) SearchCommits(opts SearchCommitsOptions) ([]*Commit, error) { // GetFilesChangedSinceCommit get all changed file names between pastCommit to current revision func (c *Commit) GetFilesChangedSinceCommit(pastCommit string) ([]string, error) { - return c.repo.getFilesChanged(pastCommit, c.ID.String()) + return c.repo.GetFilesChangedBetween(pastCommit, c.ID.String()) } // FileChangedSinceCommit Returns true if the file given has changed since the the past commit diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 0e1b00ce082e..2b780feb5c3b 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -182,14 +182,6 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co return repo.parsePrettyFormatLogToList(bytes.TrimSuffix(stdout, []byte{'\n'})) } -func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) { - stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(id1, id2).RunStdBytes(&RunOpts{Dir: repo.Path}) - if err != nil { - return nil, err - } - return strings.Split(string(stdout), "\n"), nil -} - // FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2 // You must ensure that id1 and id2 are valid commit ids. func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) { diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 54c85863bd82..1686e54834fa 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -18,7 +18,6 @@ import ( "path/filepath" "reflect" "regexp" - "runtime" "strings" texttmpl "text/template" "time" @@ -56,12 +55,6 @@ var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}[\s]*$`) // NewFuncMap returns functions for injecting to templates func NewFuncMap() []template.FuncMap { return []template.FuncMap{map[string]interface{}{ - "GoVer": func() string { - return util.ToTitleCase(runtime.Version()) - }, - "UseHTTPS": func() bool { - return strings.HasPrefix(setting.AppURL, "https") - }, "AppName": func() string { return setting.AppName }, @@ -81,10 +74,7 @@ func NewFuncMap() []template.FuncMap { "AppVer": func() string { return setting.AppVer }, - "AppBuiltWith": func() string { - return setting.AppBuiltWith - }, - "AppDomain": func() string { + "AppDomain": func() string { // documented in mail-templates.md return setting.Domain }, "AssetVersion": func() string { @@ -108,11 +98,7 @@ func NewFuncMap() []template.FuncMap { "CustomEmojis": func() map[string]string { return setting.UI.CustomEmojisMap }, - "IsShowFullName": func() bool { - return setting.UI.DefaultShowFullName - }, "Safe": Safe, - "SafeJS": SafeJS, "JSEscape": JSEscape, "Str2html": Str2html, "TimeSince": timeutil.TimeSince, @@ -140,25 +126,8 @@ func NewFuncMap() []template.FuncMap { "DateFmtLong": func(t time.Time) string { return t.Format(time.RFC1123Z) }, - "DateFmtShort": func(t time.Time) string { - return t.Format("Jan 02, 2006") - }, - "CountFmt": base.FormatNumberSI, - "SubStr": func(str string, start, length int) string { - if len(str) == 0 { - return "" - } - end := start + length - if length == -1 { - end = len(str) - } - if len(str) < end { - return str - } - return str[start:end] - }, + "CountFmt": base.FormatNumberSI, "EllipsisString": base.EllipsisString, - "DiffTypeToStr": DiffTypeToStr, "DiffLineTypeToStr": DiffLineTypeToStr, "ShortSha": base.ShortSha, "ActionContent2Commits": ActionContent2Commits, @@ -166,7 +135,6 @@ func NewFuncMap() []template.FuncMap { "PathEscapeSegments": util.PathEscapeSegments, "URLJoin": util.URLJoin, "RenderCommitMessage": RenderCommitMessage, - "RenderCommitMessageLink": RenderCommitMessageLink, "RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject, "RenderCommitBody": RenderCommitBody, "RenderCodeBlock": RenderCodeBlock, @@ -429,9 +397,6 @@ func NewFuncMap() []template.FuncMap { curBranch, ) }, - "RefShortName": func(ref string) string { - return git.RefName(ref).ShortName() - }, }} } @@ -439,9 +404,6 @@ func NewFuncMap() []template.FuncMap { // It's a subset of those used for HTML and other templates func NewTextFuncMap() []texttmpl.FuncMap { return []texttmpl.FuncMap{map[string]interface{}{ - "GoVer": func() string { - return util.ToTitleCase(runtime.Version()) - }, "AppName": func() string { return setting.AppName }, @@ -454,10 +416,7 @@ func NewTextFuncMap() []texttmpl.FuncMap { "AppVer": func() string { return setting.AppVer }, - "AppBuiltWith": func() string { - return setting.AppBuiltWith - }, - "AppDomain": func() string { + "AppDomain": func() string { // documented in mail-templates.md return setting.Domain }, "TimeSince": timeutil.TimeSince, @@ -465,22 +424,6 @@ func NewTextFuncMap() []texttmpl.FuncMap { "DateFmtLong": func(t time.Time) string { return t.Format(time.RFC1123Z) }, - "DateFmtShort": func(t time.Time) string { - return t.Format("Jan 02, 2006") - }, - "SubStr": func(str string, start, length int) string { - if len(str) == 0 { - return "" - } - end := start + length - if length == -1 { - end = len(str) - } - if len(str) < end { - return str - } - return str[start:end] - }, "EllipsisString": base.EllipsisString, "URLJoin": util.URLJoin, "Dict": func(values ...interface{}) (map[string]interface{}, error) { @@ -624,11 +567,6 @@ func Safe(raw string) template.HTML { return template.HTML(raw) } -// SafeJS renders raw as JS -func SafeJS(raw string) template.JS { - return template.JS(raw) -} - // Str2html render Markdown text to HTML func Str2html(raw string) template.HTML { return template.HTML(markup.Sanitize(raw)) @@ -925,14 +863,6 @@ func ActionContent2Commits(act Actioner) *repository.PushCommits { return push } -// DiffTypeToStr returns diff type name -func DiffTypeToStr(diffType int) string { - diffTypes := map[int]string{ - 1: "add", 2: "modify", 3: "del", 4: "rename", 5: "copy", - } - return diffTypes[diffType] -} - // DiffLineTypeToStr returns diff line type name func DiffLineTypeToStr(diffType int) string { switch diffType { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 5834703556c2..a9617541fa76 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -933,6 +933,7 @@ delete_preexisting = Delete pre-existing files delete_preexisting_content = Delete files in %s delete_preexisting_success = Deleted unadopted files in %s blame_prior = View blame prior to this change +author_search_tooltip = Shows a maximum of 30 users transfer.accept = Accept Transfer transfer.accept_desc = Transfer to "%s" @@ -2307,6 +2308,7 @@ release.tag_helper = Choose an existing tag or create a new tag. release.tag_helper_new = New tag. This tag will be created from the target. release.tag_helper_existing = Existing tag. release.title = Title +release.title_empty = Title cannot be empty. release.content = Content release.prerelease_desc = Mark as Pre-Release release.prerelease_helper = Mark this release unsuitable for production use. diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 57384ee9ee85..3072703ecc1c 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -1168,7 +1168,7 @@ issues.ref_pull_from=මෙම අදින්න ඉල්ල issues.ref_closing_from=මෙම ගැටළුව වසා දමනු ඇත%[4]s මෙම ගැටළුව %[2]s issues.ref_reopening_from=මෙම ගැටළුව නැවත විවෘත කරනු ඇත%[4]s මෙම ගැටළුව %[2]s issues.ref_closed_from=මෙම නිකුතුව%[4]s %[2]s -issues.ref_reopened_from=මෙම නිකුතුව%[4]s %[2]s +issues.ref_reopened_from=මෙම නිකුතුව%[4]s %[2]sනැවත විවෘත කරන ලදි issues.ref_from=`හිම%[1]s` issues.poster=පෝස්ටර් issues.collaborator=සහයෝගීතාව diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 535f56ce357e..6b0853e07e0c 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -1104,6 +1104,7 @@ download_file=下载文件 normal_view=普通视图 line=行 lines=行 +from_comment=(评论) editor.add_file=添加文件 editor.new_file=新建文件 diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index d5e8924f5dac..5f7ed255bc3c 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -381,7 +381,7 @@ func GetEditorconfig(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - ec, err := ctx.Repo.GetEditorconfig(ctx.Repo.Commit) + ec, _, err := ctx.Repo.GetEditorconfig(ctx.Repo.Commit) if err != nil { if git.IsErrNotExist(err) { ctx.NotFound(err) diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go index 075fb423dc5f..48815dcff8ed 100644 --- a/routers/web/admin/config.go +++ b/routers/web/admin/config.go @@ -115,6 +115,7 @@ func Config(ctx *context.Context) { ctx.Data["CustomConf"] = setting.CustomConf ctx.Data["AppUrl"] = setting.AppURL + ctx.Data["AppBuiltWith"] = setting.AppBuiltWith ctx.Data["Domain"] = setting.Domain ctx.Data["OfflineMode"] = setting.OfflineMode ctx.Data["DisableRouterLog"] = setting.Log.DisableRouterLog diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index f65e1ad3d81b..476c1d5dddd7 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -165,7 +165,7 @@ func editFile(ctx *context.Context, isNewFile bool) { // GetEditorConfig returns a editorconfig JSON string for given treePath or "null" func GetEditorConfig(ctx *context.Context, treePath string) string { - ec, err := ctx.Repo.GetEditorconfig() + ec, _, err := ctx.Repo.GetEditorconfig() if err == nil { def, err := ec.GetDefinitionForFilename(treePath) if err == nil { diff --git a/routers/web/repo/helper.go b/routers/web/repo/helper.go new file mode 100644 index 000000000000..6f9ca4874b00 --- /dev/null +++ b/routers/web/repo/helper.go @@ -0,0 +1,23 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "sort" + + "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" +) + +func makeSelfOnTop(ctx *context.Context, users []*user.User) []*user.User { + if ctx.Doer != nil { + sort.Slice(users, func(i, j int) bool { + if users[i].ID == users[j].ID { + return false + } + return users[i].ID == ctx.Doer.ID // if users[i] is self, put it before others, so less=true + }) + } + return users +} diff --git a/routers/web/repo/helper_test.go b/routers/web/repo/helper_test.go new file mode 100644 index 000000000000..e9ab44fe69f9 --- /dev/null +++ b/routers/web/repo/helper_test.go @@ -0,0 +1,27 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "testing" + + "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + + "github.com/stretchr/testify/assert" +) + +func TestMakeSelfOnTop(t *testing.T) { + users := makeSelfOnTop(&context.Context{}, []*user.User{{ID: 2}, {ID: 1}}) + assert.Len(t, users, 2) + assert.EqualValues(t, 2, users[0].ID) + + users = makeSelfOnTop(&context.Context{Doer: &user.User{ID: 1}}, []*user.User{{ID: 2}, {ID: 1}}) + assert.Len(t, users, 2) + assert.EqualValues(t, 1, users[0].ID) + + users = makeSelfOnTop(&context.Context{Doer: &user.User{ID: 2}}, []*user.User{{ID: 2}, {ID: 1}}) + assert.Len(t, users, 2) + assert.EqualValues(t, 2, users[0].ID) +} diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index e4f1172dd966..5401d60b5509 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -303,17 +303,12 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti ctx.Data["CommitStatuses"] = commitStatuses // Get assignees. - ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, repo) + assigneeUsers, err := repo_model.GetRepoAssignees(ctx, repo) if err != nil { - ctx.ServerError("GetAssignees", err) - return - } - - ctx.Data["Posters"], err = repo_model.GetIssuePosters(ctx, repo, isPullOption.IsTrue()) - if err != nil { - ctx.ServerError("GetIssuePosters", err) + ctx.ServerError("GetRepoAssignees", err) return } + ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers) handleTeamMentions(ctx) if ctx.Written() { @@ -479,11 +474,12 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R return } - ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, repo) + assigneeUsers, err := repo_model.GetRepoAssignees(ctx, repo) if err != nil { - ctx.ServerError("GetAssignees", err) + ctx.ServerError("GetRepoAssignees", err) return } + ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers) handleTeamMentions(ctx) } @@ -3354,3 +3350,46 @@ func handleTeamMentions(ctx *context.Context) { ctx.Data["MentionableTeamsOrg"] = ctx.Repo.Owner.Name ctx.Data["MentionableTeamsOrgAvatar"] = ctx.Repo.Owner.AvatarLink(ctx) } + +type userSearchInfo struct { + UserID int64 `json:"user_id"` + UserName string `json:"username"` + AvatarLink string `json:"avatar_link"` + FullName string `json:"full_name"` +} + +type userSearchResponse struct { + Results []*userSearchInfo `json:"results"` +} + +// IssuePosters get posters for current repo's issues/pull requests +func IssuePosters(ctx *context.Context) { + repo := ctx.Repo.Repository + isPullList := ctx.Params(":type") == "pulls" + search := strings.TrimSpace(ctx.FormString("q")) + posters, err := repo_model.GetIssuePostersWithSearch(ctx, repo, isPullList, search, setting.UI.DefaultShowFullName) + if err != nil { + ctx.JSON(http.StatusInternalServerError, err) + return + } + + if search == "" && ctx.Doer != nil { + // the returned posters slice only contains limited number of users, + // to make the current user (doer) can quickly filter their own issues, always add doer to the posters slice + if !util.SliceContainsFunc(posters, func(user *user_model.User) bool { return user.ID == ctx.Doer.ID }) { + posters = append(posters, ctx.Doer) + } + } + + posters = makeSelfOnTop(ctx, posters) + + resp := &userSearchResponse{} + resp.Results = make([]*userSearchInfo, len(posters)) + for i, user := range posters { + resp.Results[i] = &userSearchInfo{UserID: user.ID, UserName: user.Name, AvatarLink: user.AvatarLink(ctx)} + if setting.UI.DefaultShowFullName { + resp.Results[i].FullName = user.FullName + } + } + ctx.JSON(http.StatusOK, resp) +} diff --git a/routers/web/repo/middlewares.go b/routers/web/repo/middlewares.go index 9a4aa3382cbb..5c38b31154fe 100644 --- a/routers/web/repo/middlewares.go +++ b/routers/web/repo/middlewares.go @@ -19,7 +19,7 @@ func SetEditorconfigIfExists(ctx *context.Context) { return } - ec, err := ctx.Repo.GetEditorconfig() + ec, _, err := ctx.Repo.GetEditorconfig() if err != nil && !git.IsErrNotExist(err) { description := fmt.Sprintf("Error while getting .editorconfig file: %v", err) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 30004cefb70b..070fc109dcec 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -792,10 +792,13 @@ func ViewPullFiles(ctx *context.Context) { setCompareContext(ctx, baseCommit, commit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) - if ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository); err != nil { - ctx.ServerError("GetAssignees", err) + assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository) + if err != nil { + ctx.ServerError("GetRepoAssignees", err) return } + ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers) + handleTeamMentions(ctx) if ctx.Written() { return diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index b8c5f67f45a7..14ef1372c040 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -333,13 +333,12 @@ func NewRelease(ctx *context.Context) { } } ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled - var err error - // Get assignees. - ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository) + assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository) if err != nil { - ctx.ServerError("GetAssignees", err) + ctx.ServerError("GetRepoAssignees", err) return } + ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers) upload.AddUploadContext(ctx, "release") ctx.HTML(http.StatusOK, tplReleaseNew) @@ -361,6 +360,12 @@ func NewReleasePost(ctx *context.Context) { return } + // Title of release cannot be empty + if len(form.TagOnly) == 0 && len(form.Title) == 0 { + ctx.RenderWithErr(ctx.Tr("repo.release.title_empty"), tplReleaseNew, &form) + return + } + var attachmentUUIDs []string if setting.Attachment.Enabled { attachmentUUIDs = form.Files @@ -496,11 +501,12 @@ func EditRelease(ctx *context.Context) { ctx.Data["attachments"] = rel.Attachments // Get assignees. - ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, rel.Repo) + assigneeUsers, err := repo_model.GetRepoAssignees(ctx, rel.Repo) if err != nil { - ctx.ServerError("GetAssignees", err) + ctx.ServerError("GetRepoAssignees", err) return } + ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers) ctx.HTML(http.StatusOK, tplReleaseNew) } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index ce60d9115028..2a57f8ef370b 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -346,11 +346,18 @@ 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(ctx.Repo.Commit) - ctx.Data["FileError"] = editorconfigErr + _, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit) + if editorconfigWarning != nil { + ctx.Data["FileWarning"] = strings.TrimSpace(editorconfigWarning.Error()) + } + if editorconfigErr != nil { + ctx.Data["FileError"] = strings.TrimSpace(editorconfigErr.Error()) + } } else if ctx.Repo.IsIssueConfig(ctx.Repo.TreePath) { _, issueConfigErr := ctx.Repo.GetIssueConfig(ctx.Repo.TreePath, ctx.Repo.Commit) - ctx.Data["FileError"] = issueConfigErr + if issueConfigErr != nil { + ctx.Data["FileError"] = strings.TrimSpace(issueConfigErr.Error()) + } } isDisplayingSource := ctx.FormString("display") == "source" diff --git a/routers/web/web.go b/routers/web/web.go index 6b62ff6f8372..36304493c91c 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1228,7 +1228,10 @@ func RegisterRoutes(m *web.Route) { m.Group("/{username}/{reponame}", func() { m.Group("", func() { - m.Get("/{type:issues|pulls}", repo.Issues) + m.Group("/{type:issues|pulls}", func() { + m.Get("", repo.Issues) + m.Get("/posters", repo.IssuePosters) + }) m.Get("/{type:issues|pulls}/{index}", repo.ViewIssue) m.Group("/{type:issues|pulls}/{index}/content-history", func() { m.Get("/overview", repo.GetContentHistoryOverview) diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index b0e199fc6bd4..49a8c4e74471 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -127,6 +127,11 @@ func notify(ctx context.Context, input *notifyInput) error { defer gitRepo.Close() ref := input.Ref + if input.Event == webhook_module.HookEventDelete { + // The event is deleting a reference, so it will fail to get the commit for a deleted reference. + // Set ref to empty string to fall back to the default branch. + ref = "" + } if ref == "" { ref = input.Repo.DefaultBranch } @@ -152,6 +157,21 @@ func notify(ctx context.Context, input *notifyInput) error { return fmt.Errorf("json.Marshal: %w", err) } + isForkPullRequest := false + if pr := input.PullRequest; pr != nil { + switch pr.Flow { + case issues_model.PullRequestFlowGithub: + isForkPullRequest = pr.IsFromFork() + case issues_model.PullRequestFlowAGit: + // There is no fork concept in agit flow, anyone with read permission can push refs/for// to the repo. + // So we can treat it as a fork pull request because it may be from an untrusted user + isForkPullRequest = true + default: + // unknown flow, assume it's a fork pull request to be safe + isForkPullRequest = true + } + } + for id, content := range workflows { run := &actions_model.ActionRun{ Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0], @@ -161,7 +181,7 @@ func notify(ctx context.Context, input *notifyInput) error { TriggerUserID: input.Doer.ID, Ref: ref, CommitSHA: commit.ID.String(), - IsForkPullRequest: input.PullRequest != nil && input.PullRequest.IsFromFork(), + IsForkPullRequest: isForkPullRequest, Event: input.Event, EventPayload: string(p), Status: actions_model.StatusWaiting, diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 3bd073c070d3..41d7dc7d2b0a 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -693,7 +693,7 @@ type UpdateAllowEditsForm struct { type NewReleaseForm struct { TagName string `binding:"Required;GitRefName;MaxSize(255)"` Target string `form:"tag_target" binding:"Required;MaxSize(255)"` - Title string `binding:"Required;MaxSize(255)"` + Title string `binding:"MaxSize(255)"` Content string Draft string TagOnly string diff --git a/services/release/release.go b/services/release/release.go index eec03b46883e..a9a523119776 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -227,7 +227,7 @@ func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_mod deletedUUIDs.Add(attach.UUID) } - if _, err := repo_model.DeleteAttachments(ctx, attachments, false); err != nil { + if _, err := repo_model.DeleteAttachments(ctx, attachments, true); err != nil { return fmt.Errorf("DeleteAttachments [uuids: %v]: %w", delAttachmentUUIDs, err) } } diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index cc0580cc19dc..14281c70c068 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -11,7 +11,7 @@
{{.locale.Tr "admin.config.app_name"}}
{{AppName}}
{{.locale.Tr "admin.config.app_ver"}}
-
{{AppVer}}{{AppBuiltWith}}
+
{{AppVer}}{{.AppBuiltWith}}
{{.locale.Tr "admin.config.custom_conf"}}
{{.CustomConf}}
{{.locale.Tr "admin.config.app_url"}}
diff --git a/templates/package/content/nuget.tmpl b/templates/package/content/nuget.tmpl index 8ed2f04e4a55..d3cf9b09de03 100644 --- a/templates/package/content/nuget.tmpl +++ b/templates/package/content/nuget.tmpl @@ -4,11 +4,11 @@
-
dotnet nuget add source --name Gitea --username your_username --password your_token 
+
dotnet nuget add source --name {{.PackageDescriptor.Owner.Name}} --username your_username --password your_token 
-
dotnet add package --source Gitea --version {{.PackageDescriptor.Version.Version}} {{.PackageDescriptor.Package.Name}}
+
dotnet add package --source {{.PackageDescriptor.Owner.Name}} --version {{.PackageDescriptor.Version.Version}} {{.PackageDescriptor.Package.Name}}
diff --git a/templates/repo/clone_buttons.tmpl b/templates/repo/clone_buttons.tmpl index 278ae3a9259c..656a487915e2 100644 --- a/templates/repo/clone_buttons.tmpl +++ b/templates/repo/clone_buttons.tmpl @@ -1,7 +1,7 @@ {{if $.CloneButtonShowHTTPS}} {{end}} {{if $.CloneButtonShowSSH}} diff --git a/templates/repo/clone_script.tmpl b/templates/repo/clone_script.tmpl index 88a67d82353b..21e78727ff3e 100644 --- a/templates/repo/clone_script.tmpl +++ b/templates/repo/clone_script.tmpl @@ -11,7 +11,10 @@ const value = localStorage.getItem('repo-clone-protocol') || 'https'; const isSSH = value === 'ssh' && sshBtn || value !== 'ssh' && !httpsBtn; - if (httpsBtn) httpsBtn.classList[!isSSH ? 'add' : 'remove']('primary'); + if (httpsBtn) { + httpsBtn.textContent = window.origin.split(':')[0].toUpperCase(); + httpsBtn.classList[!isSSH ? 'add' : 'remove']('primary'); + } if (sshBtn) sshBtn.classList[isSSH ? 'add' : 'remove']('primary'); const btn = isSSH ? sshBtn : httpsBtn; diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index cfc9a930484f..06cc3aba910f 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -1,5 +1,5 @@ {{template "base/head" .}} -
+
{{template "repo/header" .}}
@@ -117,7 +117,11 @@
-
@@ -150,7 +149,7 @@ {{.locale.Tr "repo.issues.filter_assginee_no_select"}} {{range .Assignees}} - {{avatar $.Context .}}{{template "repo/search_name" .}} + {{avatar $.Context . 20}}{{template "repo/search_name" .}} {{end}}
@@ -299,7 +298,7 @@
{{range .Assignees}}
- {{avatar $.Context .}} {{.GetDisplayName}} + {{avatar $.Context . 20}} {{.GetDisplayName}}
{{end}}
diff --git a/templates/repo/issue/milestone_issues.tmpl b/templates/repo/issue/milestone_issues.tmpl index 2ca2fa2bce70..148a23db6675 100644 --- a/templates/repo/issue/milestone_issues.tmpl +++ b/templates/repo/issue/milestone_issues.tmpl @@ -1,5 +1,5 @@ {{template "base/head" .}} -
+
{{template "repo/header" .}}
@@ -71,7 +71,11 @@
-
@@ -104,7 +103,7 @@ {{.locale.Tr "repo.issues.filter_assginee_no_select"}} {{range .Assignees}} - {{avatar $.Context . 28 "gt-mr-2"}}{{template "repo/search_name" .}} + {{avatar $.Context . 20}}{{template "repo/search_name" .}} {{end}}
@@ -190,7 +189,7 @@
{{range .Assignees}}
- {{avatar $.Context . 28 "gt-mr-2"}} + {{avatar $.Context . 20}} {{.GetDisplayName}}
{{end}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 21e88ecd3b61..7e4be15c47bc 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -254,7 +254,7 @@ {{end}} {{svg "octicon-check"}} - {{avatar $.Context . 28 "gt-mr-3"}}{{template "repo/search_name" .}} + {{avatar $.Context . 20 "gt-mr-3"}}{{template "repo/search_name" .}} {{end}} diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl index ea5c70e74285..cd702d6aa679 100644 --- a/templates/repo/release/new.tmpl +++ b/templates/repo/release/new.tmpl @@ -47,7 +47,7 @@
- +
diff --git a/templates/repo/search_name.tmpl b/templates/repo/search_name.tmpl index 5a481761cc8f..951f168866f6 100644 --- a/templates/repo/search_name.tmpl +++ b/templates/repo/search_name.tmpl @@ -1 +1 @@ -{{.Name}}{{if IsShowFullName}} {{.FullName}}{{end}} +{{.Name}}{{if DefaultShowFullName}} {{.FullName}}{{end}} diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index 70d33621c6d1..36b50e0c7f51 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -1,9 +1,12 @@
{{- if .FileError}} +
+
{{.FileError}}
+
+ {{end}} + {{- if .FileWarning}}
-
-
{{.FileError}}
-
+
{{.FileWarning}}
{{end}}

diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go index 4228003e2db3..74a7e3c79546 100644 --- a/tests/integration/api_packages_test.go +++ b/tests/integration/api_packages_test.go @@ -157,6 +157,7 @@ func TestPackageAccess(t *testing.T) { admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) inactive := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 9}) + privatedOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23}) uploadPackage := func(doer, owner *user_model.User, expectedStatus int) { url := fmt.Sprintf("/api/packages/%s/generic/test-package/1.0/file.bin", owner.Name) @@ -170,6 +171,15 @@ func TestPackageAccess(t *testing.T) { uploadPackage(inactive, user, http.StatusUnauthorized) uploadPackage(admin, inactive, http.StatusCreated) uploadPackage(admin, user, http.StatusCreated) + + // team.authorize is write, but team_unit.access_mode is none + // so the user can not upload packages or get package list + uploadPackage(user, privatedOrg, http.StatusUnauthorized) + + session := loginUser(t, user.Name) + tokenReadPackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s?token=%s", privatedOrg.Name, tokenReadPackage)) + MakeRequest(t, req, http.StatusForbidden) } func TestPackageQuota(t *testing.T) { diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index e2d195822fc0..834a507b6879 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -25,6 +25,7 @@ .gt-overflow-x-scroll { overflow-x: scroll !important; } .gt-cursor-default { cursor: default !important; } .gt-items-start { align-items: flex-start !important; } +.gt-whitespace-pre { white-space: pre !important } .gt-mono { font-family: var(--fonts-monospace) !important; diff --git a/web_src/css/repository.css b/web_src/css/repository.css index a80222239478..b4bfd17352be 100644 --- a/web_src/css/repository.css +++ b/web_src/css/repository.css @@ -72,8 +72,8 @@ max-height: 500px; } -.repository .metas .ui.list.assignees .icon { - line-height: 2em; +.repository .metas .ui.list.assignees .item { + line-height: 2.5em; } .repository .metas .ui.list.assignees .teamavatar { diff --git a/web_src/js/features/common-issue.js b/web_src/js/features/common-issue.js deleted file mode 100644 index 25d41edde348..000000000000 --- a/web_src/js/features/common-issue.js +++ /dev/null @@ -1,59 +0,0 @@ -import $ from 'jquery'; -import {updateIssuesMeta} from './repo-issue.js'; -import {toggleElem} from '../utils/dom.js'; - -export function initCommonIssue() { - const $issueSelectAll = $('.issue-checkbox-all'); - const $issueCheckboxes = $('.issue-checkbox'); - - const syncIssueSelectionState = () => { - const $checked = $issueCheckboxes.filter(':checked'); - const anyChecked = $checked.length !== 0; - const allChecked = anyChecked && $checked.length === $issueCheckboxes.length; - - if (allChecked) { - $issueSelectAll.prop({'checked': true, 'indeterminate': false}); - } else if (anyChecked) { - $issueSelectAll.prop({'checked': false, 'indeterminate': true}); - } else { - $issueSelectAll.prop({'checked': false, 'indeterminate': false}); - } - // if any issue is selected, show the action panel, otherwise show the filter panel - toggleElem($('#issue-filters'), !anyChecked); - toggleElem($('#issue-actions'), anyChecked); - // there are two panels but only one select-all checkbox, so move the checkbox to the visible panel - $('#issue-filters, #issue-actions').filter(':visible').find('.column:first').prepend($issueSelectAll); - }; - - $issueCheckboxes.on('change', syncIssueSelectionState); - - $issueSelectAll.on('change', () => { - $issueCheckboxes.prop('checked', $issueSelectAll.is(':checked')); - syncIssueSelectionState(); - }); - - $('.issue-action').on('click', async function (e) { - e.preventDefault(); - let action = this.getAttribute('data-action'); - let elementId = this.getAttribute('data-element-id'); - const url = this.getAttribute('data-url'); - const issueIDs = $('.issue-checkbox:checked').map((_, el) => { - return el.getAttribute('data-issue-id'); - }).get().join(','); - if (elementId === '0' && url.slice(-9) === '/assignee') { - elementId = ''; - action = 'clear'; - } - if (action === 'toggle' && e.altKey) { - action = 'toggle-alt'; - } - updateIssuesMeta( - url, - action, - issueIDs, - elementId - ).then(() => { - window.location.reload(); - }); - }); -} diff --git a/web_src/js/features/repo-issue-list.js b/web_src/js/features/repo-issue-list.js new file mode 100644 index 000000000000..6c5d1244fd7e --- /dev/null +++ b/web_src/js/features/repo-issue-list.js @@ -0,0 +1,124 @@ +import $ from 'jquery'; +import {updateIssuesMeta} from './repo-issue.js'; +import {toggleElem} from '../utils/dom.js'; +import {htmlEscape} from 'escape-goat'; + +function initRepoIssueListCheckboxes() { + const $issueSelectAll = $('.issue-checkbox-all'); + const $issueCheckboxes = $('.issue-checkbox'); + + const syncIssueSelectionState = () => { + const $checked = $issueCheckboxes.filter(':checked'); + const anyChecked = $checked.length !== 0; + const allChecked = anyChecked && $checked.length === $issueCheckboxes.length; + + if (allChecked) { + $issueSelectAll.prop({'checked': true, 'indeterminate': false}); + } else if (anyChecked) { + $issueSelectAll.prop({'checked': false, 'indeterminate': true}); + } else { + $issueSelectAll.prop({'checked': false, 'indeterminate': false}); + } + // if any issue is selected, show the action panel, otherwise show the filter panel + toggleElem($('#issue-filters'), !anyChecked); + toggleElem($('#issue-actions'), anyChecked); + // there are two panels but only one select-all checkbox, so move the checkbox to the visible panel + $('#issue-filters, #issue-actions').filter(':visible').find('.column:first').prepend($issueSelectAll); + }; + + $issueCheckboxes.on('change', syncIssueSelectionState); + + $issueSelectAll.on('change', () => { + $issueCheckboxes.prop('checked', $issueSelectAll.is(':checked')); + syncIssueSelectionState(); + }); + + $('.issue-action').on('click', async function (e) { + e.preventDefault(); + let action = this.getAttribute('data-action'); + let elementId = this.getAttribute('data-element-id'); + const url = this.getAttribute('data-url'); + const issueIDs = $('.issue-checkbox:checked').map((_, el) => { + return el.getAttribute('data-issue-id'); + }).get().join(','); + if (elementId === '0' && url.slice(-9) === '/assignee') { + elementId = ''; + action = 'clear'; + } + if (action === 'toggle' && e.altKey) { + action = 'toggle-alt'; + } + updateIssuesMeta( + url, + action, + issueIDs, + elementId + ).then(() => { + window.location.reload(); + }); + }); +} + +function initRepoIssueListAuthorDropdown() { + const $searchDropdown = $('.user-remote-search'); + if (!$searchDropdown.length) return; + + let searchUrl = $searchDropdown.attr('data-search-url'); + const actionJumpUrl = $searchDropdown.attr('data-action-jump-url'); + const selectedUserId = $searchDropdown.attr('data-selected-user-id'); + if (!searchUrl.includes('?')) searchUrl += '?'; + + $searchDropdown.dropdown('setting', { + fullTextSearch: true, + selectOnKeydown: false, + apiSettings: { + cache: false, + url: `${searchUrl}&q={query}`, + onResponse(resp) { + // the content is provided by backend IssuePosters handler + const processedResults = []; // to be used by dropdown to generate menu items + for (const item of resp.results) { + let html = `${htmlEscape(item.username)}`; + if (item.full_name) html += `${htmlEscape(item.full_name)}`; + processedResults.push({value: item.user_id, name: html}); + } + resp.results = processedResults; + return resp; + }, + }, + action: (_text, value) => { + window.location.href = actionJumpUrl.replace('{user_id}', encodeURIComponent(value)); + }, + onShow: () => { + $searchDropdown.dropdown('filter', ' '); // trigger a search on first show + }, + }); + + // we want to generate the dropdown menu items by ourselves, replace its internal setup functions + const dropdownSetup = {...$searchDropdown.dropdown('internal', 'setup')}; + const dropdownTemplates = $searchDropdown.dropdown('setting', 'templates'); + $searchDropdown.dropdown('internal', 'setup', dropdownSetup); + dropdownSetup.menu = function (values) { + const $menu = $searchDropdown.find('> .menu'); + $menu.find('> .dynamic-item').remove(); // remove old dynamic items + + const newMenuHtml = dropdownTemplates.menu(values, $searchDropdown.dropdown('setting', 'fields'), true /* html */, $searchDropdown.dropdown('setting', 'className')); + if (newMenuHtml) { + const $newMenuItems = $(newMenuHtml); + $newMenuItems.addClass('dynamic-item'); + $menu.append('
', ...$newMenuItems); + } + $searchDropdown.dropdown('refresh'); + // defer our selection to the next tick, because dropdown will set the selection item after this `menu` function + setTimeout(() => { + $menu.find('.item.active, .item.selected').removeClass('active selected'); + $menu.find(`.item[data-value="${selectedUserId}"]`).addClass('selected'); + }, 0); + }; +} + +export function initRepoIssueList() { + if (!document.querySelectorAll('.page-content.repository.issue-list, .page-content.repository.milestone-issue-list').length) return; + initRepoIssueListCheckboxes(); + initRepoIssueListAuthorDropdown(); +} diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index f2a6f5815b08..a6105f7b2419 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -86,7 +86,7 @@ export function initRepoIssueDue() { }); } -export function initRepoIssueList() { +export function initRepoIssueSidebarList() { const repolink = $('#repolink').val(); const repoId = $('#repoId').val(); const crossRepoSearch = $('#crossRepoSearch').val(); diff --git a/web_src/js/index.js b/web_src/js/index.js index e727acfa0651..f5584d29403a 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -31,13 +31,12 @@ import { } from './features/repo-diff.js'; import { initRepoIssueDue, - initRepoIssueList, initRepoIssueReferenceRepositorySearch, initRepoIssueTimeTracking, initRepoIssueWipTitle, initRepoPullRequestMergeInstruction, initRepoPullRequestAllowMaintainerEdit, - initRepoPullRequestReview, + initRepoPullRequestReview, initRepoIssueSidebarList, } from './features/repo-issue.js'; import { initRepoEllipsisButton, @@ -77,7 +76,6 @@ import {initRepoEditor} from './features/repo-editor.js'; import {initCompSearchUserBox} from './features/comp/SearchUserBox.js'; import {initInstall} from './features/install.js'; import {initCompWebHookEditor} from './features/comp/WebHookEditor.js'; -import {initCommonIssue} from './features/common-issue.js'; import {initRepoBranchButton} from './features/repo-branch.js'; import {initCommonOrganization} from './features/common-organization.js'; import {initRepoWikiForm} from './features/repo-wiki.js'; @@ -89,6 +87,7 @@ import {initRepositoryActionView} from './components/RepoActionView.vue'; import {initGlobalTooltips} from './modules/tippy.js'; import {initGiteaFomantic} from './modules/fomantic.js'; import {onDomReady} from './utils/dom.js'; +import {initRepoIssueList} from './features/repo-issue-list.js'; // Run time-critical code as soon as possible. This is safe to do because this // script appears at the end of and rendered HTML is accessible at that point. @@ -109,7 +108,6 @@ onDomReady(() => { initGlobalFormDirtyLeaveConfirm(); initGlobalLinkActions(); - initCommonIssue(); initCommonOrganization(); initCompSearchUserBox(); @@ -163,6 +161,7 @@ onDomReady(() => { initRepoIssueContentHistory(); initRepoIssueDue(); initRepoIssueList(); + initRepoIssueSidebarList(); initRepoIssueReferenceRepositorySearch(); initRepoIssueTimeTracking(); initRepoIssueWipTitle(); diff --git a/web_src/js/modules/aria/dropdown.js b/web_src/js/modules/aria/dropdown.js index e4c881b6af3c..26c135241682 100644 --- a/web_src/js/modules/aria/dropdown.js +++ b/web_src/js/modules/aria/dropdown.js @@ -132,6 +132,11 @@ function attachInit($dropdown) { const $focusable = $textSearch.length ? $textSearch : $dropdown; // the primary element for focus, see comment above if (!$focusable.length) return; + // as a combobox, the input should not have autocomplete by default + if ($textSearch.length && !$textSearch.attr('autocomplete')) { + $textSearch.attr('autocomplete', 'off'); + } + let $menu = $dropdown.find('> .menu'); if (!$menu.length) { // some "multiple selection" dropdowns don't have a static menu element in HTML, we need to pre-create it to make it have correct aria attributes diff --git a/web_src/js/modules/fomantic.js b/web_src/js/modules/fomantic.js index 2109ff1726a4..1495b311ca60 100644 --- a/web_src/js/modules/fomantic.js +++ b/web_src/js/modules/fomantic.js @@ -19,7 +19,48 @@ export function initGiteaFomantic() { return escape(text, preserveHTML) + svg('octicon-x', 16, `${className.delete} icon`); }; + initFomanticApiPatch(); + // Use the patches to improve accessibility, these patches are designed to be as independent as possible, make it easy to modify or remove in the future. initAriaCheckboxPatch(); initAriaDropdownPatch(); } + +function initFomanticApiPatch() { + // + // Fomantic API module has some very buggy behaviors: + // + // If encodeParameters=true, it calls `urlEncodedValue` to encode the parameter. + // However, `urlEncodedValue` just tries to "guess" whether the parameter is already encoded, by decoding the parameter and encoding it again. + // + // There are 2 problems: + // 1. It may guess wrong, and skip encoding a parameter which looks like encoded. + // 2. If the parameter can't be decoded, `decodeURIComponent` will throw an error, and the whole request will fail. + // + // This patch only fixes the second error behavior at the moment. + // + const patchKey = '_giteaFomanticApiPatch'; + const oldApi = $.api; + $.api = $.fn.api = function(...args) { + const apiCall = oldApi.bind(this); + const ret = oldApi.apply(this, args); + + if (typeof args[0] !== 'string') { + const internalGet = apiCall('internal', 'get'); + if (!internalGet.urlEncodedValue[patchKey]) { + const oldUrlEncodedValue = internalGet.urlEncodedValue; + internalGet.urlEncodedValue = function (value) { + try { + return oldUrlEncodedValue(value); + } catch { + // if Fomantic API module's `urlEncodedValue` throws an error, we encode it by ourselves. + return encodeURIComponent(value); + } + }; + internalGet.urlEncodedValue[patchKey] = true; + } + } + return ret; + }; + $.api.settings = oldApi.settings; +}