Compare commits

..

7 Commits

Author SHA1 Message Date
GiteaBot
3ff81d38d8 [skip ci] Updated translations via Crowdin 2023-09-01 00:24:05 +00:00
silverwind
9b76df53dc
Minor dashboard tweaks, fix flex-list margins (#26829)
Some small dashboard tweaks:

- Remove margin-bottom from divider so first item does not appear to
have un-equal margins
- Restore previous icon color
- Add slight margin-right to icon

Before:
<img width="783" alt="Screenshot 2023-08-31 at 00 10 28"
src="https://github.com/go-gitea/gitea/assets/115237/b75f70d7-8704-4afb-866d-fea0484c52d4">

After:
<img width="783" alt="Screenshot 2023-08-31 at 00 10 08"
src="https://github.com/go-gitea/gitea/assets/115237/50ed0c47-6f7c-449e-a054-13091369d43f">

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-31 21:28:45 +00:00
Jack Hay
c0ab7070e5
Update team invitation email link (#26550)
Co-authored-by: Kyle D <kdumontnu@gmail.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2023-08-31 16:26:13 +00:00
JakobDev
3cae50e841
Redirect from {repo}/issues/new to {repo}/issues/new/choose when blank issues are disabled (#26813)
You can currently visit `{repo}/issues/new` and create a blank issue,
even if it's disabled. This PR fixes this,

Fixes https://codeberg.org/forgejo/forgejo/issues/1356

Co-authored-by: Giteabot <teabot@gitea.io>
2023-08-31 15:36:25 +00:00
wxiaoguang
d5703d4a1b
Remove "TODO" tasks from CSS file (#26835)
1. Use `gt-invisible` instead of `invisible`.
2. Use `gt-word-break` instead of `dont-break-out` (there is a slight
different "hyphens", but I think it won't affect too much since it is
only used for the "full name").
3. Remove `.small.button:has(svg)` , now our buttons could layout SVG
correctly, and actually I didn't see this CSS class is used in code.
2023-08-31 10:49:53 +00:00
Denys Konovalov
5b5bb8d354
User details page (#26713)
This PR implements a proposal to clean up the admin users table by
moving some information out to a separate user details page (which also
displays some additional information).

Other changes:
- move edit user page from `/admin/users/{id}` to
`/admin/users/{id}/edit` -> `/admin/users/{id}` now shows the user
details page
- show if user is instance administrator as a label instead of a
separate column
- separate explore users template into a page- and a shared one, to make
it possible to use it on the user details page
- fix issue where there was no margin between alert message and
following content on admin pages

<details>

<summary>Screenshots</summary>


![grafik](https://github.com/go-gitea/gitea/assets/47871822/1ad57ac9-f20a-45a4-8477-ffe572a41e9e)


![grafik](https://github.com/go-gitea/gitea/assets/47871822/25786ecd-cb9d-4c92-90f4-e7f4292c073b)


</details>

Partially resolves #25939

---------

Co-authored-by: Giteabot <teabot@gitea.io>
2023-08-31 11:21:18 +02:00
silverwind
3d109861dd
Render code blocks in repo description (#26830)
Backtick syntax now works in repo description too. Also, I replaced the
CSS for this was a new single class, making it more flexible and not
dependent on a parent. Also, very slightly reduced font size from 16.8px
to 16px.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-31 05:01:01 +00:00
43 changed files with 692 additions and 127 deletions

View File

@ -108,10 +108,9 @@ func RenderCommitBody(ctx context.Context, msg, urlPrefix string, metas map[stri
// Match text that is between back ticks. // Match text that is between back ticks.
var codeMatcher = regexp.MustCompile("`([^`]+)`") var codeMatcher = regexp.MustCompile("`([^`]+)`")
// RenderCodeBlock renders "`…`" as highlighted "<code>" block. // RenderCodeBlock renders "`…`" as highlighted "<code>" block, intended for issue and PR titles
// Intended for issue and PR titles, these containers should have styles for "<code>" elements
func RenderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML { func RenderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
htmlWithCodeTags := codeMatcher.ReplaceAllString(string(htmlEscapedTextToRender), "<code>$1</code>") // replace with HTML <code> tags htmlWithCodeTags := codeMatcher.ReplaceAllString(string(htmlEscapedTextToRender), `<code class="inline-code-block">$1</code>`) // replace with HTML <code> tags
return template.HTML(htmlWithCodeTags) return template.HTML(htmlWithCodeTags)
} }

View File

@ -2823,6 +2823,7 @@ users.list_status_filter.is_prohibit_login = Prohibit Login
users.list_status_filter.not_prohibit_login = Allow Login users.list_status_filter.not_prohibit_login = Allow Login
users.list_status_filter.is_2fa_enabled = 2FA Enabled users.list_status_filter.is_2fa_enabled = 2FA Enabled
users.list_status_filter.not_2fa_enabled = 2FA Disabled users.list_status_filter.not_2fa_enabled = 2FA Disabled
users.details = User Details
emails.email_manage_panel = User Email Management emails.email_manage_panel = User Email Management
emails.primary = Primary emails.primary = Primary

View File

@ -1777,6 +1777,19 @@ milestones.filter_sort.most_complete=消化率の高い順
milestones.filter_sort.most_issues=イシューの多い順 milestones.filter_sort.most_issues=イシューの多い順
milestones.filter_sort.least_issues=イシューの少ない順 milestones.filter_sort.least_issues=イシューの少ない順
signing.will_sign=このコミットは鍵 "%s" で署名されます。
signing.wont_sign.error=コミットの署名可否を確認中にエラーが発生しました。
signing.wont_sign.nokey=このコミットに署名するための鍵がありません。
signing.wont_sign.never=コミットが署名されることはありません。
signing.wont_sign.always=コミットは常に署名されます。
signing.wont_sign.pubkey=アカウントに公開鍵が登録されていないため、コミットは署名されません。
signing.wont_sign.twofa=コミットに署名するには、2要素認証を有効にする必要があります。
signing.wont_sign.parentsigned=親コミットが署名されていないため、このコミットも署名されません。
signing.wont_sign.basesigned=ベース側のコミットが署名されていないため、マージは署名されません。
signing.wont_sign.headsigned=HEADコミットが署名されていないため、マージは署名されません。
signing.wont_sign.commitssigned=関連するコミットすべてが署名されていないため、マージは署名されません。
signing.wont_sign.approved=PRが未承認のため、マージは署名されません。
signing.wont_sign.not_signed_in=サインインしていません。
ext_wiki=外部Wikiへのアクセス ext_wiki=外部Wikiへのアクセス
ext_wiki.desc=外部Wikiへのリンク。 ext_wiki.desc=外部Wikiへのリンク。
@ -1906,7 +1919,9 @@ settings.mirror_settings.docs.disabled_push_mirror.info=プッシュ方式のミ
settings.mirror_settings.docs.no_new_mirrors=このリポジトリは他のリポジトリと変更をミラーリングしています。 現時点では新しいミラーを作成することはできないことに留意してください。 settings.mirror_settings.docs.no_new_mirrors=このリポジトリは他のリポジトリと変更をミラーリングしています。 現時点では新しいミラーを作成することはできないことに留意してください。
settings.mirror_settings.docs.can_still_use=既存ミラーを変更したりミラーを新規に作成することはできませんが、既存ミラーを利用することは可能です。 settings.mirror_settings.docs.can_still_use=既存ミラーを変更したりミラーを新規に作成することはできませんが、既存ミラーを利用することは可能です。
settings.mirror_settings.docs.pull_mirror_instructions=プル方式のミラーを設定するには、次を参照: settings.mirror_settings.docs.pull_mirror_instructions=プル方式のミラーを設定するには、次を参照:
settings.mirror_settings.docs.more_information_if_disabled=プッシュミラーとプルミラーの詳細は、こちらをご覧ください:
settings.mirror_settings.docs.doc_link_title=リポジトリをミラーリングするには? settings.mirror_settings.docs.doc_link_title=リポジトリをミラーリングするには?
settings.mirror_settings.docs.doc_link_pull_section=ドキュメントの「リモートリポジトリからのプル」セクション。
settings.mirror_settings.docs.pulling_remote_title=リモートリポジトリからのプル settings.mirror_settings.docs.pulling_remote_title=リモートリポジトリからのプル
settings.mirror_settings.mirrored_repository=同期するリポジトリ settings.mirror_settings.mirrored_repository=同期するリポジトリ
settings.mirror_settings.direction=方向 settings.mirror_settings.direction=方向
@ -1986,6 +2001,7 @@ settings.transfer.rejected=リポジトリの移転は拒否されました。
settings.transfer.success=リポジトリの移転が成功しました。 settings.transfer.success=リポジトリの移転が成功しました。
settings.transfer_abort=転送をキャンセル settings.transfer_abort=転送をキャンセル
settings.transfer_abort_invalid=存在しないリポジトリの移転はキャンセルできません。 settings.transfer_abort_invalid=存在しないリポジトリの移転はキャンセルできません。
settings.transfer_abort_success=%s へのリポジトリ移転は正常にキャンセルされました。
settings.transfer_desc=別のユーザーやあなたが管理者権限を持っている組織にリポジトリを移転します。 settings.transfer_desc=別のユーザーやあなたが管理者権限を持っている組織にリポジトリを移転します。
settings.transfer_form_title=確認のためリポジトリ名を入力: settings.transfer_form_title=確認のためリポジトリ名を入力:
settings.transfer_in_progress=現在進行中の移転があります。このリポジトリを別のユーザーに移転したい場合はキャンセルしてください。 settings.transfer_in_progress=現在進行中の移転があります。このリポジトリを別のユーザーに移転したい場合はキャンセルしてください。
@ -2261,11 +2277,17 @@ settings.matrix.room_id=ルーム ID
settings.matrix.message_type=メッセージ種別 settings.matrix.message_type=メッセージ種別
settings.archive.button=アーカイブ settings.archive.button=アーカイブ
settings.archive.header=このリポジトリをアーカイブ settings.archive.header=このリポジトリをアーカイブ
settings.archive.text=リポジトリをアーカイブするとリポジトリ全体が読み出し専用となります。 ダッシュボードにも表示されなくなります。 新たなコミット、あるいは、イシューやプルリクエストの作成は、誰もできなくなります (あなたでさえも!)。
settings.archive.success=リポジトリをアーカイブしました。 settings.archive.success=リポジトリをアーカイブしました。
settings.archive.error=リポジトリのアーカイブ設定でエラーが発生しました。 詳細はログを確認してください。 settings.archive.error=リポジトリのアーカイブ設定でエラーが発生しました。 詳細はログを確認してください。
settings.archive.error_ismirror=ミラーのリポジトリはアーカイブできません。 settings.archive.error_ismirror=ミラーのリポジトリはアーカイブできません。
settings.archive.branchsettings_unavailable=ブランチ設定は、アーカイブリポジトリでは使用できません。 settings.archive.branchsettings_unavailable=ブランチ設定は、アーカイブリポジトリでは使用できません。
settings.archive.tagsettings_unavailable=タグ設定は、アーカイブリポジトリでは使用できません。 settings.archive.tagsettings_unavailable=タグ設定は、アーカイブリポジトリでは使用できません。
settings.unarchive.button=アーカイブ解除
settings.unarchive.header=このリポジトリをアーカイブ解除
settings.unarchive.text=リポジトリのアーカイブを解除すると、コミット、プッシュ、新規のイシューやプルリクエストを受け付ける機能が復活します。
settings.unarchive.success=リポジトリのアーカイブを解除しました。
settings.unarchive.error=リポジトリのアーカイブ解除でエラーが発生しました。 詳細はログを確認してください。
settings.update_avatar_success=リポジトリのアバターを更新しました。 settings.update_avatar_success=リポジトリのアバターを更新しました。
settings.lfs=LFS settings.lfs=LFS
settings.lfs_filelist=このリポジトリに含まれているLFSファイル settings.lfs_filelist=このリポジトリに含まれているLFSファイル
@ -2388,6 +2410,7 @@ release.edit_release=リリースを更新
release.delete_release=リリースを削除 release.delete_release=リリースを削除
release.delete_tag=タグを削除 release.delete_tag=タグを削除
release.deletion=リリースの削除 release.deletion=リリースの削除
release.deletion_desc=リリースの削除は、Giteaからの削除だけを行います。 Gitタグやリポジトリの内容、履歴には影響しません。 続行しますか?
release.deletion_success=リリースを削除しました。 release.deletion_success=リリースを削除しました。
release.deletion_tag_desc=リポジトリからこのタグを削除します。 リポジトリの内容と履歴はそのまま残ります。 続行しますか? release.deletion_tag_desc=リポジトリからこのタグを削除します。 リポジトリの内容と履歴はそのまま残ります。 続行しますか?
release.deletion_tag_success=タグを削除しました。 release.deletion_tag_success=タグを削除しました。
@ -2408,6 +2431,7 @@ branch.already_exists=ブランチ "%s" は既に存在します。
branch.delete_head=削除 branch.delete_head=削除
branch.delete=ブランチ "%s" の削除 branch.delete=ブランチ "%s" の削除
branch.delete_html=ブランチ削除 branch.delete_html=ブランチ削除
branch.delete_desc=ブランチの削除は恒久的です。 実際に削除されるまでの短い期間、ブランチが存在したままになることもありますが、たいていは元に戻すことはできません。 続行しますか?
branch.deletion_success=ブランチ "%s" を削除しました。 branch.deletion_success=ブランチ "%s" を削除しました。
branch.deletion_failed=ブランチ "%s" の削除に失敗しました。 branch.deletion_failed=ブランチ "%s" の削除に失敗しました。
branch.delete_branch_has_new_commits=マージ後に新しいコミットが追加されているため、ブランチ "%s" を削除できません。 branch.delete_branch_has_new_commits=マージ後に新しいコミットが追加されているため、ブランチ "%s" を削除できません。
@ -2573,6 +2597,7 @@ teams.all_repositories_helper=チームはすべてのリポジトリにアク
teams.all_repositories_read_permission_desc=このチームは<strong>すべてのリポジトリ</strong>の<strong>読み取り</strong>アクセス権を持ちます: メンバーはリポジトリの閲覧とクローンが可能です。 teams.all_repositories_read_permission_desc=このチームは<strong>すべてのリポジトリ</strong>の<strong>読み取り</strong>アクセス権を持ちます: メンバーはリポジトリの閲覧とクローンが可能です。
teams.all_repositories_write_permission_desc=このチームは<strong>すべてのリポジトリ</strong>の<strong>書き込み</strong>アクセス権を持ちます: メンバーはリポジトリの読み取りとプッシュが可能です。 teams.all_repositories_write_permission_desc=このチームは<strong>すべてのリポジトリ</strong>の<strong>書き込み</strong>アクセス権を持ちます: メンバーはリポジトリの読み取りとプッシュが可能です。
teams.all_repositories_admin_permission_desc=このチームは<strong>すべてのリポジトリ</strong>の<strong>管理者</strong>アクセス権を持ちます: メンバーはリポジトリの読み取り、プッシュ、共同作業者の追加が可能です。 teams.all_repositories_admin_permission_desc=このチームは<strong>すべてのリポジトリ</strong>の<strong>管理者</strong>アクセス権を持ちます: メンバーはリポジトリの読み取り、プッシュ、共同作業者の追加が可能です。
teams.invite.title=あなたは組織 <strong>%[2]s</strong> 内のチーム <strong>%[1]s</strong> への参加に招待されました。
teams.invite.by=%s からの招待 teams.invite.by=%s からの招待
teams.invite.description=下のボタンをクリックしてチームに参加してください。 teams.invite.description=下のボタンをクリックしてチームに参加してください。
@ -2602,11 +2627,13 @@ dashboard.clean_unbind_oauth=関連付けられていないOAuth接続を削除
dashboard.clean_unbind_oauth_success=関連付けられていないOAuth接続をすべて削除しました。 dashboard.clean_unbind_oauth_success=関連付けられていないOAuth接続をすべて削除しました。
dashboard.task.started=タスクを開始しました: %[1]s dashboard.task.started=タスクを開始しました: %[1]s
dashboard.task.process=タスク: %[1]s dashboard.task.process=タスク: %[1]s
dashboard.task.cancelled=タスク: %[1]s をキャンセル: %[3]s
dashboard.task.error=タスクでエラー: %[1]s: %[3]s dashboard.task.error=タスクでエラー: %[1]s: %[3]s
dashboard.task.finished=タスク: %[2]s が開始したタスク %[1]s が完了 dashboard.task.finished=タスク: %[2]s が開始したタスク %[1]s が完了
dashboard.task.unknown=不明なタスクです: %[1]s dashboard.task.unknown=不明なタスクです: %[1]s
dashboard.cron.started=Cronを開始しました: %[1]s dashboard.cron.started=Cronを開始しました: %[1]s
dashboard.cron.process=Cron: %[1]s dashboard.cron.process=Cron: %[1]s
dashboard.cron.cancelled=Cron: %[1]s をキャンセル: %[3]s
dashboard.cron.error=Cronでエラー: %s: %[3]s dashboard.cron.error=Cronでエラー: %s: %[3]s
dashboard.cron.finished=Cron: %[1]s が完了 dashboard.cron.finished=Cron: %[1]s が完了
dashboard.delete_inactive_accounts=アクティベートされていないアカウントをすべて削除 dashboard.delete_inactive_accounts=アクティベートされていないアカウントをすべて削除
@ -3295,14 +3322,17 @@ settings.delete.success=パッケージを削除しました。
settings.delete.error=パッケージの削除に失敗しました。 settings.delete.error=パッケージの削除に失敗しました。
owner.settings.cargo.title=Cargoレジストリ インデックス owner.settings.cargo.title=Cargoレジストリ インデックス
owner.settings.cargo.initialize=インデックスを初期化 owner.settings.cargo.initialize=インデックスを初期化
owner.settings.cargo.initialize.description=Cargoレジストリを使用するには、インデックス用の特別なgitリポジトリが必要です。 このオプションを使用するとそのリポジトリを(再)作成し、自動的に構成します。
owner.settings.cargo.initialize.error=Cargoインデックスの初期化に失敗しました: %v owner.settings.cargo.initialize.error=Cargoインデックスの初期化に失敗しました: %v
owner.settings.cargo.initialize.success=Cargoインデックスは正常に作成されました。 owner.settings.cargo.initialize.success=Cargoインデックスは正常に作成されました。
owner.settings.cargo.rebuild=インデックスを再構築 owner.settings.cargo.rebuild=インデックスを再構築
owner.settings.cargo.rebuild.description=インデックスが格納されているCargoパッケージと同期していない場合は、再構築すると良いでしょう。
owner.settings.cargo.rebuild.error=Cargoインデックスの再構築に失敗しました: %v owner.settings.cargo.rebuild.error=Cargoインデックスの再構築に失敗しました: %v
owner.settings.cargo.rebuild.success=Cargoインデックスは正常に再構築されました。 owner.settings.cargo.rebuild.success=Cargoインデックスは正常に再構築されました。
owner.settings.cleanuprules.title=クリーンアップルールの管理 owner.settings.cleanuprules.title=クリーンアップルールの管理
owner.settings.cleanuprules.add=クリーンアップルールを追加 owner.settings.cleanuprules.add=クリーンアップルールを追加
owner.settings.cleanuprules.edit=クリーンアップルールを編集 owner.settings.cleanuprules.edit=クリーンアップルールを編集
owner.settings.cleanuprules.none=クリーンアップルールはありません。 ドキュメントを参照してください。
owner.settings.cleanuprules.preview=クリーンアップルールをプレビュー owner.settings.cleanuprules.preview=クリーンアップルールをプレビュー
owner.settings.cleanuprules.preview.overview=%d パッケージが削除される予定です。 owner.settings.cleanuprules.preview.overview=%d パッケージが削除される予定です。
owner.settings.cleanuprules.preview.none=クリーンアップルールと一致するパッケージがありません。 owner.settings.cleanuprules.preview.none=クリーンアップルールと一致するパッケージがありません。
@ -3321,6 +3351,7 @@ owner.settings.cleanuprules.success.update=クリーンアップルールが更
owner.settings.cleanuprules.success.delete=クリーンアップルールが削除されました。 owner.settings.cleanuprules.success.delete=クリーンアップルールが削除されました。
owner.settings.chef.title=Chefレジストリ owner.settings.chef.title=Chefレジストリ
owner.settings.chef.keypair=キーペアを生成 owner.settings.chef.keypair=キーペアを生成
owner.settings.chef.keypair.description=Chefレジストリの認証にはキーペアが必要です。 すでにキーペアを生成していた場合、新しいキーペアを生成すると古いキーペアは破棄されます。
[secrets] [secrets]
secrets=シークレット secrets=シークレット

View File

@ -13,6 +13,8 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
org_model "code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system" system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password" "code.gitea.io/gitea/modules/auth/password"
@ -32,6 +34,7 @@ import (
const ( const (
tplUsers base.TplName = "admin/user/list" tplUsers base.TplName = "admin/user/list"
tplUserNew base.TplName = "admin/user/new" tplUserNew base.TplName = "admin/user/new"
tplUserView base.TplName = "admin/user/view"
tplUserEdit base.TplName = "admin/user/edit" tplUserEdit base.TplName = "admin/user/edit"
) )
@ -249,6 +252,61 @@ func prepareUserInfo(ctx *context.Context) *user_model.User {
return u return u
} }
func ViewUser(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.users.details")
ctx.Data["PageIsAdminUsers"] = true
ctx.Data["DisableRegularOrgCreation"] = setting.Admin.DisableRegularOrgCreation
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
u := prepareUserInfo(ctx)
if ctx.Written() {
return
}
repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
ListAll: true,
},
OwnerID: u.ID,
OrderBy: db.SearchOrderByAlphabetically,
Private: true,
Collaborate: util.OptionalBoolFalse,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
return
}
ctx.Data["Repos"] = repos
ctx.Data["ReposTotal"] = int(count)
emails, err := user_model.GetEmailAddresses(ctx.Doer.ID)
if err != nil {
ctx.ServerError("GetEmailAddresses", err)
return
}
ctx.Data["Emails"] = emails
ctx.Data["EmailsTotal"] = len(emails)
orgs, err := org_model.FindOrgs(org_model.FindOrgOptions{
ListOptions: db.ListOptions{
ListAll: true,
},
UserID: u.ID,
IncludePrivate: true,
})
if err != nil {
ctx.ServerError("FindOrgs", err)
return
}
ctx.Data["Users"] = orgs // needed to be able to use explore/user_list template
ctx.Data["OrgsTotal"] = len(orgs)
ctx.HTML(http.StatusOK, tplUserView)
}
// EditUser show editing user page // EditUser show editing user page
func EditUser(ctx *context.Context) { func EditUser(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.users.edit_account") ctx.Data["Title"] = ctx.Tr("admin.users.edit_account")

View File

@ -398,6 +398,11 @@ func SignUp(ctx *context.Context) {
// Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true // Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration
redirectTo := ctx.FormString("redirect_to")
if len(redirectTo) > 0 {
middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
}
ctx.HTML(http.StatusOK, tplSignUp) ctx.HTML(http.StatusOK, tplSignUp)
} }
@ -729,6 +734,12 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
} }
ctx.Flash.Success(ctx.Tr("auth.account_activated")) ctx.Flash.Success(ctx.Tr("auth.account_activated"))
if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 {
middleware.DeleteRedirectToCookie(ctx.Resp)
ctx.RedirectToFirst(redirectTo)
return
}
ctx.Redirect(setting.AppSubURL + "/") ctx.Redirect(setting.AppSubURL + "/")
} }

View File

@ -902,9 +902,17 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles
// NewIssue render creating issue page // NewIssue render creating issue page
func NewIssue(ctx *context.Context) { func NewIssue(ctx *context.Context) {
issueConfig, _ := issue_service.GetTemplateConfigFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
hasTemplates := issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo)
if !issueConfig.BlankIssuesEnabled && hasTemplates {
// The "issues/new" and "issues/new/choose" share the same query parameters "project" and "milestone", if blank issues are disabled, just redirect to the "issues/choose" page with these parameters.
ctx.Redirect(fmt.Sprintf("%s/issues/new/choose?%s", ctx.Repo.Repository.Link(), ctx.Req.URL.RawQuery), http.StatusSeeOther)
return
}
ctx.Data["Title"] = ctx.Tr("repo.issues.new") ctx.Data["Title"] = ctx.Tr("repo.issues.new")
ctx.Data["PageIsIssueList"] = true ctx.Data["PageIsIssueList"] = true
ctx.Data["NewIssueChooseTemplate"] = issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo) ctx.Data["NewIssueChooseTemplate"] = hasTemplates
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
title := ctx.FormString("title") title := ctx.FormString("title")
ctx.Data["TitleQuery"] = title ctx.Data["TitleQuery"] = title

View File

@ -573,7 +573,8 @@ func registerRoutes(m *web.Route) {
m.Group("/users", func() { m.Group("/users", func() {
m.Get("", admin.Users) m.Get("", admin.Users)
m.Combo("/new").Get(admin.NewUser).Post(web.Bind(forms.AdminCreateUserForm{}), admin.NewUserPost) m.Combo("/new").Get(admin.NewUser).Post(web.Bind(forms.AdminCreateUserForm{}), admin.NewUserPost)
m.Combo("/{userid}").Get(admin.EditUser).Post(web.Bind(forms.AdminEditUserForm{}), admin.EditUserPost) m.Get("/{userid}", admin.ViewUser)
m.Combo("/{userid}/edit").Get(admin.EditUser).Post(web.Bind(forms.AdminEditUserForm{}), admin.EditUserPost)
m.Post("/{userid}/delete", admin.DeleteUser) m.Post("/{userid}/delete", admin.DeleteUser)
m.Post("/{userid}/avatar", web.Bind(forms.AvatarForm{}), admin.AvatarPost) m.Post("/{userid}/avatar", web.Bind(forms.AvatarForm{}), admin.AvatarPost)
m.Post("/{userid}/avatar/delete", admin.DeleteAvatar) m.Post("/{userid}/avatar/delete", admin.DeleteAvatar)

View File

@ -120,9 +120,9 @@ func VerifyAuthWithOptions(options *VerifyOptions) func(ctx *context.Context) {
} }
} }
// Redirect to dashboard if user tries to visit any non-login page. // Redirect to dashboard (or alternate location) if user tries to visit any non-login page.
if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" { if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" {
ctx.Redirect(setting.AppSubURL + "/") ctx.RedirectToFirst(ctx.FormString("redirect_to"))
return return
} }

View File

@ -6,6 +6,8 @@ package mailer
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"net/url"
org_model "code.gitea.io/gitea/models/organization" org_model "code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -33,6 +35,22 @@ func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_mod
locale := translation.NewLocale(inviter.Language) locale := translation.NewLocale(inviter.Language)
// check if a user with this email already exists
user, err := user_model.GetUserByEmail(ctx, invite.Email)
if err != nil && !user_model.IsErrUserNotExist(err) {
return err
} else if user != nil && user.ProhibitLogin {
return fmt.Errorf("login is prohibited for the invited user")
}
inviteRedirect := url.QueryEscape(fmt.Sprintf("/org/invite/%s", invite.Token))
inviteURL := fmt.Sprintf("%suser/sign_up?redirect_to=%s", setting.AppURL, inviteRedirect)
if err == nil && user != nil {
// user account exists
inviteURL = fmt.Sprintf("%suser/login?redirect_to=%s", setting.AppURL, inviteRedirect)
}
subject := locale.Tr("mail.team_invite.subject", inviter.DisplayName(), org.DisplayName()) subject := locale.Tr("mail.team_invite.subject", inviter.DisplayName(), org.DisplayName())
mailMeta := map[string]any{ mailMeta := map[string]any{
"Inviter": inviter, "Inviter": inviter,
@ -40,6 +58,7 @@ func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_mod
"Team": team, "Team": team,
"Invite": invite, "Invite": invite,
"Subject": subject, "Subject": subject,
"InviteURL": inviteURL,
// helper // helper
"locale": locale, "locale": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,

View File

@ -1,6 +1,6 @@
{{template "base/head" .ctxData}} {{template "base/head" .ctxData}}
<div role="main" aria-label="{{.ctxData.Title}}" class="page-content {{.pageClass}}"> <div role="main" aria-label="{{.ctxData.Title}}" class="page-content {{.pageClass}}">
<div class="ui container"> <div class="ui container gt-mb-4">
{{template "base/alert" .ctxData}} {{template "base/alert" .ctxData}}
</div> </div>
<div class="ui container flex-container"> <div class="ui container flex-container">

View File

@ -68,36 +68,35 @@
</th> </th>
<th>{{.locale.Tr "email"}}</th> <th>{{.locale.Tr "email"}}</th>
<th>{{.locale.Tr "admin.users.activated"}}</th> <th>{{.locale.Tr "admin.users.activated"}}</th>
<th>{{.locale.Tr "admin.users.admin"}}</th>
<th>{{.locale.Tr "admin.users.restricted"}}</th> <th>{{.locale.Tr "admin.users.restricted"}}</th>
<th>{{.locale.Tr "admin.users.2fa"}}</th> <th>{{.locale.Tr "admin.users.2fa"}}</th>
<th>{{.locale.Tr "admin.users.repos"}}</th>
<th>{{.locale.Tr "admin.users.created"}}</th> <th>{{.locale.Tr "admin.users.created"}}</th>
<th data-sortt-asc="lastlogin" data-sortt-desc="reverselastlogin"> <th data-sortt-asc="lastlogin" data-sortt-desc="reverselastlogin">
{{.locale.Tr "admin.users.last_login"}} {{.locale.Tr "admin.users.last_login"}}
{{SortArrow "lastlogin" "reverselastlogin" $.SortType false}} {{SortArrow "lastlogin" "reverselastlogin" $.SortType false}}
</th> </th>
<th>{{.locale.Tr "admin.users.edit"}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{range .Users}} {{range .Users}}
<tr> <tr>
<td>{{.ID}}</td> <td>{{.ID}}</td>
<td><a href="{{.HomeLink}}">{{.Name}}</a></td> <td>
<a href="{{$.Link}}/{{.ID}}">{{.Name}}</a>
{{if .IsAdmin}}
<span class="ui basic label">{{$.locale.Tr "admin.users.admin"}}</span>
{{end}}
</td>
<td class="gt-ellipsis gt-max-width-12rem">{{.Email}}</td> <td class="gt-ellipsis gt-max-width-12rem">{{.Email}}</td>
<td>{{if .IsActive}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td>{{if .IsActive}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
<td>{{if .IsAdmin}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
<td>{{if .IsRestricted}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td>{{if .IsRestricted}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
<td>{{if index $.UsersTwoFaStatus .ID}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td>{{if index $.UsersTwoFaStatus .ID}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
<td>{{.NumRepos}}</td>
<td>{{DateTime "short" .CreatedUnix}}</td> <td>{{DateTime "short" .CreatedUnix}}</td>
{{if .LastLoginUnix}} {{if .LastLoginUnix}}
<td>{{DateTime "short" .LastLoginUnix}}</td> <td>{{DateTime "short" .LastLoginUnix}}</td>
{{else}} {{else}}
<td><span>{{$.locale.Tr "admin.users.never_login"}}</span></td> <td><span>{{$.locale.Tr "admin.users.never_login"}}</span></td>
{{end}} {{end}}
<td><a href="{{$.Link}}/{{.ID}}">{{svg "octicon-pencil"}}</a></td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>

View File

@ -0,0 +1,48 @@
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin view user")}}
<div class="admin-setting-content">
<div class="admin-responsive-columns">
<div class="gt-f1">
<h4 class="ui top attached header">
{{.Title}}
<div class="ui right">
<a class="ui primary tiny button" href="{{.Link}}/edit">{{ctx.Locale.Tr "admin.users.edit"}}</a>
</div>
</h4>
<div class="ui attached segment">
{{template "admin/user/view_details" .}}
</div>
</div>
<div class="gt-f1">
<h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.emails"}}
<div class="ui right">
{{.EmailsTotal}}
</div>
</h4>
<div class="ui attached segment">
{{template "admin/user/view_emails" .}}
</div>
</div>
</div>
<h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.repositories"}}
<div class="ui right">
{{.ReposTotal}}
</div>
</h4>
<div class="ui attached segment">
{{template "explore/repo_list" .}}
</div>
<h4 class="ui top attached header">
{{ctx.Locale.Tr "settings.organization"}}
<div class="ui right">
{{.OrgsTotal}}
</div>
</h4>
<div class="ui attached segment">
{{template "explore/user_list" .}}
</div>
</div>
{{template "admin/layout_footer" .}}

View File

@ -0,0 +1,65 @@
<div class="flex-list">
<div class="flex-item">
<div class="flex-item-leading">
{{ctx.AvatarUtils.Avatar .User 48}}
</div>
<div class="flex-item-main">
<div class="flex-item-title">
{{template "shared/user/name" .User}}
{{if .User.IsAdmin}}
<span class="ui basic label">{{ctx.Locale.Tr "admin.users.admin"}}</span>
{{end}}
</div>
<div class="flex-item-body">
<b>{{ctx.Locale.Tr "admin.users.auth_source"}}:</b>
{{if eq .LoginSource.ID 0}}
{{ctx.Locale.Tr "admin.users.local"}}
{{else}}
{{.LoginSource.Name}}
{{end}}
</div>
<div class="flex-item-body">
<b>{{ctx.Locale.Tr "admin.users.activated"}}:</b>
{{if .User.IsActive}}
{{svg "octicon-check"}}
{{else}}
{{svg "octicon-x"}}
{{end}}
</div>
<div class="flex-item-body">
<b>{{ctx.Locale.Tr "admin.users.restricted"}}:</b>
{{if .User.IsRestricted}}
{{svg "octicon-check"}}
{{else}}
{{svg "octicon-x"}}
{{end}}
</div>
<div class="flex-item-body">
<b>{{ctx.Locale.Tr "settings.visibility"}}:</b>
{{if .User.Visibility.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}}
{{if .User.Visibility.IsPrivate}}{{ctx.Locale.Tr "settings.visibility.private"}}{{end}}
</div>
<div class="flex-item-body">
<b>{{ctx.Locale.Tr "admin.users.2fa"}}:</b>
{{if .TwoFactorEnabled}}
<span class="text green">{{svg "octicon-check"}}</span>
{{else}}
{{svg "octicon-x"}}
{{end}}
</div>
{{if .User.Location}}
<div class="flex-item-body">
<span class="flex-text-inline">{{svg "octicon-location"}}{{.User.Location}}</span>
</div>
{{end}}
{{if .User.Website}}
<div class="flex-item-body">
<span class="flex-text-inline">
{{svg "octicon-link"}}
<a target="_blank" href="{{.User.Website}}">{{.User.Website}}</a>
</span>
</div>
{{end}}
</div>
</div>
</div>

View File

@ -0,0 +1,19 @@
<div class="flex-list">
{{range .Emails}}
<div class="flex-item">
<div class="flex-item-main">
<div class="flex-text-block">
{{.Email}}
{{if .IsPrimary}}
<div class="ui primary label">{{ctx.Locale.Tr "settings.primary"}}</div>
{{end}}
{{if .IsActivated}}
<div class="ui green label">{{ctx.Locale.Tr "settings.activated"}}</div>
{{else}}
<div class="ui label">{{ctx.Locale.Tr "settings.requires_activation"}}</div>
{{end}}
</div>
</div>
</div>
{{end}}
</div>

View File

@ -2,7 +2,8 @@
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/devtest.css?v={{AssetVersion}}"> <link rel="stylesheet" href="{{AssetUrlPrefix}}/css/devtest.css?v={{AssetVersion}}">
<div class="page-content devtest"> <div class="page-content devtest">
<div class="ui container"> <div class="ui container">
<h1 class="gt-border-secondary-bottom">Flex List (standalone)</h1> <h1>Flex List (standalone)</h1>
<div class="divider"></div>
<div class="flex-list"> <div class="flex-list">
<div class="flex-item"> <div class="flex-item">
<div class="flex-item-leading"> <div class="flex-item-leading">
@ -85,7 +86,7 @@
</div> </div>
</div> </div>
<div class="divider gt-my-0"></div> <div class="divider"></div>
<h1>Flex List (with "ui segment")</h1> <h1>Flex List (with "ui segment")</h1>
<div class="ui attached segment"> <div class="ui attached segment">
@ -101,6 +102,14 @@
<div class="flex-item">item 2</div> <div class="flex-item">item 2</div>
</div> </div>
</div> </div>
<h1>If parent provides the padding/margin space:</h1>
<div class="gt-border-secondary gt-py-4">
<div class="flex-list flex-space-fitted">
<div class="flex-item">item 1 (no padding top)</div>
<div class="flex-item">item 2 (no padding bottom)</div>
</div>
</div>
</div> </div>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

View File

@ -0,0 +1,31 @@
<div class="flex-list">
{{range .Users}}
<div class="flex-item flex-item-center">
<div class="flex-item-leading">
{{ctx.AvatarUtils.Avatar . 48}}
</div>
<div class="flex-item-main">
<div class="flex-item-title">
{{template "shared/user/name" .}}
{{if .Visibility.IsPrivate}}
<span class="ui basic tiny label">{{ctx.Locale.Tr "repo.desc.private"}}</span>
{{end}}
</div>
<div class="flex-item-body">
{{if .Location}}
<span class="flex-text-inline">{{svg "octicon-location"}}{{.Location}}</span>
{{end}}
{{if and .Email (or (and $.ShowUserEmail $.IsSigned (not .KeepEmailPrivate)) $.PageIsAdminUsers)}}
<span class="flex-text-inline">
{{svg "octicon-mail"}}
<a href="mailto:{{.Email}}">{{.Email}}</a>
</span>
{{end}}
<span class="flex-text-inline">{{svg "octicon-calendar"}}{{ctx.Locale.Tr "user.joined_on" (DateTime "short" .CreatedUnix) | Safe}}</span>
</div>
</div>
</div>
{{else}}
<div class="flex-item">{{ctx.Locale.Tr "explore.user_no_results"}}</div>
{{end}}
</div>

View File

@ -4,37 +4,7 @@
<div class="ui container"> <div class="ui container">
{{template "explore/search" .}} {{template "explore/search" .}}
<div class="flex-list"> {{template "explore/user_list" .}}
{{range .Users}}
<div class="flex-item flex-item-center">
<div class="flex-item-leading">
{{ctx.AvatarUtils.Avatar . 48}}
</div>
<div class="flex-item-main">
<div class="flex-item-title">
{{template "shared/user/name" .}}
{{if .Visibility.IsPrivate}}
<span class="ui basic tiny label">{{$.locale.Tr "repo.desc.private"}}</span>
{{end}}
</div>
<div class="flex-item-body">
{{if .Location}}
<span class="flex-text-inline">{{svg "octicon-location"}}{{.Location}}</span>
{{end}}
{{if and $.ShowUserEmail .Email $.IsSigned (not .KeepEmailPrivate)}}
<span class="flex-text-inline">
{{svg "octicon-mail"}}
<a href="mailto:{{.Email}}" rel="nofollow">{{.Email}}</a>
</span>
{{end}}
<span class="flex-text-inline">{{svg "octicon-calendar"}}{{$.locale.Tr "user.joined_on" (DateTime "short" .CreatedUnix) | Safe}}</span>
</div>
</div>
</div>
{{else}}
<div class="flex-item">{{$.locale.Tr "explore.user_no_results"}}</div>
{{end}}
</div>
{{template "base/paginate" .}} {{template "base/paginate" .}}
</div> </div>

View File

@ -4,10 +4,9 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no"> <meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no">
</head> </head>
{{$invite_url := printf "%sorg/invite/%s" AppUrl (QueryEscape .Invite.Token)}}
<body> <body>
<p>{{.locale.Tr "mail.team_invite.text_1" (DotEscape .Inviter.DisplayName) (DotEscape .Team.Name) (DotEscape .Organization.DisplayName) | Str2html}}</p> <p>{{.locale.Tr "mail.team_invite.text_1" (DotEscape .Inviter.DisplayName) (DotEscape .Team.Name) (DotEscape .Organization.DisplayName) | Str2html}}</p>
<p>{{.locale.Tr "mail.team_invite.text_2"}}</p><p><a href="{{$invite_url}}">{{$invite_url}}</a></p> <p>{{.locale.Tr "mail.team_invite.text_2"}}</p><p><a href="{{.InviteURL}}">{{.InviteURL}}</a></p>
<p>{{.locale.Tr "mail.link_not_working_do_paste"}}</p> <p>{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
<p>{{.locale.Tr "mail.team_invite.text_3" .Invite.Email}}</p> <p>{{.locale.Tr "mail.team_invite.text_3" .Invite.Email}}</p>

View File

@ -12,8 +12,9 @@
<button class="ui primary button">{{.locale.Tr "explore.search"}}</button> <button class="ui primary button">{{.locale.Tr "explore.search"}}</button>
</div> </div>
</form> </form>
<div class="{{if .PackageDescriptors}}flex-list{{end}} gt-pt-4"> <div>
{{range .PackageDescriptors}} {{range .PackageDescriptors}}
<div class="flex-list">
<div class="flex-item"> <div class="flex-item">
<div class="flex-item-main"> <div class="flex-item-main">
<div class="flex-item-title"> <div class="flex-item-title">
@ -34,6 +35,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
{{else}} {{else}}
{{if not .HasPackages}} {{if not .HasPackages}}
<div class="empty center"> <div class="empty center">
@ -46,7 +48,7 @@
<p>{{.locale.Tr "packages.empty.documentation" "https://docs.gitea.com/usage/packages/overview/" | Safe}}</p> <p>{{.locale.Tr "packages.empty.documentation" "https://docs.gitea.com/usage/packages/overview/" | Safe}}</p>
</div> </div>
{{else}} {{else}}
<p>{{.locale.Tr "packages.filter.no_result"}}</p> <p class="gt-py-4">{{.locale.Tr "packages.filter.no_result"}}</p>
{{end}} {{end}}
{{end}} {{end}}
{{template "base/paginate" .}} {{template "base/paginate" .}}

View File

@ -18,8 +18,9 @@
<button class="ui primary button">{{.locale.Tr "explore.search"}}</button> <button class="ui primary button">{{.locale.Tr "explore.search"}}</button>
</div> </div>
</form> </form>
<div class="{{if .PackageDescriptors}}flex-list{{end}} gt-pt-4"> <div>
{{range .PackageDescriptors}} {{range .PackageDescriptors}}
<div class="flex-list">
<div class="flex-item"> <div class="flex-item">
<div class="flex-item-main"> <div class="flex-item-main">
<a class="flex-item-title" href="{{.FullWebLink}}">{{.Version.LowerVersion}}</a> <a class="flex-item-title" href="{{.FullWebLink}}">{{.Version.LowerVersion}}</a>
@ -28,8 +29,9 @@
</div> </div>
</div> </div>
</div> </div>
</div>
{{else}} {{else}}
<p>{{.locale.Tr "packages.filter.no_result"}}</p> <p class="gt-py-4">{{.locale.Tr "packages.filter.no_result"}}</p>
{{end}} {{end}}
{{template "base/paginate" .}} {{template "base/paginate" .}}
</div> </div>

View File

@ -1,4 +1,4 @@
<div class="flex-list gt-m-0"> <div class="flex-list">
{{if eq (len .Runs) 0}} {{if eq (len .Runs) 0}}
<div class="empty center"> <div class="empty center">
{{svg "octicon-no-entry" 48}} {{svg "octicon-no-entry" 48}}

View File

@ -47,7 +47,7 @@
<td class="lines-type-marker lines-type-marker-old del-code"><span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td> <td class="lines-type-marker lines-type-marker-old del-code"><span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
<td class="lines-code lines-code-old del-code">{{/* <td class="lines-code lines-code-old del-code">{{/*
*/}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/*
*/}}<button type="button" aria-label="{{$.root.locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} invisible{{end}}" data-side="left" data-idx="{{$line.LeftIdx}}">{{/* */}}<button type="button" aria-label="{{$.root.locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} gt-invisible{{end}}" data-side="left" data-idx="{{$line.LeftIdx}}">{{/*
*/}}{{svg "octicon-plus"}}{{/* */}}{{svg "octicon-plus"}}{{/*
*/}}</button>{{/* */}}</button>{{/*
*/}}{{end}}{{/* */}}{{end}}{{/*
@ -62,7 +62,7 @@
<td class="lines-type-marker lines-type-marker-new add-code">{{if $match.RightIdx}}<span class="gt-mono" data-type-marker="{{$match.GetLineTypeMarker}}"></span>{{end}}</td> <td class="lines-type-marker lines-type-marker-new add-code">{{if $match.RightIdx}}<span class="gt-mono" data-type-marker="{{$match.GetLineTypeMarker}}"></span>{{end}}</td>
<td class="lines-code lines-code-new add-code">{{/* <td class="lines-code lines-code-new add-code">{{/*
*/}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/*
*/}}<button type="button" aria-label="{{$.root.locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-right{{if (not $match.CanComment)}} invisible{{end}}" data-side="right" data-idx="{{$match.RightIdx}}">{{/* */}}<button type="button" aria-label="{{$.root.locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-right{{if (not $match.CanComment)}} gt-invisible{{end}}" data-side="right" data-idx="{{$match.RightIdx}}">{{/*
*/}}{{svg "octicon-plus"}}{{/* */}}{{svg "octicon-plus"}}{{/*
*/}}</button>{{/* */}}</button>{{/*
*/}}{{end}}{{/* */}}{{end}}{{/*
@ -79,7 +79,7 @@
<td class="lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td> <td class="lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
<td class="lines-code lines-code-old">{{/* <td class="lines-code lines-code-old">{{/*
*/}}{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 2))}}{{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 2))}}{{/*
*/}}<button type="button" aria-label="{{$.root.locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} invisible{{end}}" data-side="left" data-idx="{{$line.LeftIdx}}">{{/* */}}<button type="button" aria-label="{{$.root.locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} gt-invisible{{end}}" data-side="left" data-idx="{{$line.LeftIdx}}">{{/*
*/}}{{svg "octicon-plus"}}{{/* */}}{{svg "octicon-plus"}}{{/*
*/}}</button>{{/* */}}</button>{{/*
*/}}{{end}}{{/* */}}{{end}}{{/*
@ -94,7 +94,7 @@
<td class="lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td> <td class="lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
<td class="lines-code lines-code-new">{{/* <td class="lines-code lines-code-new">{{/*
*/}}{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 3))}}{{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 3))}}{{/*
*/}}<button type="button" aria-label="{{$.root.locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-right{{if (not $line.CanComment)}} invisible{{end}}" data-side="right" data-idx="{{$line.RightIdx}}">{{/* */}}<button type="button" aria-label="{{$.root.locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-right{{if (not $line.CanComment)}} gt-invisible{{end}}" data-side="right" data-idx="{{$line.RightIdx}}">{{/*
*/}}{{svg "octicon-plus"}}{{/* */}}{{svg "octicon-plus"}}{{/*
*/}}</button>{{/* */}}</button>{{/*
*/}}{{end}}{{/* */}}{{end}}{{/*

View File

@ -52,7 +52,7 @@
{{else}} {{else}}
<td class="chroma lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}">{{/* <td class="chroma lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}">{{/*
*/}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/*
*/}}<button type="button" aria-label="{{$.root.locale.Tr "repo.diff.comment.add_line_comment"}}" 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}}">{{/* */}}<button type="button" aria-label="{{$.root.locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-{{if $line.RightIdx}}right{{else}}left{{end}}{{if (not $line.CanComment)}} gt-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"}}{{/* */}}{{svg "octicon-plus"}}{{/*
*/}}</button>{{/* */}}</button>{{/*
*/}}{{end}}{{/* */}}{{end}}{{/*

View File

@ -6,9 +6,9 @@
{{template "repo/code/recently_pushed_new_branches" .}} {{template "repo/code/recently_pushed_new_branches" .}}
{{if and (not .HideRepoInfo) (not .IsBlame)}} {{if and (not .HideRepoInfo) (not .IsBlame)}}
<div class="ui repo-description"> <div class="ui repo-description">
<div id="repo-desc"> <div id="repo-desc" class="gt-font-16">
{{$description := .Repository.DescriptionHTML $.Context}} {{$description := .Repository.DescriptionHTML $.Context}}
{{if $description}}<span class="description">{{$description}}</span>{{else if .IsRepositoryAdmin}}<span class="no-description text-italic">{{.locale.Tr "repo.no_desc"}}</span>{{end}} {{if $description}}<span class="description">{{$description | RenderCodeBlock}}</span>{{else if .IsRepositoryAdmin}}<span class="no-description text-italic">{{.locale.Tr "repo.no_desc"}}</span>{{end}}
<a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a> <a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a>
</div> </div>
{{if .RepoSearchEnabled}} {{if .RepoSearchEnabled}}

View File

@ -21,18 +21,18 @@
<div class="divider"></div> <div class="divider"></div>
{{end}} {{end}}
{{$previousExclusiveScope = $exclusiveScope}} {{$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 $.Context .}} <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}}gt-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> {{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}</a>
{{end}} {{end}}
<div class="divider"></div> <div class="divider"></div>
{{$previousExclusiveScope := "_no_scope"}} {{$previousExclusiveScope = "_no_scope"}}
{{range .OrgLabels}} {{range .OrgLabels}}
{{$exclusiveScope := .ExclusiveScope}} {{$exclusiveScope := .ExclusiveScope}}
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}} {{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
<div class="divider"></div> <div class="divider"></div>
{{end}} {{end}}
{{$previousExclusiveScope = $exclusiveScope}} {{$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 $.Context .}} <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}}gt-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> {{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}</a>
{{end}} {{end}}
{{else}} {{else}}

View File

@ -158,7 +158,7 @@
<div class="no-select item">{{.locale.Tr "repo.issues.new.clear_assignees"}}</div> <div class="no-select item">{{.locale.Tr "repo.issues.new.clear_assignees"}}</div>
{{range .Assignees}} {{range .Assignees}}
<a class="item muted" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}"> <a class="item muted" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}">
<span class="octicon-check invisible">{{svg "octicon-check"}}</span> <span class="octicon-check gt-invisible">{{svg "octicon-check"}}</span>
<span class="text"> <span class="text">
{{ctx.AvatarUtils.Avatar . 28 "gt-mr-3"}}{{template "repo/search_name" .}} {{ctx.AvatarUtils.Avatar . 28 "gt-mr-3"}}{{template "repo/search_name" .}}
</span> </span>

View File

@ -20,7 +20,7 @@
{{range .Reviewers}} {{range .Reviewers}}
{{if .User}} {{if .User}}
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_{{.ItemID}}" {{if not .CanChange}} data-tooltip-content="{{$.locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}> <a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_{{.ItemID}}" {{if not .CanChange}} data-tooltip-content="{{$.locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
<span class="octicon-check {{if not .Checked}}invisible{{end}}">{{svg "octicon-check"}}</span> <span class="octicon-check {{if not .Checked}}gt-invisible{{end}}">{{svg "octicon-check"}}</span>
<span class="text"> <span class="text">
{{ctx.AvatarUtils.Avatar .User 28 "gt-mr-3"}}{{template "repo/search_name" .User}} {{ctx.AvatarUtils.Avatar .User 28 "gt-mr-3"}}{{template "repo/search_name" .User}}
</span> </span>
@ -33,7 +33,7 @@
{{range .TeamReviewers}} {{range .TeamReviewers}}
{{if .Team}} {{if .Team}}
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{$.locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}> <a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{$.locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
<span class="octicon-check {{if not .Checked}}invisible{{end}}">{{svg "octicon-check" 16}}</span> <span class="octicon-check {{if not .Checked}}gt-invisible{{end}}">{{svg "octicon-check" 16}}</span>
<span class="text"> <span class="text">
{{svg "octicon-people" 16 "gt-ml-4 gt-mr-2"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}} {{svg "octicon-people" 16 "gt-ml-4 gt-mr-2"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}
</span> </span>
@ -229,7 +229,7 @@
{{$checked = true}} {{$checked = true}}
{{end}} {{end}}
{{end}} {{end}}
<span class="octicon-check {{if not $checked}}invisible{{end}}">{{svg "octicon-check"}}</span> <span class="octicon-check {{if not $checked}}gt-invisible{{end}}">{{svg "octicon-check"}}</span>
<span class="text"> <span class="text">
{{ctx.AvatarUtils.Avatar . 20 "gt-mr-3"}}{{template "repo/search_name" .}} {{ctx.AvatarUtils.Avatar . 20 "gt-mr-3"}}{{template "repo/search_name" .}}
</span> </span>

View File

@ -11,7 +11,7 @@
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<div class="{{if not .HasError}}gt-hidden{{end}} gt-mb-4" id="add-deploy-key-panel"> <div class="{{if not .HasError}}gt-hidden{{end}}" id="add-deploy-key-panel">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="field"> <div class="field">

View File

@ -1,4 +1,4 @@
<div id="issue-list" class="flex-list gt-pt-4"> <div id="issue-list" class="flex-list">
{{$approvalCounts := .ApprovalCounts}} {{$approvalCounts := .ApprovalCounts}}
{{range .Issues}} {{range .Issues}}
<div class="flex-item flex-item-baseline"> <div class="flex-item flex-item-baseline">

View File

@ -116,7 +116,7 @@
<div class="flex-item-body">{{TimeSince .GetCreate $.locale}}</div> <div class="flex-item-body">{{TimeSince .GetCreate $.locale}}</div>
</div> </div>
<div class="flex-item-trailing"> <div class="flex-item-trailing">
{{svg (printf "octicon-%s" (ActionIcon .GetOpType)) 32}} {{svg (printf "octicon-%s" (ActionIcon .GetOpType)) 32 "text grey gt-mr-2"}}
</div> </div>
</div> </div>
{{end}} {{end}}

View File

@ -17,9 +17,7 @@
</div> </div>
{{end}} {{end}}
{{template "user/heatmap" .}} {{template "user/heatmap" .}}
<div class="feeds">
{{template "user/dashboard/feeds" .}} {{template "user/dashboard/feeds" .}}
</div>
{{else if eq .TabName "stars"}} {{else if eq .TabName "stars"}}
<div class="stars"> <div class="stars">
{{template "explore/repo_search" .}} {{template "explore/repo_search" .}}

View File

@ -5,7 +5,7 @@
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<div class="{{if not .HasGPGError}}gt-hidden{{end}} gt-mb-4" id="add-gpg-key-panel"> <div class="{{if not .HasGPGError}}gt-hidden{{end}}" id="add-gpg-key-panel">
<form class="ui form{{if .HasGPGError}} error{{end}}" action="{{.Link}}" method="post"> <form class="ui form{{if .HasGPGError}} error{{end}}" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="title" value="none"> <input type="hidden" name="title" value="none">

View File

@ -7,7 +7,7 @@
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<div class="{{if not .HasSSHError}}gt-hidden{{end}} gt-mb-4" id="add-ssh-key-panel"> <div class="{{if not .HasSSHError}}gt-hidden{{end}}" id="add-ssh-key-panel">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="field {{if .Err_Title}}error{{end}}"> <div class="field {{if .Err_Title}}error{{end}}">

View File

@ -51,8 +51,8 @@ func testSuccessfullEdit(t *testing.T, formData user_model.User) {
func makeRequest(t *testing.T, formData user_model.User, headerCode int) { func makeRequest(t *testing.T, formData user_model.User, headerCode int) {
session := loginUser(t, "user1") session := loginUser(t, "user1")
csrf := GetCSRF(t, session, "/admin/users/"+strconv.Itoa(int(formData.ID))) csrf := GetCSRF(t, session, "/admin/users/"+strconv.Itoa(int(formData.ID))+"/edit")
req := NewRequestWithValues(t, "POST", "/admin/users/"+strconv.Itoa(int(formData.ID)), map[string]string{ req := NewRequestWithValues(t, "POST", "/admin/users/"+strconv.Itoa(int(formData.ID))+"/edit", map[string]string{
"_csrf": csrf, "_csrf": csrf,
"user_name": formData.Name, "user_name": formData.Name,
"login_name": formData.LoginName, "login_name": formData.LoginName,
@ -72,7 +72,7 @@ func TestAdminDeleteUser(t *testing.T) {
session := loginUser(t, "user1") session := loginUser(t, "user1")
csrf := GetCSRF(t, session, "/admin/users/8") csrf := GetCSRF(t, session, "/admin/users/8/edit")
req := NewRequestWithValues(t, "POST", "/admin/users/8/delete", map[string]string{ req := NewRequestWithValues(t, "POST", "/admin/users/8/delete", map[string]string{
"_csrf": csrf, "_csrf": csrf,
}) })

View File

@ -6,6 +6,8 @@ package integration
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strings"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -37,9 +39,9 @@ func TestOrgTeamEmailInvite(t *testing.T) {
session := loginUser(t, "user1") session := loginUser(t, "user1")
url := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name)
csrf := GetCSRF(t, session, url) csrf := GetCSRF(t, session, teamURL)
req := NewRequestWithValues(t, "POST", url+"/action/add", map[string]string{ req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{
"_csrf": csrf, "_csrf": csrf,
"uid": "1", "uid": "1",
"uname": user.Email, "uname": user.Email,
@ -56,9 +58,9 @@ func TestOrgTeamEmailInvite(t *testing.T) {
session = loginUser(t, user.Name) session = loginUser(t, user.Name)
// join the team // join the team
url = fmt.Sprintf("/org/invite/%s", invites[0].Token) inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token)
csrf = GetCSRF(t, session, url) csrf = GetCSRF(t, session, inviteURL)
req = NewRequestWithValues(t, "POST", url, map[string]string{ req = NewRequestWithValues(t, "POST", inviteURL, map[string]string{
"_csrf": csrf, "_csrf": csrf,
}) })
resp = session.MakeRequest(t, req, http.StatusSeeOther) resp = session.MakeRequest(t, req, http.StatusSeeOther)
@ -69,3 +71,308 @@ func TestOrgTeamEmailInvite(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, isMember) assert.True(t, isMember)
} }
// Check that users are redirected to accept the invitation correctly after login
func TestOrgTeamEmailInviteRedirectsExistingUser(t *testing.T) {
if setting.MailService == nil {
t.Skip()
return
}
defer tests.PrepareTestEnv(t)()
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
isMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.False(t, isMember)
// create the invite
session := loginUser(t, "user1")
teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name)
req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{
"_csrf": GetCSRF(t, session, teamURL),
"uid": "1",
"uname": user.Email,
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
// get the invite token
invites, err := organization.GetInvitesByTeamID(db.DefaultContext, team.ID)
assert.NoError(t, err)
assert.Len(t, invites, 1)
// accept the invite
inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token)
req = NewRequest(t, "GET", fmt.Sprintf("/user/login?redirect_to=%s", url.QueryEscape(inviteURL)))
resp = MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{
"_csrf": doc.GetCSRF(),
"user_name": "user5",
"password": "password",
})
for _, c := range resp.Result().Cookies() {
req.AddCookie(c)
}
resp = MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, inviteURL, test.RedirectURL(resp))
// complete the login process
ch := http.Header{}
ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
cr := http.Request{Header: ch}
session = emptyTestSession(t)
baseURL, err := url.Parse(setting.AppURL)
assert.NoError(t, err)
session.jar.SetCookies(baseURL, cr.Cookies())
// make the request
req = NewRequestWithValues(t, "POST", test.RedirectURL(resp), map[string]string{
"_csrf": GetCSRF(t, session, test.RedirectURL(resp)),
})
resp = session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
isMember, err = organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.True(t, isMember)
}
// Check that newly signed up users are redirected to accept the invitation correctly
func TestOrgTeamEmailInviteRedirectsNewUser(t *testing.T) {
if setting.MailService == nil {
t.Skip()
return
}
defer tests.PrepareTestEnv(t)()
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
// create the invite
session := loginUser(t, "user1")
teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name)
req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{
"_csrf": GetCSRF(t, session, teamURL),
"uid": "1",
"uname": "doesnotexist@example.com",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
// get the invite token
invites, err := organization.GetInvitesByTeamID(db.DefaultContext, team.ID)
assert.NoError(t, err)
assert.Len(t, invites, 1)
// accept the invite
inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token)
req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL)))
resp = MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{
"_csrf": doc.GetCSRF(),
"user_name": "doesnotexist",
"email": "doesnotexist@example.com",
"password": "examplePassword!1",
"retype": "examplePassword!1",
})
for _, c := range resp.Result().Cookies() {
req.AddCookie(c)
}
resp = MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, inviteURL, test.RedirectURL(resp))
// complete the signup process
ch := http.Header{}
ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
cr := http.Request{Header: ch}
session = emptyTestSession(t)
baseURL, err := url.Parse(setting.AppURL)
assert.NoError(t, err)
session.jar.SetCookies(baseURL, cr.Cookies())
// make the redirected request
req = NewRequestWithValues(t, "POST", test.RedirectURL(resp), map[string]string{
"_csrf": GetCSRF(t, session, test.RedirectURL(resp)),
})
resp = session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
// get the new user
newUser, err := user_model.GetUserByName(db.DefaultContext, "doesnotexist")
assert.NoError(t, err)
isMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, newUser.ID)
assert.NoError(t, err)
assert.True(t, isMember)
}
// Check that users are redirected correctly after confirming their email
func TestOrgTeamEmailInviteRedirectsNewUserWithActivation(t *testing.T) {
if setting.MailService == nil {
t.Skip()
return
}
// enable email confirmation temporarily
defer func(prevVal bool) {
setting.Service.RegisterEmailConfirm = prevVal
}(setting.Service.RegisterEmailConfirm)
setting.Service.RegisterEmailConfirm = true
defer tests.PrepareTestEnv(t)()
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
// create the invite
session := loginUser(t, "user1")
teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name)
req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{
"_csrf": GetCSRF(t, session, teamURL),
"uid": "1",
"uname": "doesnotexist@example.com",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
// get the invite token
invites, err := organization.GetInvitesByTeamID(db.DefaultContext, team.ID)
assert.NoError(t, err)
assert.Len(t, invites, 1)
// accept the invite
inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token)
req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL)))
inviteResp := MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{
"_csrf": doc.GetCSRF(),
"user_name": "doesnotexist",
"email": "doesnotexist@example.com",
"password": "examplePassword!1",
"retype": "examplePassword!1",
})
for _, c := range inviteResp.Result().Cookies() {
req.AddCookie(c)
}
resp = MakeRequest(t, req, http.StatusOK)
user, err := user_model.GetUserByName(db.DefaultContext, "doesnotexist")
assert.NoError(t, err)
ch := http.Header{}
ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";"))
cr := http.Request{Header: ch}
session = emptyTestSession(t)
baseURL, err := url.Parse(setting.AppURL)
assert.NoError(t, err)
session.jar.SetCookies(baseURL, cr.Cookies())
activateURL := fmt.Sprintf("/user/activate?code=%s", user.GenerateEmailActivateCode("doesnotexist@example.com"))
req = NewRequestWithValues(t, "POST", activateURL, map[string]string{
"password": "examplePassword!1",
})
// use the cookies set by the signup request
for _, c := range inviteResp.Result().Cookies() {
req.AddCookie(c)
}
resp = session.MakeRequest(t, req, http.StatusSeeOther)
// should be redirected to accept the invite
assert.Equal(t, inviteURL, test.RedirectURL(resp))
req = NewRequestWithValues(t, "POST", test.RedirectURL(resp), map[string]string{
"_csrf": GetCSRF(t, session, test.RedirectURL(resp)),
})
resp = session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
isMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.True(t, isMember)
}
// Test that a logged-in user who navigates to the sign-up link is then redirected using redirect_to
// For example: an invite may have been created before the user account was created, but they may be
// accepting the invite after having created an account separately
func TestOrgTeamEmailInviteRedirectsExistingUserWithLogin(t *testing.T) {
if setting.MailService == nil {
t.Skip()
return
}
defer tests.PrepareTestEnv(t)()
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
isMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.False(t, isMember)
// create the invite
session := loginUser(t, "user1")
teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name)
req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{
"_csrf": GetCSRF(t, session, teamURL),
"uid": "1",
"uname": user.Email,
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
// get the invite token
invites, err := organization.GetInvitesByTeamID(db.DefaultContext, team.ID)
assert.NoError(t, err)
assert.Len(t, invites, 1)
// note: the invited user has logged in
session = loginUser(t, "user5")
// accept the invite (note: this uses the sign_up url)
inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token)
req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL)))
resp = session.MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, inviteURL, test.RedirectURL(resp))
// make the request
req = NewRequestWithValues(t, "POST", test.RedirectURL(resp), map[string]string{
"_csrf": GetCSRF(t, session, test.RedirectURL(resp)),
})
resp = session.MakeRequest(t, req, http.StatusSeeOther)
req = NewRequest(t, "GET", test.RedirectURL(resp))
session.MakeRequest(t, req, http.StatusOK)
isMember, err = organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, user.ID)
assert.NoError(t, err)
assert.True(t, isMember)
}

View File

@ -42,3 +42,10 @@
.admin .table th { .admin .table th {
white-space: nowrap; white-space: nowrap;
} }
.admin-responsive-columns {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 1rem;
}

View File

@ -454,7 +454,7 @@ a.label,
background: var(--color-hover); background: var(--color-hover);
} }
.issue-title code { .inline-code-block {
padding: 2px 4px; padding: 2px 4px;
border-radius: var(--border-radius-medium); border-radius: var(--border-radius-medium);
background-color: var(--color-markup-code-block); background-color: var(--color-markup-code-block);
@ -506,13 +506,6 @@ a.label,
border-right-color: var(--color-primary); border-right-color: var(--color-primary);
} }
/* fix button enlarged vertically by svg icon */
/* TODO: change to just `.small.button:has(svg)` but may have global side effects */
.ui.action.input .small.button:has(svg) {
padding-top: 7px !important;
padding-bottom: 7px !important;
}
.ui.menu, .ui.menu,
.ui.vertical.menu { .ui.vertical.menu {
background: var(--color-menu); background: var(--color-menu);
@ -953,14 +946,6 @@ img.ui.avatar,
filter: saturate(2); filter: saturate(2);
} }
/* TODO: use gt-word-break instead */
.dont-break-out {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
hyphens: auto;
}
.full.height { .full.height {
flex-grow: 1; flex-grow: 1;
padding-bottom: 80px; padding-bottom: 80px;
@ -2014,11 +1999,6 @@ a.ui.basic.label:hover {
margin-left: 0; margin-left: 0;
} }
/* TODO: replace it with gt-invisible */
.invisible {
visibility: hidden;
}
.ui.segment, .ui.segment,
.ui.segments, .ui.segments,
.ui.attached.segment { .ui.attached.segment {

View File

@ -95,10 +95,3 @@
position: static; position: static;
} }
} }
.feeds code {
padding: 2px 4px;
border-radius: var(--border-radius);
background-color: var(--color-markup-code-block);
word-break: break-all;
}

View File

@ -236,10 +236,6 @@
} }
} }
.repository.file.list #repo-desc {
font-size: 1.2em;
}
.repository.file.list .repo-path { .repository.file.list .repo-path {
word-break: break-word; word-break: break-word;
} }

View File

@ -91,11 +91,23 @@
border-top: 1px solid var(--color-secondary); border-top: 1px solid var(--color-secondary);
} }
/* Fomantic UI segment has default "padding: 1em", so here it removes the padding-top and padding-bottom accordingly */ /* Fomantic UI segment has default "padding: 1em", so here it removes the padding-top and padding-bottom accordingly.
Developers could also use "flex-space-fitted" class to remove the first item's padding-top and the last item's padding-bottom */
.flex-list.flex-space-fitted > .flex-item:first-child,
.ui.segment > .flex-list:first-child > .flex-item:first-child { .ui.segment > .flex-list:first-child > .flex-item:first-child {
padding-top: 0; padding-top: 0;
} }
.flex-list.flex-space-fitted > .flex-item:last-child,
.ui.segment > .flex-list:last-child > .flex-item:last-child { .ui.segment > .flex-list:last-child > .flex-item:last-child {
padding-bottom: 0; padding-bottom: 0;
} }
/* If there is a divider besides the flex-list, some padding/margin are not needs */
.divider + .flex-list > .flex-item:first-child {
padding-top: 0;
}
.flex-list + .divider {
margin-top: 0;
}

View File

@ -64,9 +64,9 @@ function initRepoDiffConversationForm() {
$form.closest('.conversation-holder').replaceWith($newConversationHolder); $form.closest('.conversation-holder').replaceWith($newConversationHolder);
if ($form.closest('tr').data('line-type') === 'same') { if ($form.closest('tr').data('line-type') === 'same') {
$(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('invisible'); $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('gt-invisible');
} else { } else {
$(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('invisible'); $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('gt-invisible');
} }
$newConversationHolder.find('.dropdown').dropdown(); $newConversationHolder.find('.dropdown').dropdown();
initCompReactionSelector($newConversationHolder); initCompReactionSelector($newConversationHolder);

View File

@ -110,7 +110,7 @@ export function initRepoIssueSidebarList() {
} }
filteredResponse.results.push({ filteredResponse.results.push({
name: `#${issue.number} ${htmlEscape(issue.title) name: `#${issue.number} ${htmlEscape(issue.title)
}<div class="text small dont-break-out">${htmlEscape(issue.repository.full_name)}</div>`, }<div class="text small gt-word-break">${htmlEscape(issue.repository.full_name)}</div>`,
value: issue.id, value: issue.id,
}); });
}); });
@ -178,9 +178,9 @@ export function initRepoIssueCommentDelete() {
const idx = $conversationHolder.data('idx'); const idx = $conversationHolder.data('idx');
const lineType = $conversationHolder.closest('tr').data('line-type'); const lineType = $conversationHolder.closest('tr').data('line-type');
if (lineType === 'same') { if (lineType === 'same') {
$(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).removeClass('invisible'); $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).removeClass('gt-invisible');
} else { } else {
$(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).removeClass('invisible'); $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).removeClass('gt-invisible');
} }
$conversationHolder.remove(); $conversationHolder.remove();
} }

View File

@ -150,7 +150,7 @@ export function initRepoCommentForm() {
if ($(this).hasClass('checked')) { if ($(this).hasClass('checked')) {
$(this).removeClass('checked'); $(this).removeClass('checked');
$(this).find('.octicon-check').addClass('invisible'); $(this).find('.octicon-check').addClass('gt-invisible');
if (hasUpdateAction) { if (hasUpdateAction) {
if (!($(this).data('id') in items)) { if (!($(this).data('id') in items)) {
items[$(this).data('id')] = { items[$(this).data('id')] = {
@ -164,7 +164,7 @@ export function initRepoCommentForm() {
} }
} else { } else {
$(this).addClass('checked'); $(this).addClass('checked');
$(this).find('.octicon-check').removeClass('invisible'); $(this).find('.octicon-check').removeClass('gt-invisible');
if (hasUpdateAction) { if (hasUpdateAction) {
if (!($(this).data('id') in items)) { if (!($(this).data('id') in items)) {
items[$(this).data('id')] = { items[$(this).data('id')] = {
@ -215,7 +215,7 @@ export function initRepoCommentForm() {
$(this).parent().find('.item').each(function () { $(this).parent().find('.item').each(function () {
$(this).removeClass('checked'); $(this).removeClass('checked');
$(this).find('.octicon-check').addClass('invisible'); $(this).find('.octicon-check').addClass('gt-invisible');
}); });
if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {